读写分离
Redis通过一主多从来实现读写分离。在主库上进行写操作,保证数据的一致性。在从库上进行读操作,来分摊主库的读压力。如果非要在将写操作分发到其他从库上,那么就需要额外的机制(比如加锁)来保证数据的唯一性,得不偿失。
主从同步
使用从库来分摊主库的读压力,就要保证主从数据一致。主从同步有两种类型:全量以及增量同步
全量同步
1) 在从库上执行replicaof masterip masterport 来进行主从同步
2) 主从之间建立长连接,从库先发送psync ? -1给主库,其中?表示runID -1表示偏移值。因为是第一次建立连接,从库不知道主库的runId与复制偏移量。主库接收到请求后,将自身的runID以及当前的复制偏移值响应给从库。
3)主库fork子进程进行bgsave操作生成RDB文件,然后将RDB文件传输给从库。在此期间,主库正常接收读写操作,并且将读操作指令保存在replication buffer&replication backlog(用于增量同步)中。从库接受到RDB之后会清空现在有数据,然后加载RDB数据。
4)从库加载完RDB之后,从库的复制偏移量与主库开始bgsave时的复制偏移量保持一致。然后,主库会将replication buffer中的指令发送给从库,从库执行replication buffer中的操作指令,从而实现主从数据一致。当主从同步正常工作后,主库会开始增量同步。
5)replication buffer容量不够时,会导致同步失败。
主从从模式
如果一个主从需要与太多的从库进行同步的话,那么主库就要fork出很多个子进程以及多份replication buffer来进行主从同步。这对主库会造成很大的压力,从而影响本身的读写操作。针对该问题,可以选择一些从库来级联其他的从库。这样同步的压力就分摊到其他的从库上,减少对主库的压力。
增量同步
主库在同步时也会将写操作记录到replication backlog(复制积压缓冲区),且记录自身的复制偏移量。从库在同步时也会记录最新的复制偏移量。那么主从偏移量之间的数据就是从库所缺失的。当主从同步连接断开后,该从库对应的replication buffer就会被释放。重新建立连接时,从库会将自身的偏移量发送给主库。主库会判断从库缺失的数据是否在replication backlog中。如果可以进行增量同步,在replication backlog中找到从库上次的复制偏移位置,然后从该位置将指令通过replicationbuffer发送给从库。 如果没有则只能重新全量同步。
1)replication backlog中的指令也是通过replication buffer来发送给从库的。
2)replication backlog是环形缓冲区,当写入内容超出上限时会进行覆盖写,这样会导致主备不一致。因而将该缓冲区的容量设置大一些。
3)replication backlog是各个从库所共用的。唯一不同的是,每个从库同步的偏移量有差异。
4)复制偏移量是redis已经执行完的指令字节数。
5)当主从恢复连接时发现无法继续增量同步,那么就会重新开始全量同步。
replication buffer与 replication backlog有啥区别?
replication buffer相当于主从通讯的网络层buffer,当从库与主库调开链接时,与之对应的replication buffer则会被释放掉。replication backlog是一个环形队列,它是各个从库共享的,只要有一个从库与主库还处在连接中,那么replication backlog就继续存在。主库要根据各个从库的复制偏移量来选择增量同步的操作指令,然后具体的数据还是通过replication buffer发送给从库。
哨兵集群
redis读写分离中,从库挂了,客户端的读操作可以选择其他从库。但是主库挂了,就会影响业务的正常写操作以及主从同步。redis引入了哨兵机制来保证redis集群的高可用。
哨兵就是一个运行在特定模式下的 Redis 实例,只不过它并不服务请求操作,只是完成监控、选主和通知的任务。每个哨兵节点会订阅主库的"__sentinel__:hello"的频道。各个哨兵节点通过该频道能够知道其他哨兵节点的信息,这些节点彼此之间就可以建立网络连接来进行协商、选举等操作。此外,每个哨兵节点会向主库发送INFO命令来获取从库节点的信息来方便监听从库的运行情况。
哨兵的主要任务有:监视主库运行情况、选举新的主库、更新客户端的主库信息。
监视主库运行情况
哨兵会周期性的发送ping命令。如果从库响应超时,那么从库会被标记为主观下线。如果主库响应超时,不能简单的将主库标记为主观下线,然后直接主从切换。主库响应超时有很多因素导致(网络原因或者主库压力大),而且主库切换会耗费额外的资源,对业务的影响很大。因此哨兵需要确定主库真的下线。哨兵集群中,当超过配置数量的哨兵节点认为主库下线,那么主库这是才被标记为客观下线。这样就可以降低误判主库下线的概率。
哨兵选举
哨兵集群有多个哨兵节点,但是只有一个节点来执行切换动作。各个哨兵节点会通过协商选举出一个leader,由整个leader节点来进行切换。选举流程简单如下:当某一个哨兵节点发现主库下线之后,会向其他哨兵节点发送
is-master-down-by-addr 命令,询问其他节点的“意见”。当该节点收到多数节点认为主库已下线的回复之后,会立刻向其他哨兵节点发送希望自身作为leader的命令,当该节点收到多数同意答复后,该节点就成为了leader。由该节点来进行主从切换。
1)哨兵节点自身如果参与选举,那么就拒绝其他节点成为leader。
2)哨兵节点答复第一个选举请求为Y,对后面其他节点选举请求为N。
3)每个哨兵节点会轮询检测主库的运行状态,轮询周期会加上随机值,这样能够有效避免多个节点同时参加选举。
4)当第一轮选举没有结果时(网络原因,或者所有节点都希望自己当老大),那么会进行第二轮重试。
选择新的主库
哨兵集群判定主库客观下线之后,需要在从库中选择一个新的主库。首先排除网络情况不好的从库(主从经常掉线),然后依次根据配置优先级、主从同步程度相似度、runID号小优先来进行选择。首先选择优先级最高的从库,如果有则选择该从库为主库。如果没有继续判断各个从库与主库复制偏移量的差值,差值最小的为主库。如果各个从库的同步进度都一样,那么就选择runID号最小的从库作为主库。通过哨兵选举机制,当哨兵出现单节点故障时,还能继续保证redis集群的高可用。但是当哨兵节点只剩2个时,那么就无法正常执行多数服从少数的选举操作了。
更新客户端新的主库信息
客户端订阅了哨兵的消息。当哨兵选出新的主库之后,会将新主库的消息发布出去。客户端可以即使获取到新主库的信息,并以此继续进行读写请求。
切片集群
当k-v数据量太大时,正常有两种扩容方案:
1)纵向扩容;升级redis实例宿主机配置,加内存。该方案简单粗暴,但是当内存数据过多时,redis fork子进程会对主进程造成堵塞,且硬件升级成本很高。比较适用于无需持久化的单redis实例
2)横向扩容:添加redis实例,将key按照某种算法均匀分布到各个实例上,这样降低单redis实例的压力,提高redis集群的整个效率。
Redis-Cluster
Redis-Cluster是redis的切片集群方案。Redis-Cluster中有16384个哈希槽。每个key通过CRC16算法得到一个16bit的哈希值。然后用该哈希值对16384取模,最后得出来的结果就是该Key所对应的哈希槽。Redis-Cluster在部署时,哈希槽会平均分布再各个实例上,每个Redis实例会获取对应的哈希槽。N个redis实例,每个实例负责16384/N个哈希槽。然后各个Redis实例互相建立连接交互各自的切片信息。这样,每个Redis实例就能知道全部的切片信息。客户端在跟任何一个Redis实例连接时,都能从该实例处获取到切换信息。在读写key时,通过CRC16算法+取模获取到对应的哈希槽。然后能根据切片信息获取到对应的Redis实例信息。最终能找到对应的Redis实例来进行读写请求。
客户端在进行读写操作时,服务端的切片信息会发生变化,比如增减实例或者为了负责均衡重新分配哈希槽。Redis-Cluster通过重定向的机制,来保证客户端能够进行正常的请求。切片分配变化会有两种场景:
1)Redis实例已经调整完毕
当客户端按照已有的切片信息进行请求,Redis实例发现该Key不是自己负责,那么会返回MOVED错误 将正确的该哈希槽对应的新Redis信息发送给客户端。客户端根据MOVED错误中的信息重新进行请求即可,并且也会更新本地的切片信息。
2)Redis实例正在调整中
当客户端操作的key对应的哈希槽正在调整中,那么原Redis实例会返回一个ASK错误。该错误包含了当前哈希槽所对应的新实例信息。然后客户端向新实例发送一条asking命令,让新实例接受接下来的操作请求。但是与MOVED不同的是,这种场景不会更新客户端的切片信息。如果客户端收到ASK后还是可以继续向原实例发送请求。