时代在进步,生产环境中单机部署的情况已经越来越少了,redis同样如此。redis集群技术也越来越成熟,在生产中redis集群部署已经是一个常见的技术。
本文主要总结自《redis深度历险》
文章发表于本人的csdn博客redis主从复制、哨兵模式sentinel,今天搬运到掘金过来。
主从同步
所有集群模式都离不开一个基础:同步机制。节点之间的数据同步是集群基础,主从同步则是这一切的基础。
redis主从同步的意义: 有一些企业虽然没有使用redis集群(一般是基于业务量考虑),但是基本上至少做了主从同步。当一个redis 的master节点挂了的时候,如果要要重启这个redis节点,需要耗费很多时间来进行数据恢复和重启,为了不影响线上功能,使用slave节点代替master,将影响降到最低。
主从同步: redis的数据是异步同步的,所以分布式模式下的redis无法满足一致性要求。如果主从节点之间出现问题,比如网络中断,主节点可以继续对外服务,但是从节点无法同步主节点上的数据。这个时候就会出现数据不一致的情况,当网络恢复时从节点会努力去追赶上主节点上的数据。
数据同步时,支持主从、从从同步(后面统一说成是主从复制)
同步机制
增量同步
增量同步同步的是指令流,主节点会将那些对自己数据状态产生修改性影响的指令(比如增删改)记录在本地的内存buffer中,然后将buffer中的指令同步到从节点中。从节点一边执行同步以达到和主节点一样的状态,一边像主节点报告自己同步到哪里了(偏移量)。指令流在内存中,读写速率非常快。
内存buffer是有限的,它是一个环形数组,当数组内容满了会从头开始覆盖前面的内容:
当redis主节点中的指令还没来得及同步就被新指令覆盖时,从节点就无法通过指令流来进行同步了,这个时候需要新的同步机制————快照同步。
快照同步
进行快照同步时,需要将主节点内存中的数据全部刷入磁盘文件中,然后将快照文件的内容全部传到从节点。从节点接收文件完毕后,会将其内存中的数据清空,然后进行一次全量加载。加载完毕后再通知主节点进行增量同步。 如果快照同步的过程中又有新的指令被覆盖,此时还是无法进行增量同步,而是需要再发起一次快照同步,此时极有可能陷入一个循环:每次快照同步完,内存buffer中已经堆满指令,只能再发起一次快照同步。
- 当有新的节点加入集群时,会进行一次快照同步,同步完成后再进行增变量同步。
- 必须配置合理的buffer大小避免出现这种复制循环的现象。
小插曲:
在生产过程曾遇到这样的问题:redis变得非常慢,比直接查数据库还慢。这显然是不正常的,其中肯定存在问题。经过排查发现,buffer内存设置太小了,在进行主从同步时会触发快照同步,快照同步完由于buffer又被新的指令流写满,再一次触发快照同步,从而导致redis异常的慢。
紧急解决方案:增大buffer内存。
问题反思: - 没有从实际业务出发考虑到buffer的大小(其实碰到这个问题之前,并不知道还有这种事情)
- 缓存数据设置不合理,部分缓存数据的有效时间太长,部分key太长占据太多内存
- 系统设计一定要仔细 细化到任何一个小细节,一个小小的问题都可能导致系统奔溃
无盘复制
主节点进行快照同步时会进行很多IO操作,对系统负载产生很大影响。特别是当系统正在进行AOF的同步操作时,如果发生快照同步,AOF同步会被推迟执行,这将严重影响主节点服务效率。
所以从redis-2.8.18版本开始支持无盘复制功能:主节点通过套接字将快照内容发送到从节点,生成快照是一个遍历的过程:一边遍历内存一边将序列化的内容发送到从节点。从节点则还是和以前一样,先将内容存到磁盘再一次性进行加载。
Wait 指令
wait指令可以将异步复制的方式变成同步复制,确保系统的强一致性。实现强一致性的代价就是主节点的性能大大降低。
哨兵模式 Sentinel
主从模式虽然可以在一定程度上保证数据的高可用性,但是却需要人工进行节点的切换:某个主节点挂掉了,需要使用从节点,此时就需要人工进行主从的切换。都2020年了,这种模式肯定是不可取的。所以我们需要让系统自动完成这些事:如果主节点挂了,从节点会自动选举出新的节点作为主节点,系统可以继续平稳运行。
Redis Sentinel哨兵模式可以满足这种需求。
redis sentinel 是一套独立的集群,可以同时监控多个redis的 master-slave集群,可以将它看成是一套zookeeper集群。它是集群高可用的核心,一般由3~5个节点组成,即使某个节点宕机了系统还能高效运行。
客户端连接到集群时会先连接到sentinel,通过sentinel拿到master节点的地址,然后再和主节点进行数据交互。当主节点发生故障、宕机时,客户端会重新向sentinel发起一个请求以来获取新的master节点地址。sentinel随后将新选举出来的master节点地址发给客户端,客户端通过新的地址继续和redis进行数据交互。
当master节点挂掉的时候,客户端与master之间交互失败。
此时redis集群已经选举出新的master节点,可能与原master交互失败时会再发一次请求以获得新的master地址,当获取到新的master地址后与新的master节点进行数据交互。
消息丢失
redis采用异步复制的方式进行数据同步,这就意外着肯定会存在数据丢失的情况。从节点可能还没接收到主节点的所有数据,主节点就挂了。主从节点的延迟特越大,丢失的数据就越多。redis sentinel是无法保证数据完全不丢失的,但是可以通过配置使得数据进来不丢失:
min-slaves-to-write 1
min-slaves-max-lag 10
# min-slaves-to-write 1
#表示主节点至少有一个从节点在进行正常复制,否则就停止对外写服务,丧失可用性
# min-slaves-max-lag 10
#表示10s内主节点还没收到从节点的反馈就意味着从节点同步不正常
当sentinel主从切换时,主节点可能没有宕机(比如人为的进行主从切换),又或者master宕机后,已经恢复并且已经选举出新的master节点,原master节点变成了slaver,这个时候客户端如何获取正确的master节点呢?
通过源码可知,当进行连接的时候,会进行主库地址变更判断。连接池在建立连接时会去查询主库地址,然后和内存中的地址进行比对,如果发生变化则断开所有连接并使用新的地址建立新的链接。
在原master节点没有挂掉,并且原先master节点的连接已经在使用的情况下,上面所说的机制还是无法满足。这个时候就需要用到==ReadOnlyError==:当原master降级成为slaver时,所有修改性的指令都会抛出ReadOnlyError。
从而避免主从节点数据冲突的问题。