数据编码
持久化
- RDB 是一次全量备份,AOF 日志是连续的增量备份,
- RDB 是内存数据的二进制序列化形式,而 AOF 日志记录的是内存数据修改的指令记录文本
RDB
- RDB持久化是将某个时间点上Redis中的数据保存到一个RDB文件中
- 因此RDB持久化也叫做快照持久化
- 根据以下规则把内存中的数据复制到硬盘上,这称为快照
- 用户执行
SAVE
,BGSAVE
或者FLUSHALL
命令SAVE
会同步复制,执行时会阻塞所有请求,避免在生产中使用BGSAVE
会异步复制,通过LASTSAVE
获取最近一次成功执行快照的Unix时间FLUSHALL
会清空数据库中的所有数据,并在配置了自动快照时进行快照
- 通过参数配置自动快照,超过一定时间或被更改的键大于一定数量后进行异步快照
- 通过
save
选项设置多个保存条件,只要其中任意一个条件被满足,服务器就会执行BGSAVE命令
- 通过
- 设置了主从模式时,会在初始化从库时进行自动异步快照,并发送给从库
- 用户执行
- RDB文件存储上非常紧凑,是经过压缩的二进制格式
- 占用空间会小于内存中的数据大小
- 便于网络传输
- 通过RDB方式来持久化时,redis异常退出会丢失最后一次快照后更改的数据
- 如果不允许数据丢失,应该使用AOF方式进行持久化
- 通过载入RDB文件文件可以还原生成RDB文件时Redis中的数据
- Redis载入RDB文件并没有专门的命令,而是在Redis服务器启动时自动执行的
- Redis服务器启动时是否会载入RDB文件取决于服务器是否启用了AOF持久化功能
- 默认情况下Redis服务器的AOF持久化功能是关闭的,所以Redis服务器在启动时会载入RDB文件
- Redis 使用操作系统的多进程COW(Copy On Write) 机制来实现 RDB 持久化
AOF
- AOF 的主要作用是解决了数据持久化的实时性,目前已经是 Redis 持久化的主流方式
- 每次在执行写命令前储存命令本身到AOF文件中
- 会一定程度上降低性能
- 先存到磁盘,然后再执行指令
- AOF文件以文本格式储客户端原始通信协议的内容
- Redis通过逐一执行(重放)AOF文件中的命令来恢复数据到内存
- 恢复速度较RDB会慢一些
- Redis支持自动重写AOF文件
- 作用: 减少冗余命令
- 否则随着Redis服务器运行时间的增加,AOF文件中的内容会越来越多,文件的体积会越来越大
- 不仅过多的占用服务器磁盘空间,使用AOF文件来进行数据库还原所需的时间也越多
- 原理:
- 不需要对现有的AOF文件进行任何读取、分析或者写入操作,而是通过读取服务器当前的数据库数据来实现的
- 首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令
- 记录完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了
- AOF重写功能被放到后台子进程里执行
- 子进程进行AOF文件重写期间,服务器进程(父进程)可以继续处理命令请求
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性
- 作用: 减少冗余命令
- 默认不开启AOF持久化
- 可以通过
appendonly yes
配置来启用 - 此时重新启动Redis时会通过AOF而不是RDB来恢复数据
- 因为可能丢失的数据更少
- 会读入并重新执行一遍AOF文件里面保存的写命令,就可以还原Redis服务器关闭之前的数据
- 可以通过
- 由于操作系统会缓存写入硬盘的数据,所以AOF也会存在数据丢失的风险
- 可以配置主动刷新缓存到硬盘的规则和时间
always
每次执行都会同步everysec
每秒同步一次no
让操作系统决定何时刷入硬盘
- 可以配置主动刷新缓存到硬盘的规则和时间
混合持久化
高可用
主从同步
- 主从结构实现了读写分离,提高了redis服务器的负载
- 为了避免单点故障,应该把数据复制多个副本到不同服务器上
- 虽然持久化可以避免服务器重启造成了数据丢失,但是如果硬盘坏了仍会丢失数据
- 而且恢复数据需要较久时间,会影响线上服务的持续运行
- redis提供了replication功能,可以在一台服务器数据更新后,自动同步到其他数据库上
- 只需要在从库配置文件中加上
salveof 主库地址 端口
- 主库无需任何配置
- 只需要在从库配置文件中加上
- 特性
- 主库可读可写,从库只读
- 只有读压力被分摊了,而由于主库对于写请求还要同步给从库,写压力反而增大了
- 一个主库可以有多个从库,一个从库只能有一个主库
- 从库可以转化为主库
- 通过
SLAVEOF NO ONE
命令来停止接受同步数据
- 通过
- 主库可读可写,从库只读
- 主库异步同步数据给从库
- 避免主库性能降低
- 可能造成数据短期不一致
- 由于持久化操作相对耗时,可以在从库中启动持久化,而主库关闭持久化
- 这样可以提高写性能
- 从库崩溃后,主库会自动同步数据过来
- 主库崩溃后,现将从库设计为主库,然后把主库设置为新库的从库
- 这样就把数据从从库同步回主库了
主从复制
-
从库启动时的复制过程
-
增量复制
- 实现方式
- 从库会存储主库的
run ID
- 每个redis实例都有一个唯一的运行ID
- 重启后会生成一个新ID
- 同步时主库在传输命令时会同时写入
backlog
中,并记录当前积压队列命令的偏移量范围backlog
中储存的就是命令的文本
- 从库收到命令后会记录该命令的偏移量
- 开始增量复制是,从库使用
PSYNC
替换SYNC
命令,并带上主库运行ID和命令偏移量
- 从库会存储主库的
- 主库如何判断是否能够增量复制
- 判断传来的ID是不是和自己的一样
- 保证之前从库是和自己同步的,避免拿到错误数据
- 比如主库曾经断线重启过,此时ID会变,也会造成数据不一致
- 判断传来的命令偏移量是否在
backlog
队列中,如果在则发送队列中的剩余命令
- 判断传来的ID是不是和自己的一样
- 如果不能增量复制,会执行一次全量同步
backlog
体积设置的越大,容忍的主从断线时间越长
- 实现方式
-
从数据库也可以作为主库来接入从库,以承受更大的读压力
- 无硬盘复制
- 基于RDB持久化的复制方式的缺点
- 当主库就用RDB快照时,如果进行了主从复制,后续主库会从本次快照恢复数据,导致可能大量数据丢失
- 如果硬盘性能很慢,而RDB复制需要写入硬盘,会导致性能降低
- 开启无硬盘复制时,会直接通过网络发送数据给从库,而不需要先将快照写入硬盘
- 基于RDB持久化的复制方式的缺点
- 无硬盘复制
哨兵
-
作用
- 监控主库和从库是否正常运行
- 主库故障时,自动选择一个最优的从节点切换为主节点
-
客户端视角
- 客户端来连接集群时,会首先连接 sentinel,通过 sentinel 来查询主节点的地址, 然后再去连接主节点进行数据交互
- 当主节点发生故障时,客户端会重新向 sentinel 要地址,sentinel 会将最新的主节点地址告诉客户端
-
哨兵是一个独立部署的进程,通过配置文件确定要监控的主库
- 从库会被自动发现并监控,无需配置
- 可以配置同时监控多个主库,也可多个哨兵监控一个主库
sentinel monitor 主库别名 地址 端口 quorum(执行故障恢复操作最少需要几个哨兵同意)
- 最佳部署方案
- 哨兵的视角尽可能和每个redis节点的视角一致
- 为每个节点部署一个哨兵,无论是主库还是从库
- 每个哨兵和其对应节点的网络环境一致
- 设置quorum值为
N/2+1
,这样大部分哨兵同意后才进行故障恢复 - 由于每个哨兵会和所有redis节点建立连接,所以上述方案会导致很多连接
- 如果redis负载较高,会影响对哨兵的回复
- 哨兵的视角尽可能和每个redis节点的视角一致
- 实现原理
- 哨兵启动后会和要监控的主库建立2条连接
- 该连接和普通的客户端连接无异
- 一条用于订阅主库的
__sentinel__:hello
频道- 获取其他同样监控该库的哨兵信息
- 一条定期发送
INFO
,PING
等命令
- 哨兵会定时执行下面3个操作
- 每10秒向主库和从库发送
INFO
命令- 获取运行ID等消息,实现从库的自动发现和主从切换的信息更新
- 向主库和从库的
__sentinel__:hello
频道向其他哨兵分享自己的信息- 包括自己的基本信息和监控的主库相关信息
- 这样可以自动发现其他哨兵
- 通过定时
PING
监控redis和其他哨兵是否正常服务
- 每10秒向主库和从库发送
- 哨兵启动后会和要监控的主库建立2条连接
- 故障恢复
- 当一个哨兵PING某个节点超过
down-after-milliseconds
指定的时间未回复,则该哨兵认为其主观下线 - 如果该节点是主库,则该哨兵发送
SENTINEL is-master-by-addr
命令询问其他哨兵 - 如果大于
quorum
的哨兵认为节点主观下线,则该节点被认为客观下线 - 随后哨兵选举领头哨兵发起故障恢复流程
- 保证同一时间只有一个哨兵执行故障恢复
- 使用Raft算法进行选举
- 领头哨兵指定一个从库,使用
SLAVEOF NO ONE
将其选为主库- 关于从节点选举,一共有四个因素影响选举的结果:
- 断开连接时长、优先级排序、复制数量、进程id
- 如果连接断开的比较久,超过了某个阈值,就直接失去了选举权
- 如果拥有选举权,那就看谁的优先级高
- 这个在配置文件里可以设置,数值越小优先级越高
- 如果优先级相同,就看谁从master中复制的数据最多,选最多的那个
- 如果复制数量也相同,就选择进程id最小的那个
- 关于从节点选举,一共有四个因素影响选举的结果:
- 接着向所有其他从库发送
SLAVEOF
使其成为新主库的从库
- 当一个哨兵PING某个节点超过
集群方案
Redis Cluster方案(官方)
-
redis3.0版本后支持cluster功能
- 去中心化方案,没有proxy
- 客户端分片
-
涉及多键的命令需要保证每个键都位于同一个redis节点中,否则报错
- 例如
mget
命令
- 例如
-
使用
INFO cluster
命令判断是否正常启用的集群功能cluster_enabled:1
表示开启集群功能
-
集群准备就绪后会向每个redis节点发送
CLUSTER MEET ip port
命令- 告诉当前节点指定地址和端口的节点也是集群的一部分
- 加入新节点也只要发送上述命令给新节点A即可,地址和端口可以是集群中任意节点B的
- A和B握手完成后,B会使用Gossip协议将A的信息通知给集群中的每个节点
-
插槽的分配
- 所有的键会被映射到16384个插槽,每个主库负责其中一部分插槽
- 映射方式:
- 使用CRC16算法计算哈希值,并对16384取余
- 如果键名包含
{XXX}
,则只取其中的部分来计算映射,否则使用整个键计算- 因此涉及多键操作时,需要每个键含有相同的
{xxx}
部分
- 因此涉及多键操作时,需要每个键含有相同的
- 分配方式
- 新增: 在待分配的节点上执行
CLUSTER ADDSLOTS slot_num
- 移动: 使用
CLUSTER SETSLOT 槽号 NODE 新节点的运行ID
- 前提是插槽中没有任何键,因为该命令不会迁移键,否则会导致客户端在指定节点无法找到未迁移的键
- 手动获取插槽中存在的键,并手动迁移
- 获取插槽中的键:
CLUSTER GETKEYSINSLOT 槽号 数量
- 迁移键:
MIGRATE 目标地址 目标端口 键名 数据库号码 超时时间 [COPY] [REPLACE]
- COPY表示不把键从老库删除,REPLACE表示如果目标节点存在同名键,则覆盖
- 数据库号码在集群模式下只能为0
- 如果先改插槽,就会造成键的临时丢失;如果先迁移键,那么客户端读写旧节点时也会造成数据临时丢失或修改失败
- 因此手动迁移必须先下线集群
- 获取插槽中的键:
- 在线上迁移数据需要增加以下命令告知客户端键被迁移到哪了(重定向)
CLUSTER SETSLOT 槽号 MIGRATING 新节点运行ID
和CLUSTER SETSLOT 槽号 IMPORTING 原节点运行ID
- 查看: 使用
CLUSTER SLOTS
命令查看插槽对应的节点- 每条返回的记录的前两条是开始和结束槽号,接着是负责该槽段的库
- 先是主库,然后是所有从库
- 新增: 在待分配的节点上执行
-
集群的故障恢复流程和哨兵类似,区别在于节点自己充当了哨兵角色