Redis设计与实现笔记

持久化

Redis 为什么要持久化?

当你重启系统或者关闭系统后,缓存在内存中的数据都会消失殆尽,再也找不回来了。所以,为了让数据能够长期保存,就要将 Redis 放在缓存中的数据做持久化存储。

RDB

AOF

AOF持久化功能实现分为命令追加,文件写入,文件同步三个步骤

1、命令追加

服务器在执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾。

Redis复制

用户通过slaveof命令,让一个服务器去复制另一个服务器,我们称被复制的服务器为主服务器,对主服务器进行复制的服务器为从服务器

Redis复制功能分为同步与命令传播

  • 同步用于将从服务器的数据库状态更新至主服务器当前所在的数据库状态。
  • 命令传播则用于主服务器的数据库状态被修改,导致主从服务器的数据库状态不一致,让主从服务器重回到一致状态

解决旧复制断线重复制的低效问题

1、使用psync代替sync,psync具有完全重同步(用于初次复制)和部分重同步(用于断线后复制)两种模式

部分重同步的实现

  1. 复制偏移量
  2. 复制积压缓冲区
  3. 服务器运行ID

复制的实现和步骤

  1. 设置主服务器的地址和端口
  2. 建立套接字连接
  3. 发送Ping命令
  4. 身份验证
  5. 发送端口信息
  6. 同步
  7. 命令传播

如何同步呢?

  • 如果是psync命令执行的是完整重同步,主服务器需要成为从服务器的客户端,才能将保存在缓冲区里面的写命令发送给从服务器执行
  • 如果是psync命令执行的是部分重同步,主服务器需要成为从服务器的客户端,才能将保存在复制堆积(偏移量比从服务器多的那部分)缓冲区里面的写命令发送给从服务器执行

出现命令丢失怎么办?

如果主服务器发送给从服务器的写命令在半路丢失了,当从服务器会给主服务器发送replconf ack命令时,主服务器会发现从服务器的偏移量和自己的复制偏移量不一致,主服务会桌子复制缓冲区里找到和从服务器缺少的数据,然后重写发送给从服务器

主向从补发缺失数据的原理和部分重复制操作的原理非常像,不同的是补发缺失数据是在主从服务器没有断线的情况下执行,而部分重复制是在主从服务器断线后重连之后执行

心跳检测

  1. 检测主从服务器的网络连接状态
  2. 检测命令丢失

总结

  1. Redis2.8之前复制功能不能高效地处理断线后重复制情况,但Redis2.8后部分重复制解决可以解决这个问题
  2. 部分重同步的实现通过 复制偏移量、复制积压缓冲区、服务器运行ID三部分来实现
  3. 在复制操作刚开始的时候,从服务器会成为主服务器的客户端,并通过向主服务器发送命令请求来执行复制步骤,而复制操作后期,主从服务器会互相成为对方的客户端
  4. 主服务器通过从服务器传播命令来更新从服务器的状态,保持主从服务器一致,而从服务器则通过主服务器发送命令来进行心跳检测,以及命令丢失检测

哨兵

Sentinel,也就是哨兵是Redis的高可用的解决方案,由一个或多个Sentinel实例组成的哨兵系统可以监控如意多个主服务器以及它们下属的从服务器,自动将下线主服务器属下的某个从服务器升级为新的主服务器

image.png

哨兵主要有以下三个任务:

  1. 监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。
  2. 提醒(Notification):当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
  3. 自动故障迁移(Automatic failover):当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。

哨兵(sentinel) 是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。

每个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown).

若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称odown),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。

虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel)。

哨兵(sentinel) 的一些设计思路和zookeeper非常类似

启动并初始化Sentinel

  1. 初始化Sentinel服务器
  2. 使用Sentinel专用代码
  3. 初始化Sentinel状态
  4. 初始化Sentinel状态的masters属性
  5. 创建连向主服务器的网络连接

第五步结束后,Sentinel成为主服务器的客户端,他可以向主服务器发送命令,也可以从命令回复获取相关的信息

Sentinel会创建两个连向主服务器的异步网络连接

  1. 命令连接
  2. 订阅连接(为了防止未接收而导致客户端发送丢失信息)

获取主服务器信息

Sentinel默认十秒一次的频率,通过命令连接向被监控的主服务器发送info命令,并通过分析info命令的回复来获取主服务器的当前信息

获取从服务器信息

当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了为了新的从服务器创建相应的实例结构外,Sentinel还会创建连接到从服务器的命令连接和订阅连接。

