Redis 你学废了吗?

222 阅读10分钟

我正在参加「掘金·启航计划」

最近,为了能够更系统地学习 Redis,一直在看极客时间上面的《Redis 核心技术与实战》专栏。

专栏里面的内容不光老师讲的清晰易懂,Kaito 大佬在每篇文章评论区里面的内容也相当的硬核有料,算是对专栏内容的扩展和补强。(感兴趣的小伙伴也可以关注一下大佬的公众号:水滴与银弹,文章不仅内容逻辑清晰有条理,而且排版也很棒!)

用了大概一周时间,已经把专栏第一部分基础篇的内容看完了,作者主要从 Redis 的基础架构、数据结构、高性能 IO 模型、持久化、数据同步、哨兵机制、切片集群等多个层面进行了讲解。

为了更好地掌握 Redis,我自己也通过脑图的方式对基础篇的内容进行了梳理,在这里分享给大家,感兴趣的小伙伴可以收藏保存。

Redis.png

数据结构

对于数据结构部分,作者主要从三个方面进行了介绍,其中包括 Redis 值的数据结构、底层数据结构,以及 Redis 是如何保存所有键值对的。

值的数据结构

对于值的数据结构,主要分为两种,一种是简单动态字符串(String),另外一种是集合类型,其中包括 List、Hash、Set、Sorted Set。

底层数据结构

对于底层数据结构来说,Redis 为了提升性能,主要采用了双向链表、压缩列表、哈希表、跳表以及整数数组五种,相应数据结构的时间复杂度大家也可以在脑图中查看。

哈希表存储键值对

那 Redis 又是如何保存所有键值对的呢?

其实是采用哈希表的方式。

对于哈希表,其实就类似一个数组,每个元素是一个哈希桶,哈希桶中保存的是指向具体值的指针。

而在使用哈希表进行存储的时候,又会出现哈希冲突的问题,所以 Redis 采用 rehash渐进式 rehash 的方式进行了处理。这两种方式在这里就不展开介绍了,感兴趣的小伙伴可以去了解一下。

高性能IO模型

对于高性能 IO 模型,作者主要从 Redis 是不是单线程、Redis 单线程的设计机制以及多路复用机制这三部分进行了讨论。

单线程设计机制

首先,Redis 对于网络 IO键值对读写都是采用单线程的,而对于持久化、异步删除、集群数据同步等功能是由额外的线程执行的。

至于单线程 Redis 为什么快,主要有三个原因:

  1. 大部分操作是在内存上执行的;
  2. 使用了高效的数据结构,例如哈希表跳表
  3. 采用了多路复用机制,可以在网络 IO 操作中能够并发处理大量的客户端请求;

多路复用机制

而对于多路复用机制,主要指的是 select/epoll 机制,这个机制允许内核中同时存在多个监听套接字和已连接套接字,内核会一直监听套接字上的连接请求和数据请求。

此外,为了能在请求到达时通知 Redis 线程,select/epoll 机制提供了基于事件的回调机制,对于不同事件的发生,调用相应的处理函数。对于操作系统以及 IO 感兴趣的小伙伴,可以重点学习一下这部分内容。

持久化

对于持久化,Redis 主要提供了两种持久化方式,一种是 AOF(Append Only File)日志,另外一种是 RDB(Redis Database)内存快照

AOF

首先,我们来说说 AOF。

为了实现 Redis 数据持久化以及防止服务器宕机造成的数据丢失,Redis 会将写操作命令以写后日志的方式记录在磁盘上。

写后日志指的就是执行完一条命令,往日志中记录一条。这样做的好处有两点:

  1. 能够避免额外的检查开销,不会将错误命令记录到日志中;

  2. 不会阻塞当前的写操作; 但同样也存在一些风险:

  3. 宕机会导致相关数据的丢失;

  4. 阻塞下一个操作; 因此,为了避免上述风险,控制 AOF 日志写回磁盘的时机,Redis 采用了三种写回策略,我们可以通过 AOF 配置项 appendfsync 进行配置:

  • Always(同步写回):执行一次操作,就立马写回磁盘;
  • Everysec(每秒写回):每隔一秒把 AOF 文件内存缓冲区中的内容写回磁盘;
  • No(操作系统控制写回):由操作系统决定何时将缓冲区内容写回磁盘; 虽然我们控制了写回磁盘的时机,但是随着接收写命令越来越多,AOF 文件越来越大也会带来性能问题。因此,就有了 AOF 的重写机制

重写机制实际上就是“多变一”的过程。将旧日志中的多条命令,通过重写,优化成新日志中的一条命令。而且 Redis 也利用了 fork 函数以及 Copy-On-Write 机制避免了 AOF 重写的阻塞。

RDB

其次,我们再来说说 RDB。

RDB 内存快照的方式有点类似于拍照片,把某一个时刻内存中的数据以文件的形式写到磁盘上。与 AOF 相比,在做数据恢复的时候,我们可以直接把 RDB 文件读入内存,很快地完成恢复。

