速成 go 分布式内存缓存
放在以前是比较冷的项目,现在已经人手一个了
有空建议阅读极客兔兔原文手敲一遍
这里来个速成版作为个人原理复习也供各位快速回顾,有什么问题希望能不吝赐教
实现了哪些基本功能?
我们从最简单的内存 kv 缓存开始认知:
● 直接一个 map[string]any 就行了。
必要功能 1: 内存过期
● 实现方案考虑开一个协程计时,或者直接懒删除等用到再删。分别的缺点就是耗 cpu 和耗内存。
● 一般最优解还是学 redis 的懒删除 + 定时清理,综合了两者的优点
必要功能 2:被动缓存、自动回源
● 一个简单的缓存是不用回源的,这个适用于我们在业务逻辑中控制了所有手动设置缓存的场景。但更多场景的缓存设置逻辑是相同的,就是简单的不存在就设置,所以我们将其抽象成自动回源策略。
● 自动回源除了自动被动缓存以外,也可以设置定时的主动回源,方便一些我们想做缓存预热的情况。
● 回源的实现,在一些框架里边是直接接 db 的。但是这里的设计是直接留一个接口供用户自己实现。然后由于这块我们其实只抽象了一个函数,所以可以用‘接口型函数’的技巧省去一个结构体的定义。
优化点 1:并发不安全
● 首先考虑直接加互斥锁。
● 其次可以锁分片降低锁粒度 + 用 hash 来固定定位到某个分片。这个是泛用性更强的做法,同时这种思想也用在其他很多地方,数据库的分片横向扩展也有减少锁竞争这样的好处。
● 直接上 sync.Map 来主要用在增长型 set 读多写少或者插入多不怎么需要更新的场景。
优化点 2:内存泄露
● 必要性只是因为内存是理论会无限增长的,我们加一个兜底淘汰保护就好了。参考 redis 的策略默认是不提供服务,但是也能选择淘汰算法。
● 我们可以实现一个基础的 LRU 来做淘汰,当然如果确有热点数据的话,可以微调出 LRU-K 来应付实际情况。
一些细节
● 值没有使用 any,抽象了 一个 byteview,没有类型问题和反射的性能消耗,然后通过返回拷贝的方式来实现只读
实际这个项目里面其实没实现那么多
后面的变化大概就是从 go-cache 到 groupcache 了,实现源码也可以参考这两个项目
怎么解决分布式下的几个问题的:怎么定位节点,节点数量变化。
● 一致性 hash + http pool
● 一致性哈希
○ 一个哈希环,可以理解为首尾相接的数轴。我们的 key 和 ip 分别通过某种哈希算法,可以得到一个值,取模落到哈希环上。
○ 节点 ip 的哈希是环上的常驻值,然后 key 得到的值,落到环上以后,顺时针去找到它应该在的节点,这样就不用担心节点数量变化了。
○ 这种情况下容易存在的问题,就是数据倾斜,可能太多数据在一个节点上。解决方案就是‘虚拟节点’,我们增加节点数,但是不增加实际机器数。把虚拟节点映射到真实机器节点上
● http pool
○ 回顾 v 获取流程:当前机器是否有内存?没有->是否在其他机器?没有->回源
○ pool 的作用:提供 http 服务(实现 handler) + 保存定位其他 peers
○ picker + getter,一个封装一致性哈希,一个封装从 http 请求中获取缓存值
singleflight 怎么实现?
singleflight,概括来说,当并发获取缓存的时候,最先来的去拿缓存,其他的等这个最先去的结果,相同时间段内只走一趟,牺牲极小时间的即时性一致性来大量减少回源 db 的情况。
实现方案:
● key 标识一次调用,map 存储是否在调用,经过 map 走两个不同的路径,有就直接等,没有就正常调用,然后删除。
○ 参考:https://paste.mozilla.org/VvqBqQ0X
● 几个要点
○ map 的三次访问都要加锁
○ 利用 wg 来同步,阻塞和通知其他协程第一到的协程调用完了
我记得还有别的实现方案,有空来补一下,或者评论区大佬可以说一下。这个我暂时学不过来了。然后这个问题还确实被问过。
protobuf 干嘛的? 和 rpc 的关系?
● 单独 protobuf 其实很好理解,一种通用性扩展性极强的用来序列化结构数据的编码方式。
● 在分布式缓存里,他的作用就是用来代替 json 的。不过相比 json 因为他需要实现泛用性,需要写 proto 接口定义,我们一般也叫做 idl 接口定义语言。
● protobuf 就是 gprc 使用的序列化方案。一般的 rpc 其实最终还是落到用 http 来传输,主要是为了兼容性,否则每个客户端都需要做单独的适配。
--
#每天一篇简单博客 day1 (个人打卡,欢迎监督
有空建议阅读极客兔兔原文手敲一遍
这里来个速成版作为个人原理复习也供各位快速回顾,有什么问题希望能不吝赐教
我们从最简单的内存 kv 缓存开始认知:
● 直接一个 map[string]any 就行了。
必要功能 1: 内存过期
● 实现方案考虑开一个协程计时,或者直接懒删除等用到再删。分别的缺点就是耗 cpu 和耗内存。
● 一般最优解还是学 redis 的懒删除 + 定时清理,综合了两者的优点
必要功能 2:被动缓存、自动回源
● 一个简单的缓存是不用回源的,这个适用于我们在业务逻辑中控制了所有手动设置缓存的场景。但更多场景的缓存设置逻辑是相同的,就是简单的不存在就设置,所以我们将其抽象成自动回源策略。
● 自动回源除了自动被动缓存以外,也可以设置定时的主动回源,方便一些我们想做缓存预热的情况。
● 回源的实现,在一些框架里边是直接接 db 的。但是这里的设计是直接留一个接口供用户自己实现。然后由于这块我们其实只抽象了一个函数,所以可以用‘接口型函数’的技巧省去一个结构体的定义。
优化点 1:并发不安全
● 首先考虑直接加互斥锁。
● 其次可以锁分片降低锁粒度 + 用 hash 来固定定位到某个分片。这个是泛用性更强的做法,同时这种思想也用在其他很多地方,数据库的分片横向扩展也有减少锁竞争这样的好处。
● 直接上 sync.Map 来主要用在增长型 set 读多写少或者插入多不怎么需要更新的场景。
优化点 2:内存泄露
● 必要性只是因为内存是理论会无限增长的,我们加一个兜底淘汰保护就好了。参考 redis 的策略默认是不提供服务,但是也能选择淘汰算法。
● 我们可以实现一个基础的 LRU 来做淘汰,当然如果确有热点数据的话,可以微调出 LRU-K 来应付实际情况。
一些细节
● 值没有使用 any,抽象了 一个 byteview,没有类型问题和反射的性能消耗,然后通过返回拷贝的方式来实现只读
实际这个项目里面其实没实现那么多
后面的变化大概就是从 go-cache 到 groupcache 了,实现源码也可以参考这两个项目
● 一致性 hash + http pool
● 一致性哈希
○ 一个哈希环,可以理解为首尾相接的数轴。我们的 key 和 ip 分别通过某种哈希算法,可以得到一个值,取模落到哈希环上。
○ 节点 ip 的哈希是环上的常驻值,然后 key 得到的值,落到环上以后,顺时针去找到它应该在的节点,这样就不用担心节点数量变化了。
○ 这种情况下容易存在的问题,就是数据倾斜,可能太多数据在一个节点上。解决方案就是‘虚拟节点’,我们增加节点数,但是不增加实际机器数。把虚拟节点映射到真实机器节点上
● http pool
○ 回顾 v 获取流程:当前机器是否有内存?没有->是否在其他机器?没有->回源
○ pool 的作用:提供 http 服务(实现 handler) + 保存定位其他 peers
○ picker + getter,一个封装一致性哈希,一个封装从 http 请求中获取缓存值
singleflight,概括来说,当并发获取缓存的时候,最先来的去拿缓存,其他的等这个最先去的结果,相同时间段内只走一趟,牺牲极小时间的即时性一致性来大量减少回源 db 的情况。
实现方案:
● key 标识一次调用,map 存储是否在调用,经过 map 走两个不同的路径,有就直接等,没有就正常调用,然后删除。
○ 参考:https://paste.mozilla.org/VvqBqQ0X
● 几个要点
○ map 的三次访问都要加锁
○ 利用 wg 来同步,阻塞和通知其他协程第一到的协程调用完了
我记得还有别的实现方案,有空来补一下,或者评论区大佬可以说一下。这个我暂时学不过来了。然后这个问题还确实被问过。
● 单独 protobuf 其实很好理解,一种通用性扩展性极强的用来序列化结构数据的编码方式。
● 在分布式缓存里,他的作用就是用来代替 json 的。不过相比 json 因为他需要实现泛用性,需要写 proto 接口定义,我们一般也叫做 idl 接口定义语言。
● protobuf 就是 gprc 使用的序列化方案。一般的 rpc 其实最终还是落到用 http 来传输,主要是为了兼容性,否则每个客户端都需要做单独的适配。
--
#每天一篇简单博客 day1 (个人打卡,欢迎监督
全部评论
相关推荐
点赞 评论 收藏
分享
05-13 20:25
南京邮电大学 算法工程师 点赞 评论 收藏
分享