背景
Redis 作为一款简洁, 高效的开源 K/V 数据库, 可以被用于内存缓存, 持久化存储等不同场景, 大量服务于各类互联网应用. 同时也提供了丰富的功能配置, 客户可以根据各自业务需求, 在读写性能, 缓存容量, 数据可靠性等方面作出灵活的选择.
Redis 提供了 RDB 和 AOF 两种持久化方式供选择, 4.0 中更是引入了 RDB-AOF 混合持久化的方式, 整合 RDB 和 AOF 的优势, 提供更实时的数据持久化保证, 更快的恢复速度和更紧凑的空间使用. 针对 AOF 的写入, Redis 提供了两种选项供选择:
always:aoflog 实时写入落盘, 保证写入数据的安全性, 但写入性能下降严重.
everysec:buffer 写入 aoflog, 后台定期刷盘, 可以很好的保证写入性能, 但在 failure 场景下, 需要承担秒级新写入数据丢失的风险.
这两种模式需要用户在性能和数据安全性之间做出取舍, 鱼和熊掌无法兼得. 对一些对数据安全性有更高要求的场景, 需要应用层协同来保证数据安全, 会给系统设计和实现带来一定的复杂度. 另一方面, 在 Redis 发生 failover 的时候, 会有一个缓存预热重建的过程, 期间对应用会有一个可感知的不可服务时间, 以及访问延时抖动.
关于上述问题, 很重要的一个原因在于目前 DRAM 和 SSD(请忽略 HDD)之间巨大的性能鸿沟. 近几年, 备受学术界和工业界关注的 NVM(Non-volatile Memory) 技术, 给这类问题的解决带来了新的机遇.
图 1:NVM 产品的存储层次结构
目前已有的 NVM 产品, 对上层应用提供 DIMM 形态的访问接口. 作为一种 NVM 设备, 相比于 DRAM 具备掉电不丢数据的特性, 容量上也会比 DRAM 高出一个数量级, 成本优势明显. 相比于传统 SSD, 不但读写速度更快(百 ns 量级), 而且具备字节寻址的能力. 同时也要看到, NVM 产品仍然存在读写不对称, 顺序和随机访问不对称等特征.
从应用场景上来看, NVM 产品可以定位于替代部分 DRAM 功能, 支撑持久 Memory 或 In-Memory 应用. 具体来说可被应用在如下场景:
持久化内存: 作为数据持久层, 对数据一致性要求很高的持久化系统, 同时兼顾数据可靠性和数据读写性能.
内存数据库: 作为数据 In Place 空间, 提供数据运行和持久化存储空间.
系统日志卷: 作为日志卷, 例如, 在 HPC 系统中通常采用 Checkpointing 实现对计算中间状态进行持久化保存, 这是一个耗时, 耗系统吞吐量的过程.
基于 NVM 产品提供的字节寻址, 持久化, 高性能的能力, 以及考虑到其读写, 顺序和随机访问不对称等特性, 对 Redis 作了细致的设计和深度的定制化改造, 针对上面几个问题取得非常好的测试效果.
性能分析
前面提到 Redis 的 always 模式通过实时 flush 操作确保 AOF 文件的实时持久化, 但这会导致性能大幅下降. Everysec 模式通过大幅减少 flush 操作的频率, 基于 page cache 缓冲对设备的读写访问大幅提升 QPS 性能, 但是这会引入秒级的数据丢失风险. 而基于 NVM 产品提供的持久化能力可以非常优雅地解决这个问题, 兼顾性能和可靠性. 整个的数据流图如下:
图 2: 基于 NVM 产品的数据读写流程
首先将 AOF 文件直接放在基于 NVM 产品的 PMEM-aware filesystem 上(比如 EXT4 DAX 模式), 通过 mmap 将 AOF 文件映射到用户态地址空间, 之后对 AOF 的访问操作就变成了非常轻量的直接 load/store 方式, 而且要确保数据持久化也仅需要在用户态执行 persist 操作(主要是 cache flush). 可见, 基于 NVM 产品的 AOF 持久化机制相比传统的 IO 栈要轻量的多:
B Bypass 整个传统 IO 栈 (Block 层 -> 设备驱动等), 实现直接 load/store 操作.
通过 cache flush 操作即可实现持久化, 取代了 flush 系统调用.
AOF 机制的另一个问题是 AOF 文件的持续增大会造成巨大的空间浪费, 所以阿里云 Redis 团队通过后台线程的方式按照一定的策略 (考虑吞吐和资源占用量等) 对 AOF 文件进行 replay 操作. 即根据 AOF 命令在 NVM 产品上构造持久化的 KV 数据结构, 然后完成回放的 AOF 文件就可以删除, 从而解决了 AOF 占用空间的问题.
之所以选择后台线程异步 replay 的方式在 NVM 上构建持久化数据结构, 是因为该过程需要通过事务操作来保证写操作的原子性和数据结构的一致性, 耗时较大, 所以数据先写到 DDR 的方式可以保证客户端写操作的 QPS 不下降. 考虑到 NVM 拥有出色的读性能, 数据异步 replay 到 NVM 之后, 会根据数据冷热和内存占用量释放部分 value 比较大的内存副本, 转而直接基于 NVM 提供读服务. 所以 DRAM 逐渐演变为 NVM 的写 cache 和热数据的读 cache, 以充分发挥 NVM 的读性能和成本优势, 同时规避 NVM 写性能 (相对) 的短板问题.
在两台 96 核 / 384GB 神龙服务器上, 实测 string 数据结构的 SET 操作, 从结果数据来看几乎与 everysec 模式的性能持平, 同时兼顾了 always 模式的数据安全性和 everysec 模式的高性能.
图 3:Redis 写入性能对比
Redis 在重启时需要进行数据恢复操作. 原生 Redis 重启后都需要从 RDB 和 AOF 中加载数据到内存, 完成加载后才可以正常提供服务. 经过实测, 10GB 左右数据的 RDB 加载时间大概为 53 秒左右, 这段时间内 Redis 服务处于不可用状态. 而在基于 NVM 的方案中, Redis 重启后可以先基于 NVM 的持久化数据结构直接提供读服务, DRAM 数据结构重建完成后即可提供完整的读写服务. 同样针对 10GB 左右的数据集, 系统 shutdown save 后重启, 实测 1 秒内可以提供只读服务, 35 秒内可以提供完整读写服务, 整个数据恢复时间大幅下降. 如下图所示:
图 4: Redis recovery 时间对比
另外, 原生 Redis 通过 fork 一个子进程来保存全量 DB 数据到 RDB 或 AOF 文件, 即使相比上次保存的数据仅有一个 key 的变更, 也依然会全量保存整个 DB, 显然这不是一种高效的实现方式. 并且该过程会对 Redis 带来较大的性能抖动. 而基于 NVM 的方案是一种持续增量持久化的方式, 更加高效和平滑, 这点对于在线服务来说至关重要.
NVM 相比 DRAM 能提供更高的存储密度和更大容量的数据存储空间, 因此可以有效降低单位数据存储成本. 基于 NVM 的字节寻址能力和与 DRAM 的速度差异, 我们设计了 NVM 非易失性内存和 DRAM 易失性内存间的数据换入与换出策略, 能在不影响 Redis 基本性能的前提下, 提高数据的存储容量并节约成本.
结束语
整个方案中, 充分发挥了 NVM 的字节寻址, 持久化等能力, 借助 DRAM 做 cache 来弥补 NVM 读写不对称的问题, 从而实现了高可靠, 高性能, 低成本的 Redis 数据库, 从测试数据可以看出 NVM 对 Redis 在持久化以及其他性能方面提升效果非常显著.
除此之外, NVM 相比 DRAM 能提供更高的存储密度和更大容量的数据存储空间, 因此可以有效降低单位数据存储成本. 在不影响 Redis 基本读写性能的前提下, 基于 NVM 和 DRAM 的动态数据输入换出, 是我们下一步工作的方向之一.
当然, 目前方案仍存在一些待优化的点, 比如: 对于高写入场景, replay 性能跟不上 DRAM 写入速度; failover 时候, 需要等 DRAM 重建完成才能提供写服务等. 后续会继续跟进这些问题, 结合技术和产品来一起合理改进.
来源: http://www.jianshu.com/p/7cc339222234