Redis 主要是通过 savebgsave 这两个命令生成 RDB文件的,区别在于 bgsave 不会阻塞线程,而是创建一个子进程,专门用于写入 RDB 文件。

Redis 4.0 开始混合使用 AOF 日志和内存快照

简单来说就是,以一定频率执行内存快照,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

AOF 和 RDB 该如何选择

那对于 AOF 日志和 RDB 内存快照这两种方式,我们又该如何选择呢?

以下有三点建议:

  1. 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;
  2. 如果允许分钟级别的数据丢失,可以只使用RDB;
  3. 如果只用 AOF,优先使用 Everysec 配置选项,因为它在可靠性和性能之间取了一个平衡;

数据同步

Redis 除了使用数据持久化 AOF 和 RDB 这两种方式保证高可靠性外,为了保证服务尽量少中断,还采用了主从库的模式,支持读写分离以及主从库之间的数据同步

主从同步

对于主从同步,主要可以分为三个阶段:

  1. 主从库之间建立连接,协商同步;
  2. 主库执行 bgsave 命令,生成 RDB 文件,发送给从库;
  3. 主库将 replication buffer 中的新写命令发送给从库;

主从级联模式

而为了分担全量复制时主库压力,Redis 也支持主从级联模式。

主从库网络断开怎么办

那么,你可能会问,如果主从库之间的网络断开了怎么办,连接恢复后该如何同步数据?

Redis 2.8 开始支持了主从库之间的增量复制,主要依赖于 repl_backlog_buffer 环形缓冲区和 replication_buffer 进行实现。这部分比较复杂,在这篇文章里就不展开讲了。

哨兵机制和哨兵集群

为了能更好的监控主库和从库的状态,提升高可靠性,Redis 还推出了哨兵机制。(感觉有点类似于监工哈哈)

哨兵机制主要分为三部分,其中包括监控选主以及通知

监控

哨兵会通过周期性 PING 的方式来判断主从库的在线状态,如果响应超时,就会被哨兵判定为“主观下线”。

那 Redis 又是如何解决哨兵误判主库下线问题的呢?

那么这里就引出了一个概念,哨兵集群。Redis 使用多个哨兵一起监控主库状态,一旦多数哨兵判断主库为下线状态,那这个主库就会被判定为“客观下线”。

选主

那主库被判定为“客观下线”后,又是怎样选择新主库的呢?

首先,Redis 会从是否在线以及之前的网络状态情况两个方面对从库进行筛选。

筛选之后就会对从库进行打分,打分主要有三个原则:

  1. 优先级最高的从库得分高;
  2. 和旧主库同步程度最接近的从库得分高;
  3. 从库ID号小的得分高; 那选择好了新的主库,由哪个哨兵来执行主从切换呢?这里就需要讲一下 Leader 选举的过程了。

哨兵在判定主库为“主观下线”状态后,就会给其他哨兵发送投票请求,告诉其他哨兵希望由自己来执行主从切换,并让所有哨兵进行投票。在投票过程中,任何一个想成为 Leader 的哨兵,都需要满足以下两个条件:

  1. 拿到半数以上的赞成票;
  2. 拿到的票数同时还要大于等于哨兵配置文件中的 quorum 值;

通知

切换好新主库之后,哨兵就需要通知客户端新主库的相关信息了。

这里主要基于 Redis 的 pub/sub 消息订阅机制。客户端可以通过哨兵提供的消息订阅频道获取一些事件的通知,包括主库下线事件从库重新配置事件新主库切换事件等。

切片集群

讲完了 Redis 的高可靠性之后,Redis 又是怎样实现高可扩展性的呢?

为了能够保存更多的数据,实现横向扩展,官方从 3.0 开始提供了 Redis Cluster 的方案,用于实现切片集群

哈希槽

切片集群使用哈希槽来处理数据和实例之间的映射关系。一个切片集群共有 16384 个哈希槽,在创建集群的时候,Redis 会自动将这些哈希槽平均分配到集群实例上。我们也可以手动调整每个实例上的哈希槽个数,从而解决不同实例配置差异造成的影响。

如何定位数据

那将数据存储在多个实例上,客户端又是如何定位数据的呢?

首先,客户端和集群实例建立连接之后,实例会将哈希槽的分配信息发给客户端。客户端会在本地缓存中缓存哈希槽的信息。

但是,在集群中,实例和哈希槽的对应关系并不是一成不变的,比如,实例有新增或删除

对于这种情况,Redis 就需要重新分配哈希槽了。

那么,为了解决数据定位的问题,Redis Cluster 提供了一种重定向机制,也就是说如果客户端给一个实例发送数据读写操作时,这个实例上并没有相应的数据,那么客户端就需要再给一个新实例发送操作命令。

总结

以上就是基础篇的全部内容了,有些地方可能讲的不够细或是有问题的地方欢迎大家指正。也希望大家能够通过这篇文章,对 Redis 有一个整体的了解,从这篇文章开始,更加深入的学习 Redis。