gitbook/Redis核心技术与实战/docs/298205.md
2022-09-03 22:05:03 +08:00

19 KiB
Raw Permalink Blame History

28 | Pika如何基于SSD实现大容量Redis

你好,我是蒋德钧。

我们在应用Redis时随着业务数据的增加比如说电商业务中随着用户规模和商品数量的增加就需要Redis能保存更多的数据。你可能会想到使用Redis切片集群把数据分散保存到多个实例上。但是这样做的话会有一个问题如果要保存的数据总量很大但是每个实例保存的数据量较小的话就会导致集群的实例规模增加这会让集群的运维管理变得复杂增加开销。

你可能又会说我们可以通过增加Redis单实例的内存容量形成大内存实例每个实例可以保存更多的数据这样一来在保存相同的数据总量时所需要的大内存实例的个数就会减少就可以节省开销。

这是一个好主意,但这也并不是完美的方案:基于大内存的大容量实例在实例恢复、主从同步过程中会引起一系列潜在问题,例如恢复时间增长、主从切换开销大、缓冲区易溢出。

那怎么办呢我推荐你使用固态硬盘Solid State DriveSSD。它的成本很低每GB的成本约是内存的十分之一而且容量大读写速度快我们可以基于SSD来实现大容量的Redis实例。360公司DBA和基础架构组联合开发的Pika键值数据库,正好实现了这一需求。

Pika在刚开始设计的时候就有两个目标一是单实例可以保存大容量数据同时避免了实例恢复和主从同步时的潜在问题二是和Redis数据类型保持兼容可以支持使用Redis的应用平滑地迁移到Pika上。所以如果你一直在使用Redis并且想使用SSD来扩展单实例容量Pika就是一个很好的选择。

这节课我就和你聊聊Pika。在介绍Pika前我先给你具体解释下基于大内存实现大容量Redis实例的潜在问题。只有知道了这些问题我们才能选择更合适的方案。另外呢我还会带你一步步分析下Pika是如何实现刚刚我们所说的两个设计目标解决这些问题的。

大内存Redis实例的潜在问题

Redis使用内存保存数据内存容量增加后就会带来两方面的潜在问题分别是内存快照RDB生成和恢复效率低以及主从节点全量同步时长增加、缓冲区易溢出。我来一一解释下

我们先看内存快照RDB受到的影响。内存大小和内存快照RDB的关系是非常直接的实例内存容量大RDB文件也会相应增大那么RDB文件生成时的fork时长就会增加这就会导致Redis实例阻塞。而且RDB文件增大后使用RDB进行恢复的时长也会增加会导致Redis较长时间无法对外提供服务。

接下来我们再来看下主从同步受到的影响,

主从节点间的同步的第一步就是要做全量同步。全量同步是主节点生成RDB文件并传给从节点从节点再进行加载。试想一下如果RDB文件很大肯定会导致全量同步的时长增加效率不高而且还可能会导致复制缓冲区溢出。一旦缓冲区溢出了主从节点间就会又开始全量同步影响业务应用的正常使用。如果我们增加复制缓冲区的容量这又会消耗宝贵的内存资源。

此外如果主库发生了故障进行主从切换后其他从库都需要和新主库进行一次全量同步。如果RDB文件很大也会导致主从切换的过程耗时增加同样会影响业务的可用性。

那么Pika是如何解决这两方面的问题呢这就要提到Pika中的关键模块RocksDB、binlog机制和Nemo了这些模块都是Pika架构中的重要组成部分。所以接下来我们就来先看下Pika的整体架构。

Pika的整体架构

Pika键值数据库的整体架构中包括了五部分分别是网络框架、Pika线程模块、Nemo存储模块、RocksDB和binlog机制如下图所示

这五个部分分别实现了不同的功能,下面我一个个来介绍下。

首先网络框架主要负责底层网络请求的接收和发送。Pika的网络框架是对操作系统底层的网络函数进行了封装。Pika在进行网络通信时可以直接调用网络框架封装好的函数。

其次Pika线程模块采用了多线程模型来具体处理客户端请求包括一个请求分发线程DispatchThread、一组工作线程WorkerThread以及一个线程池ThreadPool

