红黑树+小根堆实现的基于优先级的本地缓存#20
Conversation
|
....\ekit\queue\concurrent_array_blocking_queue.go:21:2: missing go.sum entry for module providing package golang.org/x/sync/semaphore (imported by github.com/ecodeclub/ekit/queue); to add: 基本没啥大的改动,优先级队列的peak、入队、出队三个操作可以无缝替换小根堆的查看堆顶、插入结点、提去堆顶三个操作 |
|
要先等 ekit 那边暴露了优先级队列,直接使用那边的优先级队列。 |
flycash
left a comment
There was a problem hiding this comment.
你是不是没有处理过期时间?支持过期?
这个实现看起来没特别大的问题,但是太复杂了。我想到一个比较简单的方式,可以简化实现,并且降低本身内存结构的消耗。
也就是保持两个结构,一个红黑树,放数据;一个是优先级队列,放优先级信息。
运作方式就是:
- 过期时间控制采用懒惰 + 定时删除的策略。也就是在 GET 的时候会检测有没有过期,如果过期了的话直接返回不存在,同时执行红黑树上的删除。
- 红黑树上的删除,就是把 Data 字段(也就是用户的数据)重置为 nil,对应的 key 也置为 nil。但是优先级队列上的不动,或者说是把这个节点标记位已经被删除。
- 这时候,这种被删除的节点就只存在优先级队列了。
- 当触发了淘汰策略的时候,就从优先级队列里面淘汰元素。遵循这种逻辑:不断从队列里面取出来元素,如果节点本身被标记位已经删除了,就下一个;否则就是真的淘汰了一个节点。
- 启动一个 goroutine,遍历红黑树,懒惰删除过期数据。
|
1、数据结构简化之后,逻辑确实简单多了。 2、lru和lfu这两种策略和优先级策略作用的对象不同,还是上面那个问题,priorityType这个字段我没想到好办法绕过去。 3、定时删除过期数据那里,怎么设置时间间隔呢?sleep一秒还是无限循环中间不停,不停总有点费cpu的感觉。 4、优先级队列的初始化大小还有set用的MapSet的初始化大小那两个init参数,option模式好像也解决不了用户不传的问题,还是说这里默认传个0也是没太大问题的 |
Codecov Report
@@ Coverage Diff @@
## main #20 +/- ##
==========================================
+ Coverage 96.21% 98.39% +2.18%
==========================================
Files 2 4 +2
Lines 264 624 +360
==========================================
+ Hits 254 614 +360
Misses 7 7
Partials 3 3
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
flycash
left a comment
There was a problem hiding this comment.
创建一个 priority 的子包,然后把这个代码丢进去。而后,额外提供两个 New 方法,NewLRUTreeCache 和 NewLFUTreeCache。这样,就可以删除 WithPriorityType 这个选项。用户不能指定。用户通过 NewLRU 或者 LFU 创建的就是对应的缓存,而直接通过 RBTreePriority 创建的,就是普通的优先级。
| ErrWrongPriorityType = errors.New("ecache: 错误的优先级类型") | ||
| ErrOnlyKVCanSet = errors.New("ecache: 只有 kv 类型的数据,才能执行 Set") | ||
| ErrOnlyKVCanGet = errors.New("ecache: 只有 kv 类型的数据,才能执行 Get") | ||
| ErrOnlyKVNXCanSetNX = errors.New("ecache: 只有 SetNX 创建的数据,才能执行 SetNX") | ||
| ErrOnlyKVCanGetSet = errors.New("ecache: 只有 kv 类型的数据,才能执行 GetSet") | ||
| ErrOnlyListCanLPUSH = errors.New("ecache: 只有 list 类型的数据,才能执行 LPush") | ||
| ErrOnlyListCanLPOP = errors.New("ecache: 只有 list 类型的数据,才能执行 LPop") | ||
| ErrOnlySetCanSAdd = errors.New("ecache: 只有 set 类型的数据,才能执行 SAdd") | ||
| ErrOnlySetCanSRem = errors.New("ecache: 只有 set 类型的数据,才能执行 SRem") | ||
| ErrOnlyNumCanIncrBy = errors.New("ecache: 只有数字类型的数据,才能执行 IncrBy") | ||
| ErrOnlyNumCanDecrBy = errors.New("ecache: 只有数字类型的数据,才能执行 DecrBy") |
|
有咩有可能提供一个 PriorityStrateg,把内部和 LRU,LFU 的操作都封装到里面?比如说更新优先级,或者获取优先级。 |
|
把之前保存优先级数据的结构扩展了一下,把和优先级有关系的操作都从缓存结构里面抽出来整到里面去了, |
|
我发现一个新的问题,就是在 ekit 的优先级队列里面,调整优先级并不会触发堆结构调整。这是不是说,你这里调整了优先级之后,其实效果不对? |
|
应该是咩有效果。 |
|
如果是没有效果的话,就要把 LRU 和 LFU 删掉,后面再单独各自写一个。 |
|
ekit 的那个优先级队列直接改优先级数值是没用的。 |
flycash
left a comment
There was a problem hiding this comment.
我感觉这种实现太复杂了,性价比不高。这里要改为:
- 不要支持 LRU 和 LFU,后面单独提供新的实现。
- 认为节点的优先级不会发生改变。即便用户乱搞,改变了也没关系,因为我们说按照优先级淘汰,就算是淘汰错了,也没有特别大的影响。从我之前使用优先级淘汰策略来看,的确不存在修改的场景。
- 对应的,这里面的 priority _node 和 priority_strategy 应该都用不上了
对了,把现有的代码放好,说不定将来我又需要了,还能找回来。
|
优先级结点priority _node和优先级策略priority_strategy这两个结构就直接移除了。 优先级的代码就都揉到缓存结构上去了,优先级队列里面装的直接就是缓存结点,缓存结点需要额外存一下本身的优先级,要不然每次都要重新算。 缓存过期自动清理逻辑需要删除的时候,因为优先级队列不支持随机删除,所以采用的是清空结点上的数据(key="",val是个any所以我给了struct{}{}),然后结点本身不动的策略。等到触发淘汰的时候再说,出队发现是空的就循环继续看下一个就好。 |
|
大哥,解决冲突! |
红黑树+小根堆实现的基于优先级的本地缓存
缓存本体用红黑树,数据用缓存结点做一层封装
通过缓存结点可以得到一个权重值出来,这个权重值用小根堆处理
小根堆的结构是,权重作为排序,然后数据用一个优先级结点做一层封装
额外维护一个map,map[权重]优先级结点,解决小根堆查找时需要遍历的问题
小根堆和这个map,做一个封装
小根堆自带asc排序,至于desc排序,我内置了一个 (max int64)/2 作位权重最大值,作差就可以用小根堆实现desc
优先级结点里面有一个map用于保存缓存结点的指针,缓存结点里也会维护一个优先级结点的指针
双向的指针,方便删除缓存或者发生淘汰时,双向的查找
lru,lfu,内存大小,自定义优先级,暂时没想到一个统一的抽象能把这四个都塞进去
ekit 那边实现了一个小根堆结构,同时需要把internal里面的红黑树也放出来