检测主观下线状态

Sentinel会以一秒一次的频率向所有与它创建命令连接的实例(包括主服务器、从服务器、其他Sentinel)发送ping命令,并且通过实例发挥的ping回复来判断实例是否在线

检测客观下线状态

当哨兵将一个主服务器检测到主观下线,为了确认,它还会向其他哨兵询问该主服务器是否真的下线,如果其他哨兵也确认,那么Sentinel会判定该主服务器客观下线了,然后对主服务器执行故障转移操作。

选举Sentinel领头

当主服务器判定为客观下线后,监控该主服务器的所有Sentinel会选举一个领头sentinel出来执行故障转移操作

故障转移

1、选出新的主服务器

挑选出一个状态良好、数据完整的从服务器,然后向这个从服务器发送slaveof no one命令,将这个从服务器转移为主服务器。

如果有多个优先级最高、复制偏移量最大的从服务器,那么按照运行ID进行排序,选出运行ID最小的从服务器。

2、修改从服务器的复制目标

让所有的从服务器去复制新的主服务器,可以通过向从服务器发送slaveof命令来实现

3、将旧的主服务器变为从服务器

集群

即使使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用集群,就是分布式存储。即每台redis存储不同的内容,
共有16384个slot。每个redis分得一些slot,hash_slot = crc16(key) mod 16384 找到对应slot,键是可用键,如果有{}则取{}内的作为可用键,否则整个键是可用键
集群至少需要3主3从,且每个实例使用不同的配置文件,主从不用配置,集群会自己选。

Redis集群是Redis提供的分布式方案,集群通过分片来进行数据共享,并且提供复制和故障转移功能。

下面主要讲集群的节点、槽指派、命令执行、重新分片、转向、故障转移、消息等各个方面。

节点

将各个独立的节点连接起来,构成一个包含多个节点的集群。连接节点是通过cluster meet命令。

  1. 启动节点
  2. 集群数据结构
  3. cluster meet命令的实现(让另一个节点添加到本节点的集群里)

槽指派

redis集群采用的是数据分片,即sharding,而并不是一致性哈希(consistency hash)。

一个redis集群包含16834个哈希槽(hash slot)数据库中的每个键都属于这16384个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中CRC16(key)语句用于计算键key的 CRC16 校验和

集群中的每个节点负责处理一部分哈希槽。 举个例子, 一个集群可以有三个哈希槽, 其中:

  • 节点 A 负责处理 0号至 5500号哈希槽。
  • 节点 B 负责处理 5501号至 11000号哈希槽。
  • 节点 C 负责处理 11001号至 16384号哈希槽。

这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:

  • 如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。
  • 与此类似, 如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C , 然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。

因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。

1. 记录节点的槽指派信息

2. 传播节点的槽指派信息

3. 记录集群所有槽的指派信息

总结

1、节点通过握手将其他节点加入到自己所有的集群中
2、集群有16384(2^14)个槽可以指派给集群的各个节点,每个节点都会记录哪个槽指派给自己,而哪些槽指派给别人
3、对于集群的重新分片是由redis-strib负责执行的,重新分片的关键是将属于哪个槽的所有键值从一个节点转移到另一个节点。
4、MOVED错误表示槽的负责权已经从一个节点转移到了另一个节点,而ask错误是两个节点在迁移槽的过程使用的一种临时措施。
5、集群的从节点用于复制主节点和故障转移,并在主节点下线时,代替主节点继续处理命令请求
6、节点接收的命令请求不是由自己处理的槽,会发送MOVED错误给客户端,里面带指引客户端转向正在负责相关槽的节点

MOVED错误和ASK错误的区别?

两个都是重定向,MOVED是已经迁移完了,ASK是正在迁移过程中

为什么redis集群的最大槽数是16384个?

Redis 集群有16384(2^14)个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。

这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。

HASH_SLOT = CRC16(客户端key) mod 16384

CRC16算法产生的hash值有16bit,可以产生的值在0~65535之间。

在redis节点发送心跳包时需要把所有的槽放到这个心跳包里,以便让节点知道当前集群信息,16384=16k,在发送心跳包时使用char进行bitmap压缩后是2k(2 * 8 (8 bit) * 1024(1k) = 2K),也就是说使用2k的空间创建了16k的槽数。
65535=65k,压缩后就是8k(8 * 8 (8 bit) * 1024(1k) = 8K),也就是说需要需要8k的心跳包。

你可能感兴趣的:(Redis设计与实现笔记)