请求分发线程专门监听网络端口一旦接收到客户端的连接请求后就和客户端建立连接并把连接交由工作线程处理。工作线程负责接收客户端连接上发送的具体命令请求并把命令请求封装成Task再交给线程池中的线程由这些线程进行实际的数据存取处理如下图所示

在实际应用Pika的时候我们可以通过增加工作线程数和线程池中的线程数来提升Pika的请求处理吞吐率进而满足业务层对数据处理性能的需求。

Nemo模块很容易理解它实现了Pika和Redis的数据类型兼容。这样一来当我们把Redis服务迁移到Pika时不用修改业务应用中操作Redis的代码而且还可以继续应用运维Redis的经验这使得Pika的学习成本就较低。Nemo模块对数据类型的具体转换机制是我们要重点关心的下面我会具体介绍。

最后我们再来看看RocksDB提供的基于SSD保存数据的功能。它使得Pika可以不用大容量的内存就能保存更多数据还避免了使用内存快照。而且Pika使用binlog机制记录写命令用于主从节点的命令同步避免了刚刚所说的大内存实例在主从同步过程中的潜在问题。

接下来我们就来具体了解下Pika是如何使用RocksDB和binlog机制的。

Pika如何基于SSD保存更多数据

为了把数据保存到SSDPika使用了业界广泛应用的持久化键值数据库RocksDB。RocksDB本身的实现机制较为复杂你不需要全部弄明白你只要记住RocksDB的基本数据读写机制对于学习了解Pika来说就已经足够了。下面我来解释下这个基本读写机制。

下面我结合一张图片来给你具体介绍下RocksDB写入数据的基本流程。

当Pika需要保存数据时RocksDB会使用两小块内存空间Memtable1和Memtable2来交替缓存写入的数据。Memtable的大小可以设置一个Memtable的大小一般为几MB或几十MB。当有数据要写入RocksDB时RocksDB会先把数据写入到Memtable1。等到Memtable1写满后RocksDB再把数据以文件的形式快速写入底层的SSD。同时RocksDB会使用Memtable2来代替Memtable1缓存新写入的数据。等到Memtable1的数据都写入SSD了RocksDB会在Memtable2写满后再用Memtable1缓存新写入的数据。

这么一分析你就知道了RocksDB会先用Memtable缓存数据再将数据快速写入SSD即使数据量再大所有数据也都能保存到SSD中。而且Memtable本身容量不大即使RocksDB使用了两个Memtable也不会占用过多的内存这样一来Pika在保存大容量数据时也不用占据太大的内存空间了。

当Pika需要读取数据的时候RocksDB会先在Memtable中查询是否有要读取的数据。这是因为最新的数据都是先写入到Memtable中的。如果Memtable中没有要读取的数据RocksDB会再查询保存在SSD上的数据文件如下图所示

到这里你就了解了当使用了RocksDB保存数据后Pika就可以把大量数据保存到大容量的SSD上了实现了大容量实例。不过我刚才向你介绍过当使用大内存实例保存大量数据时Redis会面临RDB生成和恢复的效率问题以及主从同步时的效率和缓冲区溢出问题。那么当Pika保存大量数据时还会面临相同的问题吗

其实不会了,我们来分析一下。

一方面Pika基于RocksDB保存了数据文件直接读取数据文件就能恢复不需要再通过内存快照进行恢复了。而且Pika从库在进行全量同步时可以直接从主库拷贝数据文件不需要使用内存快照这样一来Pika就避免了大内存快照生成效率低的问题。

另一方面Pika使用了binlog机制实现增量命令同步既节省了内存还避免了缓冲区溢出的问题。binlog是保存在SSD上的文件Pika接收到写命令后在把数据写入Memtable时也会把命令操作写到binlog文件中。和Redis类似当全量同步结束后从库会从binlog中把尚未同步的命令读取过来这样就可以和主库的数据保持一致。当进行增量同步时从库也是把自己已经复制的偏移量发给主库主库把尚未同步的命令发给从库来保持主从库的数据一致。

