当前位置: 首页 > 科技观察

你还在为Redis的三种集群模式感到困惑吗?

时间:2023-03-12 22:15:20 科技观察

前言Redis作为一种高性能的内存数据库,广泛应用于当前主流的分布式架构系统中。为了提高系统的容错率,使用多实例Redis也是不可避免的,但是复杂度也远高于单实例。本文主要介绍Redis在多机数据库下的三种实现。主从模式Redis的主从模式是指主从复制。用户可以使用SLAVEOF命令或配置让一个服务器复制另一个服务器成为它的从服务器。主从模式架构Redis是如何实现主从模式的?Redis从服务器向主服务器发起同步时,一般使用SYNC或PSYNC命令。初始同步从服务器收到SLAVEOF命令后,会与其主服务器进行一次同步操作,进入主从复制过程。从服务器向主服务器发起SYNC或PSYNC命令。主服务器执行BGSAVE命令,生成RDB文件,使用缓存区记录以后所有的写命令。RDB文件生成后,主服务器会发送给从服务器。导入RDB文件,将自身数据库的状态同步更新为主服务器执行BGSAVE命令时的状态。主服务器将缓冲区中的所有写命令发送给从服务器,从服务将执行这些写命令,数据库状态与主服务器的最新状态同步。SYNC和PSYNC的区别主从同步完成后,如果从服务器宕机了一段时间,需要等主服务器重新上线后重新同步。SYNC和PSYNC命令的区别在于断开连接后重新复制的方式。不同的。SYNC从服务器重新向主服务器发起SYNC命令,主服务器重新生成所有数据并将RDB快照发送给从服务器开始同步。PSYNC从服务器重新向主服务器发起PSYNC命令。主服务器根据双方数据的偏差判断是需要完全重新同步还是只将断线期间执行的写命令发送给从服务器。显然,先发送PSYNC比发送SYNC效率要高得多。要知道同步所有数据是一个非常耗费资源的操作(磁盘IO、网络),仅仅因为网络一时不稳定就同步所有资源是非常不值得的。因此Redis在2.8版本之后开始使用PSYNC进行复制。PSYNC是如何实现部分重同步的?部分重同步的实现主要依赖三部分1.记录复制偏移量。主服务器和从服务器都会维护一个复制偏移量。当主服务器向从服务器发送N个字节的数据时,它会复制自己的偏移量+N。当从服务器从主服务器接收到N个字节的数据时,它也会加上自己的复制偏移量+N。当主从数据同步时,偏移量相等。而一旦从服务器断开连接一段时间,收到的数据就少了。那么此时master和slave的serveroffsets是不相等的,他们的差就是少传输的字节数。如果传输的数据量不是太大,没有超过主服务器的复制积压缓冲区大小,那么缓冲区内容会直接发送给从服务器,避免完全重新同步。否则,需要完全重新同步。2.复制积压缓冲区复制积压缓冲区是由主服务器维护的先进先出字节队列,默认大小为1mb。每当向从服务器发送写命令时,数据就会存储在这个队列中。每个字节记录自己的拷贝偏移量。当从服务器重新连接时,它会将自己的复制偏移量发送给主服务器。如果replicationbacklogbuffer中存在replicationoffset之后的数据,则只需要将后续数据发送给slaveserver即可。3、记录服务器ID在进行主从同步时,主服务器会将自己的服务器ID(通常是自动生成的UUID)发送给从服务器。断线恢复后,从服务器会判断该ID是否为当前连接的主服务器。如果是同一个ID,说明主服务器没有变化,尝试了部分重同步。如果不是同一个ID,说明主服务发生了变化,会完全重新同步主服务器。具体流程图如下:Redis哨兵模式(Sentinel)Redis主从模式虽然可以实现很好的数据备份,但可用性不高。一旦主服点宕机,只能手动切换主服。所以Redis的Sentinel模式也是解决主从模式的一种高可用方案。Sentinel模式引入了一个Sentinel系统来监控主服务器及其所属的所有从服务器。一旦发现主服务器宕机,就会自动选举其中一台从服务器升级到新的主服务器上,达到故障逃避的目的。同一个Sentinel系统也需要实现高可用,所以一般是一个集群,互相监控。而Sentinel本身其实就是一个Redis服务器,让Redis处于一种特殊的模式。实现原理1.Sentinel与主从服务器建立连接。Sentinel服务器启动后,会创建到master服务器的命令连接,并订阅master服务器的sentinel:hello频道,创建订阅连接。默认情况下,Sentinel会每10秒向主服务器发送一次INFO命令,主服务器将返回主服务器自身及其所有从服务器的信息。根据返回的信息,如果Sentinel服务器发现有新的从服务器上线,它会在连接主服务器的同时创建到从服务器的命令连接和订阅连接。2.判断主服务器是否离线每个Sentinel服务器每秒都会向与其连接的所有实例(包括主服务器、从服务器和其他Sentinel服务器)发送PING命令,根据实例是否离线来判断是否离线回复PONG命令。如果实例在收到PING命令的down-after-milliseconds毫秒内(根据配置)没有有效响应,判断主观下线。然后该实例将被发起PING命令的Sentinel主观地识别为离线。判断客观下线当一个主服务器被一个Sentinel服务器判断为客观下线时,为了保证主服务器真的下线,Sentinel会向Sentinel集群中的其他服务器进行确认,如果判断主服务器是offline当Sentinel服务器数量达到一定数量(通常是N/2+1),那么主服务器就会被客观判断为离线,需要进行failover。3.选举leaderSentinel当一个master服务器被客观判断为离线时,Sentinel集群会选举一个leaderSentinel服务器对离线的master服务器进行failover操作。整个选举实际上是基于RAFT共识算法实现的。大致思路是这样的:每个发现masterserver掉线的Sentinel都会请求其他Sentinels将自己设为本地leaderSentinels。收到的哨兵可以同意或拒绝。一个哨兵如果得到半数以上哨兵的支持,就会成为本次选举的领头羊。如果在给定时间内没有选出leaderSentinel,则在一段时间后重新开始选举,直到选出leaderSentinel。4、选举新的masterserverleaderserver会从slaveservice中选出最合适的作为新的masterserver。选择规则为:选择健康的从节点,排除最近未响应INFO命令的断开连接的从服务器。选择优先级配置高的从服务器选择复制偏移量大的服务器(表示数据最完整)选择新的主服务器后,主服务器会向新的主服务器发送SLAVEOFnoone命令,真正升级他到主服务器,并修改其他从服务器的复制目标,将旧的主服务器设置为从服务器,实现故障转移。RedisClusterRedissentinel模式实现了高可用和读写分离,但是仍然只有一个master节点,即所有的写操作都在master节点,这也成为了性能瓶颈。因此Redis在3.0之后加入了Cluster模式,通过去除无意节点来实现。集群会通过分片的方式将键值对节点保存在数据库中。一个Redis集群将由多个节点组成,每个节点之间相互连接。如果已连接,将保存有关自身和其他节点的信息。节点之间通过八卦协议交换彼此的状态,并保存新加入节点的信息。ShardingRedisCluster数据的整个数据库会被划分为16384个哈希槽,数据库中的每个键都属于这16384个槽中的一个,集群中的每个节点可以有0个或最多16384个槽。设置插槽分配我们可以使用命令CLUSTERADDSLOTS[slot...]将一个或多个插槽分配给一个节点。例如127.0.0.1:7777>CLUSTERADDSLOTS12345命令是将slot1,2,3,4,5分配给本地端口号为7777的节点。设置后,节点将发送slot分配信息给其他集群,以便其他集群更新信息。计算key属于哪个slotdefslot_number(key):returnCRC16(key)&16383计算hashslot的位置其实是用CRC16算法计算key值然后对16383取模得到最终所属的slot.也可以用CLUSTERKEYSLOT查看。分片过程当客户端发起对键值对的操作命令时,会随机分配到其中一个节点。节点计算密钥所属的槽。判断当前节点是否是key所属的slot。如果是,直接执行操作命令。如果不是,则将移动的错误返回给客户端。移动的错误将包含正确的节点地址和端口。客户端收到后可以直接重定向到正确的节点。RedisCluster的高可用Redis的每个节点都可以分为主节点和对应的从节点。主节点负责处理槽,从节点负责复制一个主节点,并在主节点下线时替换下线的主节点。如何实现故障转移其实和哨兵模式类似。Redis的每个节点都会周期性的向其他节点发送Ping消息来检测对方节点是否在线。当一个节点检测到另一个节点离线时,它会被设置为疑似离线。如果机器中超过一半的节点将主节点设置为怀疑离线,则该节点将被标记为离线并开始故障转移。通过raft算法从离线主节点的从节点中选出一个新的主节点。被选中的从节点执行SLAVEOFnoone命令成为新的主节点。新的主节点撤销离线主节点的槽分配,并将这些槽指向你的新主节点并广播到集群。把自己从从节点变成主节点。新的主节点开始接受并负责处理槽。小结本文主要介绍Redis的三种集群模式。总结一下主从模式可以实现读写分离和数据备份。但是sentinel模式,并没有“高可用”,可以看成是master-slave模式的“高可用”版本,它引入了Sentinel来监控整个Redis服务集群。但是由于只有一个master节点,所以还是存在写入瓶颈。Cluster模式不仅提供了一种高可用的手段,而且将数据分块存储在各个节点中,可以支持高并发的写入和读取。当然,实现也是最复杂的。