Redis学习笔记

76 阅读7分钟

数据结构

redis中键值对的数据类型:String(字符串)、List(列表)、Hash(哈希)、Set(集合)和 Sorted Set(有序集合)

底层数据结构一共有 6 种,分别是:简单动态字符串、双向链表、压缩列表、哈希表、跳表和整数数组。

image.png

全局hash表

首先redis是一个key-value类型数据库,redis使用了一个哈希表存储所有键值对,也称之为全局哈希表。这个全局哈希表的值类型可以是上面我们说的字符串或者集合类型。

跟我们平时使用的hashmap一样,对key做哈希操作计算hash值,当两个不同key的哈希值一样也就是发生hash冲突的时候,这个hash值对应的数据会用一个链表来存储,所以没有hash冲突的时候查询复杂度是O(1),发生hash冲突的时候则需要遍历这个hash冲突链。

rehash

当冲突越来越多,也就是冲突链变得很长的时候,redis会做一个rehash操作,也就是增加hash桶,这就需要重新计算所有key的hash值。redis使用了两个全局hash表,rehash的时候,对hash表2分配可能是2倍的空间,将原来的hash表1的数据重新计算hash映射到hash表2,然后拷贝过去,释放hash表1的空间。

如果一次性迁移过去,为了防止中间插入数据造成数据混乱,肯定是要阻塞当前redis主线程,那势必会造成性能问题,因此redis是渐进式的rehash操作,每次有请求时,再把对应数据拷贝到hash表2中,这样就把一次性拷贝工作,分摊到了每次请求中,减少了压力。


压缩列表:也是数组结构,但是表头会包含长度,列表尾的索引

跳表:跳表是链表结构,增加了多级索引,因此可以比较快的查找数据。

Redis单线程模型

Redis 的大部分操作在内存上完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。另一方面,就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率。

Redis持久化机制

Redis 的持久化主要有两大机制,即 AOF(Append Only File)日志和 RDB 快照

AOF日志

Redis执行完命令后,会把命令记录到AOF日志中,这个操作是在Redis主线程中完成。AOF配置有三个可选值:

  • Always:同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
  • Everysec:每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
  • No:操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。

当AOF文件太大怎么办?Redis还有一个AOF重写机制,可以将多个命令合并成一条命令,(对同一个key的修改,可以只保存最终的修改命令),这大大缩小了AOF文件大小。并且重写过程是由后台子进程来完成的。

内存快照RDB

和 AOF 相比,RDB 记录的是某一时刻的数据,并不是操作,所以,在做数据恢复时,我们可以直接把 RDB 文件读入内存,很快地完成恢复。 Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。

  • save:在主线程中执行,会导致阻塞;
  • bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。

bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。当快照在生成的时候,如果主线程收到修改的命令,会对这个数据生成一个副本,然后在副本上进行修改,就不会影响快照的生成。

但是快照是某一时刻的,如果redis发生宕机,那么最近一个快照生成的时刻到发生宕机这段期间的数据操作就丢失了。Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

Redis主从库模式

Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。 当我们启动多个 Redis 实例的时候,它们相互之间就可以通过 replicaof(Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步。

image.png 通过分析主从库间第一次数据同步的过程,你可以看到,一次全量复制中,对于主库来说,需要完成两个耗时的操作:生成 RDB 文件和传输 RDB 文件。

Redis采用的是“主-从-从”模式,也就是可以选择一些从库,去级联其他从库,这样master节点就不用和所有的从库通信,减轻主库的压力。那如果主从之间网络断了怎么办呢,重连是否需要重新传输rdb文件?不需要的,当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。 repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置。 因为断连后,主库只需要把自己当前写到的位置和从库当前读到位置之间的命令同步给从库就行。

哨兵机制

哨兵其实就是一个运行在特殊模式下的 Redis 进程,主从库实例运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。

哨兵实例之间可以相互发现,要归功于 Redis 提供的 pub/sub 机制,也就是发布 / 订阅机制。只有订阅了同一个频道的应用,才能通过发布的消息进行信息交换。

image.png

监控:哨兵会周期性的给主从发送PING命令,如果从库没有在规定时间内响应,就会被标记为下线状态。如果主库被标记下线,就会开启选主的流程。只有当哨兵集群中多数哨兵认为主库已下线,才会开始重新选主。

哨兵机制通常会采用多实例组成的集群模式进行部署,这也被称为哨兵集群。引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。

选主:根据一定规则筛选和打分,评分最高的作为主库,比如用网络连接状况来筛选。用从库优先级、从库复制进度以及从库 ID 号这三个规则来打分。

通知:选了master之后需要将master信息通知其它slave。

image.png

哨兵集群原理

为了避免单个哨兵故障后无法进行主从切换,以及为了减少误判率,又引入了哨兵集群;哨兵集群又需要有一些机制来支撑它的正常运行:

  • 基于 pub/sub 机制实现哨兵集群之间的通信;
  • 基于 INFO 命令获取 slave 列表,帮助 哨兵与 slave 建立连接;
  • 通过哨兵的 pub/sub,实现了与客户端和哨兵之间的事件通知。

参考

图片以及内容参考自极客时间蒋德钧的《Redis核心技术与实战》

Redis 高可用篇:你管这叫 Sentinel 哨兵集群原理