背景
Pika 中的 Zset 这种数据结构不能限制单个 Zset 存储的最大元素个数,导致一个 Zset 里面可以存无限制的 field,我们希望 Pika 支持设置 Zset 存储的最大元素个数,当超过用户设置的最大元素个数时,可以自动清理 Zset 中不需要的数据,非常适合只保存定量历史数据的业务场景.
主要特性:
- 支持设置 Zset 保存的最大元素个数
- 支持头部和尾部删除策略
- 支持动态设置执行删除任务周期,错峰删除,避免影响线上业务
- 支持手动执行删除任务
设计方案
我们希望从两个方面入手,第一部分是靠 Pika 内部自动定期清理 Zset 中,这里的定期包括规定时间(比如每隔一小时)和规定时段 (比如每天两点到四点)以下是 Pika.conf 中新增的变量. 这里的 zset-auto-del-interval 和 zset-auto-del-cron 就是我上面说的那两个定期方式. 这种定期删除的方法,我们以一个线程的方式放在 Pika 的DoTimingTask 里面,即每隔 5 秒系统会检查一下,到了规定时间即进行删除操作. 另一种方式则是以命令的方式进行删除,我们新增了两个命令 zsetautodel 和 zsetautodeloff 这两个命令,第一个命令是执行手动删除,执行时我们传入两个参数 cursor(游标) 和 speed-factor(睡眠时间) ,然后进行删除操作,zsetautodeloff 这个命令即是停止此时的手动删除操作(自动删除不能停止). 接下来我们来讲一下怎么进行具体的删除操作.
1 | ######################## |
删除流程
我们首先以手动删除命令 zsetautodel 开始讲,执行该命令时需要传入两个参数 cursor(游标) 和 speed-factor(睡眠时间)
这里我们看下 RequestManualTask 这个函数,这里我设计了一个结构体 ZsetTaskItem 用来存三个参数,一个是task_type,一个是 cursor,speed_factor,然后执行 DoZsetAutoDelTask 这个函数。
这里有个 switch 语句来判断执行哪一段逻辑,如果是手动删除,则是 ZSET_MANUAL_TASK,自动删除的话则是 ZSET_CRON_TASK,
由于我们可能一次清理不能删除掉所有的 Zset 的 field,所以这里设置了一个 while 循环,这里的 stop_manual_task_ 就是跳出循环的条件,这个和另外的那个命令有联系,如果你执行 zsetautodeloff 命令的话,这里的 stop_manual_task 就会被设置成 true,从而跳出循环,结束清理。
删除部分主要是执行函数是这个 BatchTrimZsetKeys 我们可以看到先获取游标,然后遍历所有的 key,用 Zcard 先获取对应的 key 中需不需要进行删除,如果需要则使用 ZRemrangebyrank 命令进行删除,这里支持头部和尾部删除,在配置文件中的 zset-auto-del-direction 如果设置为 0 则是头部删除,为 1 则是尾部删除.
接下来我们看下自动删除的流程(和手动删除流程差不多),首先是 DoAutoDelZsetMember 这个函数没隔 5 秒执行一次,然后符合删除条件的话调用 RequestCronTask() 函数进行删除,下面的逻辑就和手动删除是类似的了
实践效果
我们先用压测工具往一个 Zset 里面加 597 个 field,然后执行 ZsetAutoDel 0 0.1 进行删除,可以看到此时的 Zset 里面的 field 已经被删掉成 99 个了。
总结
功能上已经进行了实现,但是有些地方还是没写仔细,比如删 Zset 的同时写 Binlog 的流程还没有走通,以及进行删除时对每个 key 进行上锁操作,保证数据一致性。同时关于 Zset 的限长个数,我们是在配置文件中写死的,我们不能动态更改 Zset 的限制长度。考虑后期加命令去实现这部分配置文件中数据的更改。
相关 PR: #1846
相关 Issue: #1720