Pika支持Zset限长方案

背景

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
########################
## Zset auto del setting
########################
# when the number of zset is more than the threshold, auto delete members. 0 means disable auto delete.
zset-auto-del-threshold : 100
# delete direction, 0 means delete from head, -1 means delete from tail.
zset-auto-del-direction : 0
# the number of deleted member
zset-auto-del-num : 1
# auto delete cron task, forhat: start-end, like 02-04, pika will check to schedule auto delete zset between 2 to 4 o'clock everyday
zset-auto-del-cron :
# auto delete interval, format: interval, like 6, pika will check to schedule auto delete zset every 6 hours.
zset-auto-del-interval : 1
# when delete a key members success, will sleep for a moment. sleep time = delete time * speed factor. you can set like 0.1 [0,1000]
zset-auto-del-cron-speed-factor : 0.1
# scan the number of keys from zset db at a time.
zset-auto-del-scan-round-num : 10000
# the ratio of how mutch deleted zset keys can trigger compact zset db, [0,1]
zset-compact-del-ratio : 1
# how mutch deleted zset keys num that can trigger compact zset db
zset-compact-del-num : 100000000

删除流程

我们首先以手动删除命令 zsetautodel 开始讲,执行该命令时需要传入两个参数 cursor(游标) 和 speed-factor(睡眠时间)

截屏2023-08-08 15 13 39 截屏2023-08-08 15 14 39

这里我们看下 RequestManualTask 这个函数,这里我设计了一个结构体 ZsetTaskItem 用来存三个参数,一个是task_type,一个是 cursor,speed_factor,然后执行 DoZsetAutoDelTask 这个函数。

截屏2023-08-08 15 40 36

这里有个 switch 语句来判断执行哪一段逻辑,如果是手动删除,则是 ZSET_MANUAL_TASK,自动删除的话则是 ZSET_CRON_TASK,

截屏2023-08-08 15 40 49

由于我们可能一次清理不能删除掉所有的 Zset 的 field,所以这里设置了一个 while 循环,这里的 stop_manual_task_ 就是跳出循环的条件,这个和另外的那个命令有联系,如果你执行 zsetautodeloff 命令的话,这里的 stop_manual_task 就会被设置成 true,从而跳出循环,结束清理。
截屏2023-08-08 15 41 07

删除部分主要是执行函数是这个 BatchTrimZsetKeys 我们可以看到先获取游标,然后遍历所有的 key,用 Zcard 先获取对应的 key 中需不需要进行删除,如果需要则使用 ZRemrangebyrank 命令进行删除,这里支持头部和尾部删除,在配置文件中的 zset-auto-del-direction 如果设置为 0 则是头部删除,为 1 则是尾部删除.
截屏2023-08-08 15 41 42
截屏2023-08-08 15 42 00

接下来我们看下自动删除的流程(和手动删除流程差不多),首先是 DoAutoDelZsetMember 这个函数没隔 5 秒执行一次,然后符合删除条件的话调用 RequestCronTask() 函数进行删除,下面的逻辑就和手动删除是类似的了

截屏2023-08-08 15 27 57 截屏2023-08-08 15 28 42

实践效果

我们先用压测工具往一个 Zset 里面加 597 个 field,然后执行 ZsetAutoDel 0 0.1 进行删除,可以看到此时的 Zset 里面的 field 已经被删掉成 99 个了。

截屏2023-08-08 15 32 20 截屏2023-08-08 15 33 38

总结

功能上已经进行了实现,但是有些地方还是没写仔细,比如删 Zset 的同时写 Binlog 的流程还没有走通,以及进行删除时对每个 key 进行上锁操作,保证数据一致性。同时关于 Zset 的限长个数,我们是在配置文件中写死的,我们不能动态更改 Zset 的限制长度。考虑后期加命令去实现这部分配置文件中数据的更改。

相关 PR: #1846

相关 Issue: #1720