notebook notebook
首页
  • 计算机网络
  • 计算机系统
  • 数据结构与算法
  • 计算机专业课
  • 设计模式
  • 前端 (opens new window)
  • Java 开发
  • Python 开发
  • Golang 开发
  • Git
  • 软件设计与架构
  • 大数据与分布式系统
  • 常见开发工具

    • Nginx
  • 爬虫
  • Python 数据分析
  • 数据仓库
  • 中间件

    • MySQL
    • Redis
    • Elasticsearch
    • Kafka
  • 深度学习
  • 机器学习
  • 知识图谱
  • 图神经网络
  • 应用安全
  • 渗透测试
  • Linux
  • 云原生
面试
  • 收藏
  • paper 好句
GitHub (opens new window)

学习笔记

啦啦啦,向太阳~
首页
  • 计算机网络
  • 计算机系统
  • 数据结构与算法
  • 计算机专业课
  • 设计模式
  • 前端 (opens new window)
  • Java 开发
  • Python 开发
  • Golang 开发
  • Git
  • 软件设计与架构
  • 大数据与分布式系统
  • 常见开发工具

    • Nginx
  • 爬虫
  • Python 数据分析
  • 数据仓库
  • 中间件

    • MySQL
    • Redis
    • Elasticsearch
    • Kafka
  • 深度学习
  • 机器学习
  • 知识图谱
  • 图神经网络
  • 应用安全
  • 渗透测试
  • Linux
  • 云原生
面试
  • 收藏
  • paper 好句
GitHub (opens new window)
  • 爬虫

  • Python 数据分析

  • MySQL

  • Redis

    • 专栏:Redis 核心技术与实战

      • 基础架构、数据结构与 IO 模型
      • 持久化机制:AOF 日志和 RDB 快照
      • 主从复制与哨兵机制
      • 切片集群
      • Redis 中的数据结构
      • 时间序列数据的保存
      • Redis 的消息队列方案
      • 异步机制、CPU 架构对性能的影响
      • 如何应对变慢的 Redis
      • Redis 的内存碎片、缓冲区
      • Redis 用作缓存
      • Pika:基于SSD实现大容量Redis
        • 1 Pika:如何基于 SSD 实现大容量 Redis?
          • 1.1 大内存 Redis 实例的潜在问题
          • 1.2 Pika 的整体架构
          • 1.3 Pika 如何基于 SSD 保存更多数据?
          • 1.4 Pika 如何实现 Redis 数据类型兼容?
          • 1.5 Pika 的其他优势与不足
          • 1.6 小结
      • 无锁的原子操作和分布式锁
      • Redis 的事务机制
      • Redis 主从同步的坑
      • 秒杀场景下的应用
      • 集群的数据倾斜和通信开销问题
  • Elasticsearch

  • Kafka

  • 数据仓库

  • 数据科学
  • Redis
  • 专栏:Redis 核心技术与实战
yubin
2023-03-26
目录

Pika:基于SSD实现大容量Redis

参考:

  • 23 Pika:如何基于 SSD 实现大容量 Redis?| 极客时间 (opens new window)

# 1 Pika:如何基于 SSD 实现大容量 Redis?

在应用 Redis 时,随着业务数据的增加,会需要 Redis 保存更多的数据,这时会想到使用 Redis 切片集群,从而把数据分散保存到多个实例上,但这样会导致集群的实例规模增加,让集群的运维变得复杂。

还有一种方法是增加 Redis 单个实例的内存容量,从而减少所需的 Redis 实例的个数。这是个好主意,但并不完美:基于大内存的大容量实例在实例恢复、主从同步过程中会引起一系列潜在问题,例如恢复时间增长、主从切换开销大、缓冲区易溢出。

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

Pika 在刚开始设计时有两个目标:

  1. 单实例可以保存大容量数据,同时避免了实例恢复和主从同步时的潜在问题;
  2. 和 Redis 数据类型保持兼容,可以支持使用 Redis 的应用平滑地迁移到 Pika 上。

