集群架构,分布式寻址
之前说了Redis的主从架构和哨兵架构,他们之间存在演进的过程,即哨兵解决了主从架构的主从无法切换的问题,但没有完全解决,会出现访问瞬断的问题,访问瞬断的原因就是我们把slaver变成master时,需要修改slaver的配置文件(zookeeper不需要改配置文件),且为了让配置文件生效,还必须要重启slaver,这需要很长的时间,而这个时间redis因为没有master,所以服务是停止的,用户的体验十分不好。除了这个问题外,哨兵模式还有一个很大的问题,那就是他的sentinel是一个集群来进行监听master,但哨兵模式只有一个master对外提供服务,没办法支持很高并发的写操作,且难以避免得导致这个master很忙,持久化文件过大,影响了数据恢复或主从同步的效率。为了解决访问瞬断和单master的问题,我们最终采用的是cluster集群模式,而非主从或哨兵。
cluster大致得说,就是多个主从架构组成的大集群。显然,他有两个特点,一是他有多个master,二是他没有sentinel。那么也就有了两个问题,一是多个master是怎么协调的呢?二是没有了sentinel,他是如何知道master的状态并完成主从切换的呢?而集群架构如何解决哨兵架构的两个缺点访问瞬断?我们一个一个来解决。
首先解决这些问题中的多master协调和访问瞬断。他们都涉及到redis是怎么管理master的问题,大致得说,redis和mysql的分库分表差不多,就是分片存储,分片存储也就意味着数据没有冗余。与传统的分布式寻址方式不同,redis没有用传统的hash算法和一致性hash算法,而是采用新的hash slot槽位分布式寻址算法。Redis一共定义了16384个槽位,把所有的槽位分配给各个master,这个值可以在配置文件中设置。当我们进行写操作的时候,jedis会对key进行hash运算,将key的hash值对16384进行取余,找到对应的槽位,但jedis如何知道槽位对应哪个master呢?原来在第一次连接客户端时,jedis会缓存节点信息,所有就能由槽位定位master,然后进行写操作。而如果我们的master宕机,导致slot槽信息发生改变的时候,这个改变也会被同步到jedis缓存中,过程是当客户端根据旧的slot缓存,将数据set到错误的master的时候,master发现自己管理的槽位和数据的hash取余后对不上,也就是说两次hash取余使得数据存入slot槽位的准确性。再然后就会发送一个跳转重定位指令,携带目标master的地址,客户端在刷新本地的slot缓存信息,同样的原理在我们使用水平扩容时,发送请求失败后也会进行slot缓存信息的刷新。所以我们的写请求就被分流了,降低了单个master的压力,说到这里,访问瞬断就被解决了,但不是解决,因为选举后的master也要改配置文件和重启,对于单个的请求,他还是会有几秒钟的瞬断,但宏观得看,对于其他的写请求,因为被分流了,并不会访问瞬断。但是slot也有一个新的问题,也就是默认情况下,只要有一个master挂掉,则整个服务会停掉,因为一个计算的slot就无法被存放到正确的master,但Redis高可用的点就体现在这里,我们可以通过配置文件,设置cluster-require-full-coverage为no,则部分slot槽位的不可用不会导致整个服务不可用。
再吹毛求疵一下,说slot机制导致的两个问题,首先就是数据的分配得还不够均匀,原因就在于,我们只是对key进行hash分配slot,而没有对field进行均匀分配,这是一个后期优化的点。再就是slot机制严重影响了我们的批量操作mset,原因就是mset一次性打包了很多的key,而这么多的key按理来说是应该被分配到不同的master上的,但是,mset的机制是把多个key发送到同一个master上,这样就违背了slot机制,也就是说,集群不支持批量操作,除非多个key被hash再取余后结果对应同一个slot,但key又不能重名。redis对于这个点,提出了一种解决方案,也是一种给key命名的规范,变相得让key能够重名,也就是用{前缀}:name和{前缀}:id作为多个key,因为{}的存在,Redis在计算slot的时候,只会根据{}里面的前缀来计算slot,这样,只要前缀一样,计算的slot就一样,就能被mset一次性将多个key发送到同一个master了。
接下来解决,没有sentinel,集群是如何知道master的状态,从而触发主从切换的。在每个单个的主从架构中,各个节点通过gossip协议进行通信,节点之间的通信是独立的,和redis对外提供服务的端口是不一致的,也就是说redis和zookeeper一样,需要两个端口,但和zk不同的是,第二个不需要明文写出来,而是在第一个端口的基础上自动加一万作为第二个端口。gossip协议的特点是集群元数据分散,与gossip对应的是集中式,他也是一种集群元数据的维护方案,只不过redis选择了gossip。集中式就是将所有集群元信息集中管理,一旦更改,那么所有的节点都感知到,优点是元数据更新快,延迟小。但显然,集群数量多时,元数据的更新会会在一瞬间导致大量节点来读取新数据,导致元数据的存储压力很大,所以有了gossip分散存储协议。gossip协议通过了一系列的命令比如ping,pong,fail,meet来进行数据的同步,一个节点一个节点得扩散更新,优点是元信息的存储压力小,而缺点是数据更新有延迟。至此,说清楚了Redis的节点通信机制。
有了节点通信机制,那么就能监控master的状态了,这是支持主从切换的选举机制的前提,那么再来说说Redis的选举机制,同一个主从的slaver具有参选权,其他主从的master具有选举权。但有多个slaver怎么选呢?其中一个因素就是发现自己master宕机的速度,当slaver用gossip的ping命令,发现ping不通master的时候,就会触发选举,广播一个消息,让其他主从的master来投自己,对每个master来说,只响应第一个让自己投票的slaver,而且自己的票只有一张。当slaver获得半数的其他主从master的票后,停止选举,当选master,这里的半数意味着集群要有3个master节点,且最好是奇数,3个是因为,如果只有2个master,其中一个挂后,只剩下1个节点,一张票,而一张票违背了半数原则。奇数在这里的原因和zookeeper一样,考虑到了节省资源。之前说选举成功的因素在于谁先发现master宕机,但只看这个因素有点太武断了,应该选一些网络状态好的slaver当选,所以为了加大网络状态在选举中的权重,redis的slaver在发现maser宕机和发起选举间会有一个延迟,作用是让尽可能多个slaver在同一起跑线,增加网络状态的因素。
接下来大致得说一下具体的redis集群是如何搭建的,用到哪些命令和配置,具体的需要翻看纸质笔记。首先需要为各个节点单独特制一些配置文件,然后在启动时根据配置文件启动就可以了。需要常规的配置时包括,daemonize设置为yes,这样就能后台启动了,再就是设置好端口号,数据和日志的文件目录。就下来集群也有的配置项,首先是开启集群,他是官方提供的,不需要中间件,而是直接进行配置文件配置,将cluster-enable设置为yes就可以了,然后将各个节点的配置文件名在cluster-config-file中进行注册,因为之前说过了,在主从切换时会修改配置文件,所以需要配置好各个节点的配置文件。然后就是常规的集群超时时间的配置了,最好在启动一下aof的数据持久化。然后是一些配置文件中的坑,先要把bind 127.0.0.1注释掉,否则就会导致其他节点不能访问,这也是之前说的,因为没有sentinel,节点之间是需要gossip通信的。同理,关闭保护模式protected-node设置为no。再同理,最后一个坑是设置访问密码,节点之间的互相访问也是需要密码的,如果设置了redis访问密码,当然,生产环境下是肯定有密码的,所以我们需要把密码在配置文件中再设置一边,放在masterauth字段中,这样才能不影响节点之间的通信。总结一下,为了不影响节点之间的通信,我们总过配置了三处地方,一是注释bind字段,二是关闭保护模式,三是配置节点访问密码同单个节点的访问密码。至此,我们的各个节点就能按照上述的正确的配置文件来正常启动了,但是各个节点之间还是游离的,需要create -cluster -replices 1 +ip+端口号来建立联系,1的意思是一个主节点对应一个从节点,可以个性化设置。这样就大功告成了,我们可以用cluster info查看集群信息,里面还会显示slot的信息。
总的来说,redis集群优点有无中心架构,支持动态扩容,对业务透明。具备Sentinel的监控和自动Failover(故障转移)能力。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。高性能,客户端直连redis服务,免去了proxy代理的损耗。
缺点是运维很复杂,数据迁移需要人工干预,只能使用0号数据库,不支持批量操作(pipeline管道操作),并非不支持,分布式逻辑和存储模块耦合等
可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。
最后来说一下集群redis的java使用。maven引入jedis后就能使用了,需要注意的是yml的配置,需要配置好所以node的ip和端口号,就可以使用了stringRedisTemplate了,正常使用,内部封装好了的,包括计算slot的操作,我们是感知不到集群的。