不过和Redis使用缓冲区相比使用binlog好处是非常明显的binlog是保存在SSD上的文件文件大小不像缓冲区会受到内存容量的较多限制。而且当binlog文件增大后还可以通过轮替操作生成新的binlog文件再把旧的binlog文件独立保存。这样一来即使Pika实例保存了大量的数据在同步过程中也不会出现缓冲区溢出的问题了。

现在我们先简单小结下。Pika使用RocksDB把大量数据保存到了SSD同时避免了内存快照的生成和恢复问题。而且Pika使用binlog机制进行主从同步避免大内存时的影响Pika的第一个设计目标就实现了。

接下来我们再来看Pika是如何实现第二个设计目标的也就是如何和Redis兼容。毕竟如果不兼容的话原来使用Redis的业务就无法平滑迁移到Pika上使用了也就没办法利用Pika保存大容量数据的优势了。

Pika如何实现Redis数据类型兼容

Pika的底层存储使用了RocksDB来保存数据但是RocksDB只提供了单值的键值对类型RocksDB键值对中的值就是单个值而Redis键值对中的值还可以是集合类型。

对于Redis的String类型来说它本身就是单值的键值对我们直接用RocksDB保存就行。但是对于集合类型来说我们就无法直接把集合保存为单值的键值对而是需要进行转换操作。

为了保持和Redis的兼容性Pika的Nemo模块就负责把Redis的集合类型转换成单值的键值对。简单来说我们可以把Redis的集合类型分成两类

  • 一类是List和Set类型它们的集合中也只有单值
  • 另一类是Hash和Sorted Set类型它们的集合中的元素是成对的其中Hash集合元素是field-value类型而Sorted Set集合元素是member-score类型。

Nemo模块通过转换操作把这4种集合类型的元素表示为单值的键值对。具体怎么转换呢下面我们来分别看下每种类型的转换。

首先我们来看List类型。在Pika中List集合的key被嵌入到了单值键值对的键当中用key字段表示而List集合的元素值则被嵌入到单值键值对的值当中用value字段表示。因为List集合中的元素是有序的所以Nemo模块还在单值键值对的key后面增加了sequence字段表示当前元素在List中的顺序同时还在value的前面增加了previous sequence和next sequence这两个字段分别表示当前元素的前一个元素和后一个元素。

此外在单值键值对的key前面Nemo模块还增加了一个值“l”表示当前数据是List类型以及增加了一个1字节的size字段表示List集合key的大小。在单值键值对的value后面Nemo模块还增加了version和ttl字段分别表示当前数据的版本号和剩余存活时间用来支持过期key功能如下图所示

我们再来看看Set集合。

Set集合的key和元素member值都被嵌入到了Pika单值键值对的键当中分别用key和member字段表示。同时和List集合类似单值键值对的key前面有值“s”用来表示数据是Set类型同时还有size字段用来表示key的大小。Pika单值键值对的值只保存了数据的版本信息和剩余存活时间如下图所示

对于Hash类型来说Hash集合的key被嵌入到单值键值对的键当中用key字段表示而Hash集合元素的field也被嵌入到单值键值对的键当中紧接着key字段用field字段表示。Hash集合元素的value则是嵌入到单值键值对的值当中并且也带有版本信息和剩余存活时间如下图所示

最后对于Sorted Set类型来说该类型是需要能够按照集合元素的score值排序的而RocksDB只支持按照单值键值对的键来排序。所以Nemo模块在转换数据时就把Sorted Set集合key、元素的score和member值都嵌入到了单值键值对的键当中此时单值键值对中的值只保存了数据的版本信息和剩余存活时间如下图所示

采用了上面的转换方式之后Pika不仅能兼容支持Redis的数据类型而且还保留了这些数据类型的特征例如List的元素保序、Sorted Set的元素按score排序。了解了Pika的转换机制后你就会明白如果你有业务应用计划从使用Redis切换到使用Pika就不用担心面临因为操作接口不兼容而要修改业务应用的问题了。

