- Redis的高可靠性:
- 数据尽量少丢失(AOF、RDB)
- 服务尽量少中断(增加副本冗余量,将一份数据同时保存在多个实例上)
- 主从库读写分离,主、从库均可接收读操作,主库接受写操作,执行后同步给从库
- 主从库第一次数据同步:
- 主从库建立连接、协商同步,为全量复制做准备
- 主库同步所有数据到从库,从库收到数据后,在本地完成数据加载,这个过程主要依赖于内存快照生成的RDB文件
- 主从库全量复制时的主库压力: 全量复制需要生成、传输RDB文件,从库数量大时会导致主库忙于fork子进程生成RDB文件,阻塞主线程处理正常请求,导致主库响应速度下降,传输RDB文件会占用主库网络带宽
- “主-从-从”模式,主从级联模式分担主库压力: 选择部分从库,用于级联其他从库,以减轻主库压力
- 主从库完成全量复制后,会维护一个网络连接,这个过程被称为基于长连接的命令传播,可以避免频繁建立连接的开销
- 主从库网络连接中断: 网络中断后,主库会采用增量复制(Redis 2.8之后)的方式继续同步,只把主从库网络断连期间主库收到的命令同步给从库
- repl_backlog_buffer: 一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置,缓冲区写满后,主库继续写入则会覆盖之前写入的操作,可在配置中调整repl_backlog_size参数
- 主从同步使用RDB而不使用AOF的原因: RDB文件内容是经过压缩的二进制数据(不同数据类型进行针对性优化),文件很小,AOF文件记录了每次写操作的指令,文件体积大,且包括冗余操作(同一个Key的多次操作)
- 主库还可以根据slave_repl_offset判断是全量复制,还是增量复制
- 哨兵: 本质上是一个运行在特定模式下的redis实例,不服务请求操作,只完成监控、选主、通知的任务
- 切换主库流程:
- 监控: 判断主从库下线
- 选主: 选出新主库
- 通知: 让从库执行replicaof,与新主库同步,通知客户端,与新主库连接
- 主观下线: 哨兵进程使用PING命令检测自己与主、从库的网络连接情况,用于判断实例状态
- 客观下线: 哨兵集群中大多数实例判断主库已经“客观下线”时,主库才会被标记为“客观下线”,当有N个哨兵实例时,最好要有N/2+1个实例判断主库为“主观下线”,最终才能判断主库为“客观下线”
- 选定新主库:
- 筛选: 检查从库当前在线状态,并且判断它之前的网络连接状态
- 根据从库优先级、从库复制进度、从库ID号三个规则给从库打分,得分最高者为新主库
- 哨兵集群组成: 基于Redis提供的pub/sub机制,哨兵与主库建立连接,发布自身连接信息(IP、端口),并从主库上订阅信息,获取其他哨兵发布的连接信息
- 哨兵处理彼此之间建立连接形成集群外,还需要跟从库建立连接,因为在哨兵的监控任务中,需要对主从库进行心跳判断,主从库切换完成后,还需要通知从库与新主库进行同步
- 由哪个哨兵执行主从切换: 通过投票机制(Leader选举),与哨兵集群判断“客观下线”过程相似
- 要保证所有哨兵实例配置一致,尤其是主观下线的判断之down-after-milliseconds,此值不一致可能导致哨兵集群无法对主库故障形成共识
- 哨兵对主从库进行的在线状态判断等操作属于时间时间,通过定时器完成,每个哨兵的定时器执行周期都会添加一个随机时间偏移,避免哨兵同时判定主库下线,同时进行leader选举
- 切片集群(分片集群): 指启动多个Redis实例组成一个集群,然后按照一定的规则,把收到的数据划分成多分,每一份用一个实例来保存
- 扩展方案:
- 纵向扩展: 升级单个Redis实例资源配置, 实施简单、直接,但是会受到硬件和成本的限制
- 横向扩展: 横向增加当前Redis实例的个数
- Redis Cluster: 无中心化,采用哈希槽(Hash Slot)处理数据和实例之间的映射关系,每个键值对会根据它的Key被映射到一个哈希槽中
- 可根据不同实例配置,手动分配哈希槽,需要把所有槽分配完毕,否则Redis集群无法工作
- 客户端定位数据: 客户端与集群实例建立连接后,实例会把哈希槽的分配信息发送给客户端
- Redis实例会把自己的哈希槽信息发给与它相连接的其他实例,来完成哈希槽分配信息的扩散
- 重新分配哈希槽:
- 在集群中,实例新增或者删除,都会导致Redis需要重新分配哈希槽
- 为了负载均衡,Redis需要吧哈希槽在所有实例上重新分配一遍
- Redis Cluster的重定向机制: 实例接受到客户端请求后,假如实例上并没有这个键值对映射的哈希槽,这个实例会给客户端返回一个包含新实例访问地址的MOVED命令响应结果,客户端接受后则会更新本地缓存的对应关系,并重新发送请求到正确的实例
- 假如请求时实例数据迁移只完成了部分,旧实例会给客户端返回一条ASK报错信息,客户端接收后向正确的新实例发送ASKING命令,请求新实例允许执行客户端接下来发送的命令,然后再向新实例发送命令读取数据(此过程不更新客户端对应关系缓存)
- 分片实现采用hash-based而非range-based,能把数据打散,不容易引起数据倾斜,有利于数据分片分布均衡,提高访问性能