所以,如果你一直在使用 Redis,并且想使用 SSD 来扩展单实例容量,Pika 就是一个很好的选择。

本节将先介绍基于大内存实现大容量 Redis 实例的潜在问题,然后介绍 Pika 的相关设计。

# 1.1 大内存 Redis 实例的潜在问题

Redis 使用内存保存数据,内存容量增加后,就会带来两方面的潜在问题:

  • 内存快照 RDB 生成和恢复效率低:这会导致 Redis 实例阻塞过久;
  • 主从节点全量同步时长增加:如果 RDB 文件很大,会导致全量同步的时长增加,效率不高,而且还可能会导致复制缓冲区溢出。一旦缓冲区溢出了,主从节点间就会又开始全量同步,影响业务应用的正常使用。如果我们增加复制缓冲区的容量,这又会消耗宝贵的内存资源。

那么,Pika 是如何解决这两方面的问题呢?这就要提到 Pika 中的关键模块 RocksDB、binlog 机制和 Nemo 了,这些模块都是 Pika 架构中的重要组成部分。

# 1.2 Pika 的整体架构

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

20230327140841

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

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

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

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

20230327141201

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

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

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

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

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

# 1.3.1 使用 RocksDB 来存储数据

为了把数据保存到 SSD,Pika 使用了持久化 KV 数据库 RocksDB (opens new window)。这里只需要了解 RocksDB 的基本数据读写机制就可以:

20230327142246
  • Memtable 是一个小内存空间,大约几MB或几十MB。两个 Memtable 来交替缓存写入数据。

根据上图可以看到,RocksDB 会先用 Memtable 缓存数据,再将数据快速写入SSD,即使数据量再大,所有数据也都能保存到SSD中。而且,Memtable本身容量不大,即使RocksDB使用了两个Memtable,也不会占用过多的内存,这样一来,Pika在保存大容量数据时,也不用占据太大的内存空间了。

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

20230327142438

到这里,你就了解了,当使用了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 保存大容量数据的优势了。

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

Pika 的底层存储使用了 RocksDB 来保存数据,但是,RocksDB 只提供了单值的键值对类型,而 Redis 键值对中的值却可以是集合类型。这样,对于集合类型就无法直接存储在 RocksDB 中,需要 Pika 的 Nemo 模块来完成将 Redis 集合类型 -> 单值 kv 的转换。

简单来说,可以将 Redis 的集合类型分成两类:

  • 一类是 List 和 Set 类型,它们的集合中每个元素都是单值;
  • 另一类是 Hash 和 Sorted Set 类型,它们的集合中的元素都是 pair,其中,Hash 集合元素是 field-value 类型,而 Sorted Set 集合元素是 member-score 类型。

下面看一下 Nemo 模块是如何做的这种转换。

# 1.4.1 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功能),如下图所示:

20230327154003

# 1.4.2 Set 类型

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

20230327154123

# 1.4.3 Hash 类型

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

20230327160236

# 1.4.4 Sorted Set 类型

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

20230327161633

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


前面分析了 Pika 的两大优势:

  • 基于 SSD 保存大量数据
  • 与 Redis 兼容

接下来看一下 Pika 的其他优势和潜在不足。

# 1.5 Pika 的其他优势与不足

跟Redis相比,Pika最大的特点就是使用了SSD来保存数据,这个特点能带来的最直接好处就是,Pika单实例能保存更多的数据了,实现了实例数据扩容。除此之外,Pika 还有两个额外优势:

  • 实例重启快。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 官网 (opens new window) 上提供的测试数据:

20230327162634

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

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

# 1.6 小结

这节课,我们学习了基于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 [认证信息]
1

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

编辑 (opens new window)
上次更新: 2023/04/26, 13:34:07
Redis 用作缓存
无锁的原子操作和分布式锁

← Redis 用作缓存 无锁的原子操作和分布式锁→

最近更新
01
Deep Reinforcement Learning
10-03
02
误删数据后怎么办
04-06
03
MySQL 一主多从
03-22
更多文章>
Theme by Vdoing | Copyright © 2021-2024 yubincloud | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×