经过刚刚的分析我们可以知道Pika能够基于SSD保存大容量数据而且和Redis兼容这是它的两个优势。接下来我们再来看看跟Redis相比Pika的其他优势以及潜在的不足。当在实际应用Pika时Pika的不足之处是你需要特别注意的地方这些可能都需要你进行系统配置或参数上的调优。

Pika的其他优势与不足

跟Redis相比Pika最大的特点就是使用了SSD来保存数据这个特点能带来的最直接好处就是Pika单实例能保存更多的数据了实现了实例数据扩容。

除此之外Pika使用SSD来保存数据还有额外的两个优势。

首先,实例重启快。Pika的数据在写入数据库时是会保存到SSD上的。当Pika实例重启时可以直接从SSD上的数据文件中读取数据不需要像Redis一样从RDB文件全部重新加载数据或是从AOF文件中全部回放操作这极大地提高了Pika实例的重启速度可以快速处理业务应用请求。

另外主从库重新执行全量同步的风险低。Pika通过binlog机制实现写命令的增量同步不再受内存缓冲区大小的限制所以即使在数据量很大导致主从库同步耗时很长的情况下Pika也不用担心缓冲区溢出而触发的主从库重新全量同步。

但是就像我在前面的课程中和你说的“硬币都是有正反两面的”Pika也有自身的一些不足。

虽然它保持了Redis操作接口也能实现数据库扩容但是当把数据保存到SSD上后会降低数据的访问性能。这是因为数据操作毕竟不能在内存中直接执行了而是要在底层的SSD中进行存取这肯定会影响Pika的性能。而且我们还需要把binlog机制记录的写命令同步到SSD上这会降低Pika的写性能。

不过Pika的多线程模型可以同时使用多个线程进行数据读写这在一定程度上弥补了从SSD存取数据造成的性能损失。当然你也可以使用高配的SSD来提升访问性能进而减少读写SSD对Pika性能的影响。

为了帮助你更直观地了解Pika的性能情况我再给你提供一张表这是Pika官网上提供的测试数据。

这些数据是在Pika 3.2版本中String和Hash类型在多线程情况下的基本操作性能结果。从表中可以看到在不写binlog时Pika的SET/GET、HSET/HGET的性能都能达到200K OPS以上而一旦增加了写binlog操作SET和HSET操作性能大约下降了41%只有约120K OPS。

所以我们在使用Pika时需要在单实例扩容的必要性和可能的性能损失间做个权衡。如果保存大容量数据是我们的首要需求那么Pika是一个不错的解决方案。

小结

这节课我们学习了基于SSD给Redis单实例进行扩容的技术方案Pika。跟Redis相比Pika的好处非常明显既支持Redis操作接口又能支持保存大容量的数据。如果你原来就在应用Redis现在想进行扩容那么Pika无疑是一个很好的选择无论是代码迁移还是运维管理Pika基本不需要额外的工作量。

不过Pika毕竟是把数据保存到了SSD上数据访问要读写SSD所以读写性能要弱于Redis。针对这一点我给你提供两个降低读写SSD对Pika的性能影响的小建议

  1. 利用Pika的多线程模型增加线程数量提升Pika的并发请求处理能力
  2. 为Pika配置高配的SSD提升SSD自身的访问性能。

最后我想再给你一个小提示。Pika本身提供了很多工具可以帮助我们把Redis数据迁移到Pika或者是把Redis请求转发给Pika。比如说我们使用aof_to_pika命令并且指定Redis的AOF文件以及Pika的连接信息就可以把Redis数据迁移到Pika中了如下所示

aof_to_pika -i [Redis AOF文件] -h [Pika IP] -p [Pika port] -a [认证信息]

关于这些工具的信息你都可以直接在Pika的GitHub上找到。而且Pika本身也还在迭代开发中我也建议你多去看看GitHub进一步地了解它。这样你就可以获得Pika的最新进展也能更好地把它应用到你的业务实践中。

每课一问

按照惯例我给你提个小问题。这节课我向你介绍的是使用SSD作为内存容量的扩展增加Redis实例的数据保存量我想请你来聊一聊我们可以使用机械硬盘来作为实例容量扩展吗有什么好处或不足吗

欢迎在留言区写下你的思考和答案,我们一起交流讨论。如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见。