前言
Redis作为存储系统,不论数据存储在内存中,还是我们之前聊过的,以快照或者AOF日志文件形式持久化到磁盘,作为单实例,会面临很多问题:
- 数据量伸缩
单实例Redis存储k-v数量受限于内存和磁盘容量。长期运行的生产环境,会有很大瓶颈。
- 访问量伸缩
单实例的Redis单线程执行客户端请求,吞吐量依赖于单机能够处理的数量
- 单点故障
Redis持久化机制一定程度上,缓解了宕机/重启带来的数据安全性问题。但是对于单机系统应用,故障难以解决,会造成很大的生产问题
针对上述问题,对于数据存储系统,分布式环境下解决方案基本上通用的,如下:
- 水平拆分 sharding
- 主备复制 replication
- 故障转移 failover
水平拆分
为了解决数据量和访问量增加带来的单机局限问题,采用水平拆分数据,将数据按照规则,拆分分散到不同节点处理。水平拆分后的每个节点存储的数据原则上没有交集,节点间相互独立。
具体的拆分策略一般如下所示:
hash映射
对应业务数据key,采用hash算法,映射到有限值域(hash值)上,映射尽量均匀,然后将均匀分布的hash值映射到Redis实例上。
范围映射
直接按照业务数据key进行选择,对应的实例存放。比如,0<= key <100 存放到Redis 实例0上;当大于100,存放在redis其他实例上。按照一定的规则去存放
hash和范围结合
典型的实现方式就是一致性hash。先对业务数据key进行hash求值,然后根据有限值域hash值,进行范围映射,进行存放。
按照一定的策略进行数据拆分存放,但对于客户端对于数据的请求,需要按照对应的规则,去请求到对应的实例上,称为请求路由。
对于只读的跨实例请求,按照拆分的规则进行路由到对应的实例即可。
对于跨实例的读写请求,需要特殊注意。比如,一个客户端请求需要先进行读取实例A中数据,根据获得的数据,再对实例B实施写操作,此时整个业务请求就失去了原子性。如果没有采用集群方案之前,可以通过proxy 代理层处理sharding逻辑。如独立的实例:Twemproxy
主备复制
为了解决单机性能瓶颈,引入数据拆分,将数据分发至多个redis节点,多个节点数据独立。
但是,当出现宕机问题,还是没法避免数据丢失问题。因此,引用主备复制机制,解决宕机数据安全问题。
主备复制流程
Redis包含master slave两类节点,master对外提供数据读写服务;slave节点作为master的数据备份,拥有master的全量数据,对外不提供写服务,可以读。
- slave节点刚启动,向master节点,发起SYNC命令,master被动将新进的slave节点加入自己的主备复制集群中
- master收到SYNC命令之后,开启BGSAVE操作,进行快照操作
- master完成BGSAVE之后,将快照信息发送给slave
- 在发送期间,master收到其他请求的写命令,除了正常响应之外,会存储到back-log
- 快照信息传送完毕,会继续传送back-log,等于追加增加的指令。
- 后续所有的对于master的写操作,master会同时同步给slave。保持实时异步复制
对于slave来讲,处理逻辑如下:
- 发送完SYNC指令,对外提供读服务
- 接收到master的快照信息,此时将slave的现有数据清空,将master的快照写入内存
- 接受back-log执行
如多个slave节点发送SYNC命令到master,那么只要master没有完成BGSAVE操作,slave节点收到的快照信息和back-log是一致的。
断点续传
当slave节点发送SYNC指令到master,master会dump全部数据,进行异步复制。当一个已经和master完成了同步,且持续保持了很长时间的slave节点,突然断开重连,那么如果仍执行SYNC指令,就会再重发一遍。会造成不必要的开销,其中可能改变的数据没有或者很少。
Redis的PSYNC用来替代SYNC指令,可以实现断点续传。master slave维护一个offset记录当前已经同步过的指令,当出现断点重连之后,比较offset,只进行同步没有执行过的命令内容。
故障转移
当主备复制被正确使用之后,组成的集群,就具有一定的高可用,当master存在宕机或者故障,slave会自动切换为master,对外提供读写服务,这种机制称为failover(故障转移)。
针对如何发现故障,一般解决方案为采用一个daemon进程,监控所有的master-slave节点,观察可用性,并提供切换方案。
但是单机的daemon又会出现单点问题,需要引入多个daemon,才进行集群管理。
多个daemon集群方案,又会出现新的问题,要解决集群数据一致性问题。
Redis的sentinel提供了一套多daemon的交互机制,解决故障发现、故障转移决策协商机制等问题。
sentinel的互相感知
通过发布订阅模式来实现互相感知。
Master节点发布一个sentinel频道channel,所有的sentinel需要订阅master上的同channel,并发布自己的信息到channel,因此当新的sentinel增加,能够及时订阅到channel信息,并感知到当前所有订阅该channel的节点的信息。
master的故障发现
sentinel节点通过心跳检测的方式,定期向master发送心跳包判断是否存活。一旦没有收到正确响应,当前sentinel节点,判断master为主观不可用态,此时没有最终的决议,只有当前节点的判断。
随后所有的sentinel节点进行确定,当确定不可用的节点数大于配置的阈值,则判断不可用,进行failover故障转移流程。
failover决策
Redis的sentinel机制采用的是Raft数据一致性协议,那可以Paxos的最强有力的颠覆者。如果不了解Raft的可以参考我之前的文章,深入分布式缓存-Raft,相关的介绍。