What's new in PikiwiDB(Pika) v3.5.5
尊敬的社区朋友及 PikiwiDB(Pika) 用户们:
非常高兴地宣布 — PikiwiDB(Pika)【下文简称 Pika】长期维护版本 v3.5 的最新版本 — v3.5.5 今天正式发布。在这个版本中,除了修复所有已知的主从复制 bug 外,通过引入 RTC 特性其性能又有了大幅度提升,且主从 failover 机制兼容了 Redis-Sentinel。
1 重大改进
1.1 RTC 模型
在 v3.5.5 版本中我们引入了 RTC 模型,旨在提高缓存的访问效率,具体修改流程如下所示:
上图左侧展示的是 Pika 服务请求的 IO 处理路径:请求依次经过网络线程、请求队列、工作线程,最终到达内存或硬盘引擎进行处理。这种设计的不足之处在于,请求在各个线程间传递会导致频繁的 CPU 上下文切换以及 CPU 高速缓存的 Cache Miss 问题,从而增加 CPU 资源的消耗。在高并发场景下,这种额外的 CPU 消耗尤为显著。
年初(2024 年 3 月)美团发布了 《美团大规模 KV 存储挑战与架构实践》一文,给出了一个名为 RTC(Run-to-Completion) 的技术方案,网络线程被赋予了直接处理读请求的能力,特别是那些能直接从内存引擎中命中的读请求。
当时 Pika 已经实现了混合存储:在内存 RedisCache 中缓存热数据,在磁盘中存储全量数据。在这个基础之上,恰好看到这个 RTC 解决方案,我们觉得这个方案甚好,可以优化 Pika 的读写流程,提高吞吐,降低延时。引入 RTC 方案后,新读写流程如上图右侧:
当网络线程接收到请求后,Pika 首先判断该请求是否为读操作请求。
如果是读请求,并且能在 RedisCache 中找到所需的数据(RedisCache 命中读请求),则网络线程直接返回结果给客户端。
对于写请求或 RedisCache 未命中的读请求,则依旧沿用原有的处理路径,访问 DB 硬盘引擎进行读写。
RTC 在网络线程内部就能实现 Redis 读命中的闭环处理,避免了由于请求在不同线程间的流转而带来的 CPU 资源开销。具体而言,Pika 的多个 Worker 线程争抢同一个 TaskQueue,且 Worker 在等待任务队列时,没有任何轻量级等待策略,直接使用了“很重” 的 cv.wait(std::condition_variable::wait),如果不使用 RedisCache,这或许不构成瓶颈,因为读写请求都打在 RocksDB 上,此时 Pika 工作线程需要等待 RocksDB 的读写流程。但如果打开了 Pika RedisCache 开关且缓存命中率超过 60% 时,热点数据集中度较高,在读操作远多于写操作的高并发场景下,这个线程池中转的过程就会构成性能瓶颈。
通过压测发现,打开 RedisCache 和 RTC 模型开关,且在 Pika 内嵌 Redis Cache 缓存命中率达到 60% 以上的高并发情况下,Get QPS 能提高到原本的 1.5 倍(从 18.4w 到 27.2w),P99 延时降低 47.5% (2.4ms 以下)。尽管此模型仅针对读缓存命中的读多写少请求,但鉴于实际应用场景中读操作远多于写操作,并且热点数据集中度较高,因此在多数业务集群部署后均实现了显著的性能提升。
关键 ISSUE/PR:
Improve the RTC process of Read/Write modelhttps://github.com/OpenAtomFoundation/pika/issues/2542Pika 使用 RTC 模型访问 Rediscachehttps://github.com/OpenAtomFoundation/pika/pull/2837Pika RTC 模型开关https://github.com/OpenAtomFoundation/pika/pull/2841
1.2 通过 Redis-Sentinel 对 Pika 主从实例进行故障自愈
自一年前 (2023 年 7 月)发布 v3.5.0 版本以来,Pika 无法兼容 Redis-Sentinel 进行 auto failover,主要有以下几个原因:
事务冲突
Redis-Sentinel 选举以后,进行主从切换操作时会给 slave 发送 "slaveof no one" 等一批 事务形式的命令:
当时 Pika 不支持 "client kill type pubsub" 与 "client kill type normal" 命令,整个事务会 abort 不执行,导致实际的 slaveof 命令没有执行,所以无法完成新主切换。针对这个问题,在 PR 2854 中支持了这两个命令:"client kill type pubsub" 杀掉所有 pubsub client 连接,"client kill type normal" 命令杀掉除 pubsub 外的其他 client 连接。
指标不兼容
Redis-Sentinel 通过 "info" 命令的指标 "slave_repl_offset" 作为选主依据,这个指标反映了某个 slave 实例从 master 接收的主从同步字节数的总量(多 DB 场景下这个值就是多 DB 同步字节数的总和),而 Pika 的主从体系下每个 DB 各自独立,每个 DB 都有一个 binlog 偏移量,由 (filenum, offset) 这样二维坐标形式的两个数字构成,没有提供 "slave_repl_offset" 指标供 Redis-Sentinel 做判断。Pika v3.3.6 版本没有提供这个指标,但通过 fake 数据方式兼容了 Redis-Sentinel, 但这样的选主其实是不可靠的。
在 PR 2854 中,Pika 通过算式 "slave_repl_offset = filenum * filesize + offset" 给出了 "info" 命令的 "slave_repl_offset" 指标。
slave-priority 默认参数的变更
Pika Slave "info" 命令的 "slave-priority" 指标默认值为 0,这个值导致 Pika Slave 不会被 Redis-Sentinel 列入候选 master。
在 PR 2854 中,Pika 将这个默认值改为 100,Redis-Sentinel 会将 Pika Slave 实例列入候选 master。
进程 coredump
在修复以上问题后,在测试过程中 发现 Redis-Sentinel 连接 Pika 实例并发出 "shutdown" 命令 Pika 实例会 coredump。追查后发现:Pika 退出时的资源释放流程中,网络线程关闭的时机不对(关的太晚了),有些重要资源对象已经析构了,但网络线程还能收发包,这就导致 Pika 实例收到 "shutdown" 命令时使用已经析构的资源对象进行命令处理,最终导致 Pika 实例 coredump。
在 PR 2854 中,修改了网络线程 Stop 函数的位置,提前终止线程 pika_dispatcher_thread_,停止 dispatcher 进行网络任务分配。
一个意外收获
在测试过程中,还发现了一个资源退出时的主从同步相关的问题:Pika Slave 消费 Binlog 时,先将 Binlog 落盘,然后异步提交 WriteDB 任务进行数据的物化(重放至 RocksDB),Pika 退出时不会等待这些异步 WriteDB 任务,这就可能导致 Slave 丢数据。
在 PR 2854 中,Pika 退出时,会等到 WriteDBWorker TaskQueue 为空才继续往下释放资源,退出进程。
关键 PR:
Pika 兼容 Redis-Sentinel 进行主从切换https://github.com/OpenAtomFoundation/pika/pull/2854
1.3 主从复制
Pika v3.3.6 有很多主从复制的缺陷,如丢数据、数据污染、死锁等问题。v3.5.5 版本对 Pika 全量复制及增量复制进行了大量优化和 bug 修复,取得了非常好的效果。
例如,PR 2638 在 "info" 命令中输出了 "repl_connect_status" 指标,以方便用户更加清晰看到当前 Pika 实例的主从复制状态。
关键 PR:
优化主从复制,确保 Master 端的 SlaveNode 在提交 bgsave 任务前进入 DBSync 状态,防止 bgsave 执行时的 binlog 在极端情况下被清除https://github.com/OpenAtomFoundation/pika/pull/2798
修改主从复制过程中 flushdb binlog 的处理逻辑,确保按照顺序执行,避免出现主从不一致的情况https://github.com/OpenAtomFoundation/pika/pull/2790
优化 Apply binlog 时锁机制,减少不必要的锁竞争https://github.com/OpenAtomFoundation/pika/pull/2773
修复批量扩容时,多个 slave 同时连接 master,短时间多次 bgsave 导致部分从节点数据不完整的问题https://github.com/OpenAtomFoundation/pika/pull/2746
修复 Spop 在写 binlog 时可能会出现竞态问题https://github.com/OpenAtomFoundation/pika/pull/2647
修复多 DB 下全量同步超时后不重试的问题https://github.com/OpenAtomFoundation/pika/pull/2667
修复多 DB 主从超时场景下,可能会出现窗口崩溃的问题https://github.com/OpenAtomFoundation/pika/pull/2666
修复主从同步限速逻辑中重复解锁的问题https://github.com/OpenAtomFoundation/pika/pull/2657
增加主从复制状态指标 repl_connect_status,方便运维人员清晰明确的判断当前的主从复制状态https://github.com/OpenAtomFoundation/pika/pull/2656
重构主从复制模式 slave 节点的主从同步线程模型,尽可能减少 binlog 消费阻塞问题https://github.com/OpenAtomFoundation/pika/pull/2638
1.4 RocksDB Compaction
Pika 的底层磁盘存储引擎 RocksDB 在进行 compaction 时会显著影响 Pika 的读写性能。因此,控制好 compaction 是优化 Pika 读写性能的关键。
v3.5.5 使用了 v8.7.3 版本的 RocksDB,开放了更多 RocksDB 参数,以方便用户优化 RocksDB 性能:
min-write-buffer-number-to-merge: 默认值为 1,如果将此值设置得更大,意味着需要更多的写缓冲区被填满后才进行 flush。这样可以减少 flush 的频率,增加数据在内存中的累积量,从而可能提高写入吞吐量。
level0-stop-writes-trigger: 默认值为 36,定义了 L0 层中 sst 文件的最大数量,一旦达到这个数量,RocksDB 将会采取 暂停写入、强制 compaction 等措施来防止写入操作继续累积,以避免 L0 层变得过于庞大,进而可能导致写入放大、查询性能下降等问题。
level0-slowdown-writes-trigger:默认值为 20,用于控制当 Level 0 的 SST 文件数量达到这个阈值时,触发写减速(write slowdown),防止 Level 0 的文件数量过多,导致后续 compaction 操作的压力过大。
level0-file-num-compaction-trigger:默认值为 4,当 Level 0 的 SST 文件数量达到这个参数设定的阈值时,RocksDB 会开始执行 compaction 操作,将 Level 0 的文件合并到 Level 1,以减少 Level 0 的文件数量,降低读取延迟,并优化存储空间的利用率。
max-subcompactions:默认值为 1,用于控制 RocksDB 中并发执行的 sub-compaction 任务数量,其值为 1 表示关闭 sub-compaction。如果系统资源充足,建议提升该参数以优化 compaction 效率。
max-bytes-for-level-base:指定了 L1 SST 文件总的大小。这个大小是 RocksDB 进行数据分层管理和 compaction 决策的重要依据:如果 L1 层的大小设置得太小,可能会导致 L0 层的 compaction 过于频繁,进而影响写性能。反之,如果设置得太大,可能会占用较多的磁盘空间,并且影响读取性能,因为读取操作可能需要跨越更多的层级。Pika 没有在 pika.conf 中开放此参数给用户配置,而是使用其他参数(level0-file-num-compaction-trigger 和 write-buffer-size)计算后的结果。
关键 PR:
新增 RocksDB Compaction 策略动态调整参数,用户可以根据业务调整 Compaction 策略,降低 Compaction 操作对服务性能的损耗https://github.com/OpenAtomFoundation/pika/pull/2538
1.5 测试集
Pika 测试集由 gTest 单测集、Redis TCL 测试集和 Go 测试集组成。v3.5.5 丰富了诸多特性的 go test 功能,并进一步完善了基本数据类型的 TCL 测试。
关键 PR:
Pika Geo 数据类型增加 TCL 测试,并修复测试过程中遇到的缺陷https://github.com/OpenAtomFoundation/pika/pull/2753
2 改进列表
下面详细列出了本次发版的主要功能升级和改进。
2.1 新特性
给 RTC 模型增加处理开关,可以根据开关控制是否开启 RTC 模型https://github.com/OpenAtomFoundation/pika/pull/2841
用 RTC 模型处理 Pika 访问缓存部分,提升 Pika 服务的读性能https://github.com/OpenAtomFoundation/pika/pull/2837
Incr、append 命令在传输 binlog 时,使用 pksetexat 命令,防止因为不正确的操作导致数据无法过期,出现脏数据https://github.com/OpenAtomFoundation/pika/pull/2833
将管理命令移出主线程,防止因为管理命令调用频繁或者耗时过高,阻塞主线程https://github.com/OpenAtomFoundation/pika/pull/2727
Pika 线程整理,避免启动无用线程造成资源浪费https://github.com/OpenAtomFoundation/pika/pull/2697
添加 pika benchark 工具,旨在提升压测效率,并输出可视化的统计图表https://github.com/OpenAtomFoundation/pika/pull/2663
2.2 bug 修复
修改 Pika 自动化测试客户端链接 server 端的超时时间,避免因为长时间断开导致测试失败https://github.com/OpenAtomFoundation/pika/pull/2683
修复 kill client 命令杀连接流程不正确的https://github.com/OpenAtomFoundation/pika/pull/2682
修复
blpop/brpop
更新数据库的时候未更新缓存可能回导致数据库、缓存不一致的现象https://github.com/OpenAtomFoundation/pika/pull/2858修复 Pika 不支持 Redis-Sentinel 的问题https://github.com/OpenAtomFoundation/pika/pull/2854
修改
flushall
的逻辑避免统一处理时,多清理一次数据https://github.com/OpenAtomFoundation/pika/pull/2846PkPatternMatchDel
命令在删除 DB 的同时删除 cache,避免出现数据库、缓存不一致的情况https://github.com/OpenAtomFoundation/pika/pull/2839优化读锁的范围,避免因为重复消费 binlog 导致主、从数据不一致的问题https://github.com/OpenAtomFoundation/pika/pull/2818
修改 client watch 的 key,被任何人修改(包括自己的改动),都会失效的问题https://github.com/OpenAtomFoundation/pika/pull/2815
修改 slave_priority 的默认值,防止因为运维同学没有设置该值,导致主从切换失败https://github.com/OpenAtomFoundation/pika/pull/2813
Multi 命令更新数据库的同时更新缓存,避免读取不到数据https://github.com/OpenAtomFoundation/pika/pull/2810
修复执行
slaveof no one
时出现异常日志输出的问题https://github.com/OpenAtomFoundation/pika/pull/2800修复 Pika block-cache 信息计算不准确,内存使用计算不标准的问题https://github.com/OpenAtomFoundation/pika/pull/2797
修改主从复制过程中 flushdb binlog 的处理逻辑,确保按照顺序执行,避免出现主从不一致的情况https://github.com/OpenAtomFoundation/pika/pull/2794
添加标志位、时间戳和返回值机制,确保 Pika 在执行 flushdb 且处理异步删除旧目录时冲突时正确处理https://github.com/OpenAtomFoundation/pika/pull/2790
修改主从复制过程中 flushdb binlog 的处理逻辑,确保按照顺序执行,避免出现主从不一致的情况https://github.com/OpenAtomFoundation/pika/pull/2793
修复
PKPatternMatchDel
命令未删除迭代器可能会导致 RocksDB 删除数据不彻底的问题https://github.com/OpenAtomFoundation/pika/pull/2786timerTaskThread_ 重命令为 timer_task_thread_https://github.com/OpenAtomFoundation/pika/pull/2776
修复 min-blob-size 参数解析失败,导致 kv 分离使用出错的问题https://github.com/OpenAtomFoundation/pika/pull/2767
修复 sentinel 主从切换时,由于主节点和从节点数据量不一致导致主从切换状态扭转错误问题https://github.com/OpenAtomFoundation/pika/pull/2766
修复
Zverank
命令计算错误,导致返回值错误的问题https://github.com/OpenAtomFoundation/pika/pull/2763修复
Pksetat
命令更新 DB 的同时未更新缓存,可能会存在数据库、缓存不一致的问题https://github.com/OpenAtomFoundation/pika/pull/2759修复 Pika-port 传输数据过程中报错的问题https://github.com/OpenAtomFoundation/pika/pull/2758
修复 RsynClient 异常退出后,未做失败处理导致全量复制数据不完整的问题https://github.com/OpenAtomFoundation/pika/pull/2756
修复 Pika 不能批量扩容的问题https://github.com/OpenAtomFoundation/pika/pull/2746
修复参数未初始化导致 slotsscan 等命令不能和 bgsave 命令相互制衡的问题https://github.com/OpenAtomFoundation/pika/pull/2745
修复 SlotMigrate 返回值错误,可能会导致数据迁移中断的问题https://github.com/OpenAtomFoundation/pika/pull/2741
修复 RedisCache 层因为没有使用 storage 层解析的 ttl 可能会导致数据库、缓存不一致的问题https://github.com/OpenAtomFoundation/pika/pull/2729
修复 Stream 类型数据不能被 RocksDB compact 会导致过期数据一直存在的问题https://github.com/OpenAtomFoundation/pika/pull/2724
修复 ACL 认证可能会偶发性失败的问题https://github.com/OpenAtomFoundation/pika/pull/2714
修复 Pika cmdID 赋值在 Cmd 初始函数中,可能会导致并发构造的时候出现内存泄漏的问题https://github.com/OpenAtomFoundation/pika/pull/2692
修复 Spop 在写 binlog 时可能会出现竞态问题https://github.com/OpenAtomFoundation/pika/pull/2674
修复 server_stat 中的数据竞态问题https://github.com/OpenAtomFoundation/pika/pull/2671
修复多 DB 下全量同步超时后不重试的问题https://github.com/OpenAtomFoundation/pika/pull/2667
修复多 DB 主从超时场景下,可能会出现窗口崩溃的问题https://github.com/OpenAtomFoundation/pika/pull/2666
修复主从同步限速逻辑中重复解锁的问题https://github.com/OpenAtomFoundation/pika/pull/2657
2.3 提升改进项
优化主从复制,确保 Master 端的 SlaveNode 在提交 bgsave 任务前进入 DBSync 状态,防止 bgsave 执行时的 binlog 在极端情况下被清除https://github.com/OpenAtomFoundation/pika/pull/2798
优化 Apply binlog 时锁机制,减少不必要的锁竞争https://github.com/OpenAtomFoundation/pika/pull/2773
添加 Geo 数据类型的 TCL 测试,并修复测试过程中遇到的 bughttps://github.com/OpenAtomFoundation/pika/pull/2753
更新 Pika Docker Readme, 可以按照 Readme 在 Docker 中部署 Pika 服务https://github.com/OpenAtomFoundation/pika/pull/2743
支持对更多的 RocksDB 参数进行动态调整,用户根据不同的业务使用场景调整参数提升 Pika 的读写性能https://github.com/OpenAtomFoundation/pika/pull/2728
Pkpatternmatchdel 命令支持删除 Redis Stream 数据类型https://github.com/OpenAtomFoundation/pika/pull/2723
重构主从复制模式 slave 节点的主从同步线程模型,尽可能减少 binlog 消费阻塞问题https://github.com/OpenAtomFoundation/pika/pull/2638
增加主从复制状态指标 repl_connect_status,方便运维人员清晰明确的判断当前的主从复制状态https://github.com/OpenAtomFoundation/pika/pull/2656
新增 RocksDB Compaction 策略动态调整参数,用户可以根据业务调整 Compaction 策略,降低 Compaction 操作对服务性能的损耗https://github.com/OpenAtomFoundation/pika/pull/2538
对事务新增 TCL 测试并修复测试中遇到的 bughttps://github.com/OpenAtomFoundation/pika/pull/2844
2.4 发版 tag
https://github.com/OpenAtomFoundation/pika/releases/tag/v3.5.5
3 社区
感谢所有为 v3.5.5 做出贡献的社区成员,包括 issue/PR 提交者、代码 reviewer 【排名不分先后,依据字母顺序】:
baerwang
baixin01
bigdaronlee163
cheniujh
chejinge
haiyang426
lqxhub
luky116
Mixficsol
QlQlqiqi
saz97
VanessaXWGUO
wangshao1
XiaoLiang2333
PikiwiDB (Pika) 开源社区热烈欢迎您的参与和支持。如果您有任何问题、意见或建议,请添加 PikiwiDB 小助手【微信号: PikiwiDB】为好友,它会拉您加入官方微信群。
版权声明: 本文为 InfoQ 作者【apache/dubbo-go】的原创文章。
原文链接:【http://xie.infoq.cn/article/39cfe2abbcdffcd021f90551e】。未经作者许可,禁止转载。
评论