文章目录
3 集群篇—众人拾柴火焰高
复制
基本概述
Redis支持复制的功能,以实现当一台服务器的数据更新后,自动将新的数据异步同步到其它数据库。
Redis复制实现中,把数据库分为主数据库master和从数据库slave ,主数据库可以进行读写操作,从数据库默认是只读的,当主数据库数据变化的时候,会自动同步给从数据库。
主从复制的结构支持一个Mater带多个Slave,也可以Slave带Slave模式
复制的好处:
- 可以实现读写分离
- 在主数据崩溃时可以实现数据恢复
- 可进行水平扩容支撑高并发
基本配置
配置原则
:主数据库不做配置,从数据库中设置replicaof 主数据库ip 主数据库port
具体的配置请查阅附录一
基本原理
- 全量复制
- slave启动时,会向master发送psync请求,如果这是slave重新连 接master,那么master仅仅会复制给slave缺少的数据; 如果是第 一次连接master,那么会触发一次全量复制
- 主数据库接到psync请求后,如果是全量复制,会在后台保存快照 ,就是实现RDB持久化,并将保存快照期间接收到的命令缓存起来
- 快照完成后,主数据库会将快照文件和所有缓存的命令发送给从数据库
- 从数据库接收后,会载入快照文件并执行缓存的命令,从而完成复 制的初始化
-
增量复制
- 如果是重新连接,Master会检查backlog里面的offset, master和 slave都会保存一个复制的offset还有一个master id,offset是保 存在backlog中的。Master只会把已经复制的offset后面的数据复 制给Slave,类似断点续传
- 在数据库使用阶段,主数据库会自动把每次收到的写命令同步到从 服务器
- Slave在复制的时候,不会阻塞Master的正常工作;也不会阻塞对 自己的查询操作,它会用旧的数据集来提供服务; 但是复制完成的 时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外 服务了
PS:Master一定要持久化
- 乐观复制策略:Redis采用乐观复制的策略,容忍在一定时间内主从数据库 的内容不同,当然最终的数据会是一样的。
这个策略保证了性能, 在复制的时候,主数据库并不阻塞,照样处理客户端的请求。 Redis提供了配置来限制只有当数据库至少同步给指定数量 的从数据库时,主数据库才可写,否则返回错误。配置是:min-replicas-to-write、min-replicas-max-lag
- 无硬盘复制:Master直接创建一个子进程,来生成RDB文件的内容,并通 过网络直接传送给Slave,也就是RDB文件不保存到磁盘上。
数据丢失解决方案
- 异步复制导致丢失:master宕机,在主备切换的过程中,可能会导致数据丢失。
- 主从脑裂导致丢失:网络原因导致master短时间脱离集群,集群产生了新master,但客户端仍然往旧mater写数据,网络恢复后,旧master变成slave与新mater同步导致数据丢失。
解决方案:配置min-replicas-to-write和min-replicas-max-lag两个参数,比如: min-replicas-to-write 1 、 min-replicas-max-lag 10。
要求至少有1个slave,数据复制和同步的延迟不能超过10秒。
一旦所有的slave,数据复制和同步的延迟都超过了10秒钟, 那么这个时候,master就不会再接收任何请求了。
脑裂情况下,一个master跟其它slave丢了连接,那么这两个配置可以确保,如果不能继续给指定数量的slave发送数据,而且slave 超过10秒没有给自己应答消息,那么就直接拒绝客户端的写请求。
哨兵
基本概述
哨兵是Redis 复制集集群的重要组件,它的主要作用有:
- 集群监控:监控主从数据库运行是否正常
- 故障转移:当主数据库出现故障时,自动将从数据库转换成为主数据库
- 配置中心:客户端通过连接哨兵来获得当前Redis服务的主节点地址
- 消息通知:哨兵可以将故障转移的结果发送给客户端
开启哨兵功能需要建立一个个sentinel.conf文件,里面设置要监控的主数据库的名字,比如:sentinel monitor 监控的主数据库的名字 127.0.0.1 6380 1
其中数字1表示的是哨兵判断主节点是否法神故障的最低票数,这个文件在运行期间会被sentinel动态进行更改,可以同时监控多个主数据库,一行一个配置即可,详细的配置参考附录二。
哨兵集群在整个主从集群中的功能关系结构图如下:
主从节点存放数据,而哨兵节点用来自动监控和维护集群,不存放数据
每个哨兵节点维护了3个定时任务:
- 1 哨兵向master-slave节点发送info命令来获取复制集的结构
- 2 哨兵之间通过pub/sub系统来互相感知装态和信息
- 3 哨兵向master-slave节点发送ping命令来进行心跳检测
基本原理
- SDown:主观下线,在心跳检测的定时任务中,如果其它节点超过一定时间没有回复, 哨兵节点就会将其进行主观下线。也就是说,主观下线的意思是一个哨兵节点“主观地”判断某个节点下线
-
ODown:客观下线,哨兵节点在对主节点进行主观下线后,会通过sentinel is-master-down-by-addr命令询问其它哨兵节点该主节点的状态;如果判断主节点下线的哨兵数量达到一定数值,则对该主节点进行客观下线。
哨兵四大基本原理:自动发现、选举领导、故障转移、配置传播
- 自动发现
哨兵互相之间的相互自动发现,是通过redis的pub/sub系统实现的 ,每个哨兵都会往sentinel:hello这个channel里发送一个消息,这时 候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在。
每隔两秒钟,每个哨兵都会往自己监控的某个master+slave对应的 sentinel:hello channel里发送一个消息,内容是自己的host、ip和 runid还有对这个master的监控配置。
每个哨兵也会去监听自己监控的每个master+slaves对应的 sentinel:hello channel,然后去感知到同样在监听这个 master+slave的其他哨兵的存在。
每个哨兵跟其他哨兵交换对master的监控配置,互相进行监控配置 的同步
- 选举领导:选举出领导者哨兵
哨兵选举涉及两个参数:quorum(确认客观下线的最少的哨兵数量),majority(授权进行主从切换的最少的哨兵数量)
每次一个哨兵要做主备切换,首先需要quorum数量的哨兵认为master客观下线,然后选举出一个哨兵来做切换,这个哨兵还需要得到majority哨兵的授权,才能正式执行切换
当主节点被判断客观下线以后,各个哨兵节点会进行协商, 选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操作。
监视该主节点的所有哨兵都有可能被选为领导者,选举使用的算法是Raft算法;Raft算法的基本思路是先到先得:即在一轮选举中,哨兵A向B发送成为领导者的申请,如果B没有同意过其他哨兵,则会同意A成为领导者
- 故障转移:选举出的领导者哨兵,开始进行故障转移操作,选举出新mater节点,大体可以分为3个步骤
1 在从节点中选择新的主节点:选择的原则是,首先过滤掉不健康的从节点; 然后选择优先级最高的从节点(由slave-priority指定);如果优先级无法区分,则选择复制offset最大的从节点;如果仍无法区分,则选择runid最小的从节点
2 更新主从状态:通过slaveof no one命令,让选出来的从节点成为主节点; 并通过slaveof命令让其他节点成为其从节点
3 将已经下线的主节点设置为新的主节点的从节点,当其重新上线后,它会成为新的主节点的从节点
- 配置传播
新的master选出过后,执行切换的那个哨兵,会从要切换到的新master那里得到一个configuration epoch,这就是一个 version号,每次切换的version号都必须是唯一的。
如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待failover-timeout时间,然后接替继续执行切换,此时会重新获取一个新的configuration epoch,作为新的version号。
哨兵完成master切换之后,会在自己本地更新生成最新的master配置,然后同步给其他的哨兵,就是通过之前说的pub/sub 消息机制。
这时version号就很重要了,因为各种消息都是通过一个 channel去发布和监听的,所以一个哨兵完成一次master的切换之 后,新的master配置是跟着新的version号的。
其他的哨兵都是根据版本号的大小来更新自己的master配置的。
使用建议
- 哨兵节点的数量应为多个,哨兵本身应该集群,保证高可用
- 哨兵节点的数量应该是奇数
- 各个哨兵节点的配置应一致
另外如果哨兵节点部署在Docker等容器里面,尤其要注意端口的正确映射,还需要知道哨兵集群+主从复制,并不能保证数据零丢失
分片
由于复制中,每个数据库都是拥有完整的数据,因此复制的 总数据存储量,受限于内存最小的数据库节点,如果数据量过大, 复制就无能为力了。
这个时候就需要用到分片技术,分片是将数据拆分到多个Redis实例的过程,这样每个Redis实例将只包含完整数据的一部分。
常见的分片方式有:按范围分片和哈希分片
分片实现方式
- 在客户端进行分片
- 通过代理来进行分片,比如:Twemproxy
- 查询路由:就是发送查询到一个随机实例,这个实例会保证转发你的查询到正确的节点,Redis集群在客户端的帮助下,实现了查询路由的一种混合形式,请求不是直接从Redis实例转发到另一个, 而是客户端收到重定向到正确的节点(客户端启动时,加上-c参数)
- 在服务器端进行分片, Redis采用哈希槽(hash slot)的方式在服务器端进行分片:Redis集群有16384个哈希槽,使用键的CRC16 编码对16384取模来计算一个键所属的哈希槽
分片缺点
- 不支持涉及多键的操作,如mget,如果所操作的键都在同一个节点 ,就正常执行,否则会提示错误
- 分片的粒度是键,因此每个键对应的值不要太大
- 数据备份会比较麻烦,备份数据时你需要聚合多个实例和主机的持 久化文件
- 扩容的处理比较麻烦
- 故障恢复的处理会比较麻烦,可能需要重新梳理Master和Slave的 关系,并调整每个复制集里面的数据
结论:尽量避免去使用分片概念
集群架构
基本概念
由于数据量过大,单个复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展,每个复制集只负责存储整个数据集的一部分,这就是Redis的集群。
分片可以模拟实现集群效果,但是有非常多的缺点,已经不推荐使用
现在Redis支持集群,在不降低性能的情况下,还提供了网络分区后的 可访问性和支持对主数据库故障的恢复。
- redis集群支持多个Master,每个Master可以挂载多个Slave,便于支持读写分离
- 集群Cluster内置了高可用的支持,无需再去使用哨兵的功能
- 集群中所有节点彼此互相连接,内部使用二进制协议(gossip)来进行优化传输速度和带宽
- 客户端与任一节点直接连接即可访问集群数据
- 集群负责把插槽分配到各个物理服务节点,由集群来负责维护节点、插槽、数据之间关系
使用集群后,都只能使用默认的0号数据库
每个Redis集群节点需要两个TCP连接打开,正常的TCP端口用来服 务客户端,例如6379,加10000的端口用作数据端口,必须保证防火墙打开这两个端口
Redis集群不保证强一致性,这意味着在特定的条件下,Redis集群 可能会丢掉一些被系统收到的写入请求命令。
手工创建集群
集群的最低标准6个redis实例:3个master节点,每个master带一个slave节点
集群创建的步骤大致 如下:
- 1 集群配置:将每个数据库的cluster-enabled配置选项打开,然 后再修改如下内容:pidfile、port、logfile、dbfilename、 cluster-config-file
- 2 启动数据库:分别启动这些Redis数据库,可以用info cluster查看信息
- 3 连接节点:使用cluster meet,把所有的数据库都放到一个集群中,可以通过cluster info ,或者cluster nodes 查看信息
- 4 设置slave::设置部分数据库为slave,使用cluster replicate
- 5 分配插槽:使用cluster addSlots,这个命令目前只能一个一个加,如果要加区间的话,就得客户端编写代码来循环添加。有个实用的技巧:把所有的Redis停下来,然后直接修改node-xxx.conf 的配置文件,只需要配置master的数据库就可以,然后再重启数据库。分配完记得用使用cluster slots命令检查下
最后,通过cluster info查看集群信息,如果显示ok,那就可以使用了
插槽与预分区
插槽
插槽是Redis对Key进行分片的单元。在Redis的集群实现中 ,内置了数据自动分片机制,集群内部会将所有的key映射到16384个插槽中,集群中的每个数据库实例负责其中部分的插槽的读写。
键与插槽的关系:Redis会将key的有效部分,使用CRC16算法计算出散列值, 然后对16384取余数,从而把key分配到插槽中。
键名的有效部分规则是:如果键名包含{},那么有效部分就是{}中的值;否则就是取整个键名
可以看成是集群数据的逻辑划分,也是redis内存的逻辑划分
移动已分配的插槽,这个稍微麻烦点,尤其是有了数据过后,假设要迁移123号插槽从A 到B,大致步骤如下:
- 1:在B上执行cluster setslot 123 importing A
- 2:在A上执行cluster setslot 123 migrating B
- 3:在A上执行cluster getkeysinslot 123 要返回的数量
- 4:对上一步获取的每个键执行migrate命令,将其从A迁移到B
- 5:在集群中每个服务器上执行cluster setslot 123 node B (集群其实会自动同步,不过有延迟)
避免键的临时丢失:上面迁移方案中的前两步就是用来避免在移动已分配插槽过 程中,键的临时丢失问题的,大致思路如下
1 当前两步执行完成后,如果客户端向A请求插槽123中的键时,如果 键还未被转移,A将处理请求
2 如果键已经转移,则返回,把新的地址告诉客户端,客户端将发起 新的请求以获取数据
当客户端向某个数据库发起请求时,如果键不在这个数据库里面,将会返回一个move重定向的请求,里面包含新的地址,客户端收到这个信息后,需要重新发起请求到新的地址去获取数据。
当然,大部分的Redis客户端都会自动去重定向,也就是这个过程对开发人员是透明的。redis-cli也支持自动重定向,只需要在启动时加入 -c 的 参数。
预分区
为了实现在线动态扩容和数据分区,Redis的作者提出了预分区的方案,实际就是在同 一台机器上部署多个Redis实例,当容量不够时将多个实例拆分到不同的机器上,这样就达到了扩容的效果。
- 1:在新机器上启动好对应端口的Redis实例
- 2:配置新端口为待迁移端口的从库
- 3:待复制完成,与主库完成同步后,切换所有客户端配置到新的从库的端口
- 4:配置从库为新的主库
- 5:移除老的端口实例
- 6:重复上述过程把要迁移的数据库转移到指定服务器上
以上拆分流程是Redis作者提出的一个平滑迁移的过程,不过该拆分方法还是很依赖Redis本身的 复制功能的,如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。
集群故障恢复
故障判定
- 集群中每个节点都会定期向其他节点发出ping命令,如果没有收到回复,就 认为该节点为疑似下线,然后在集群中传播该信息
- 当集群中的某个节点,收到半数以上认为某节点已下线的信息,就会真的标 记该节点为已下线,并在集群中传播该信息
- 如果已下线的节点是master节点,那就意味着一部分插槽无法写入了
- 如果集群任意master挂掉,且当前master没有slave,集群进入fail状态
- 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态
- 当集群不可用时,所有对集群的操作做都不可用,客户端会收到The cluster is down错误信息
故障恢复
发现某个master下线后,集群会进行故障恢复操作,来将一个slave 变成master,基于Raft算法,大致步骤如下:
- 1:某个slave向集群中每个节点发送请求,要求选举自己为master
- 2:如果收到请求的节点没有选举过其他slave,会同意
- 3:当集群中有超过节点数一半的节点同意该slave的请求,则该Slave选举成功
- 4:如果有多个slave同时参选,可能会出现没有任何slave当选的情况,将会等 待一个随机时间,再次发出选举请求
- 5:选举成功后,slave会通过 slaveof no one命令把自己变成master
如果故障后还想集群继续工作,可设置cluster-require-full-coverage为 no,默认yes。
另外要弄清楚master挂掉了,重启还可以加入集群,只是变成Slave;但挂掉的slave重启,如果对应的master变化了,是不能加入集群 的,除非修改它们的配置文件,将其原master指向新master。
只要主从关系建立,就会触发主和该从采用save方式持久化数据, 不论你是否禁止save。
在集群中,如果默认主从关系的主挂了并立即重启,如果主没有做持久化,数据会完全丢失,从而从的数据也被清空。
建议恢复机制交给集群自己去处理,不要使用脚本来控制master的启停,操作不当会导致数据完全丢失
附录一:复制常用配置
关于复制一些基本操作命令:
- info replication :可以查看复制节点的相关信息(info是一个非常强大的命令,可以查看很多信息比如内存、cpu等)
- slaveof:可在运行期间修改slave节点的信息,如果该数据库已经是某个主数据库的从数据库,那么会停止和原主数据库的同步关系 ,转而和新的主数据库同步
- slaveof no one:使当前数据库停止与其他数据库的同步,转成主数据库,不影响它的slave节点
- replicaof :指定某一个redis作为另一个redis的从服务器,通过指定IP和端口来设置主redis
- masterauth:如果主redis设置了验证密码的话(使用requirepass来设置),则在从redis的配置中要使用masterauth来设置校验密码,否则的话,主redis会拒绝从redis的访问请求
- replica-read-only:设置从Redis为只读
- repl-ping-replica-period:设置从redis会向主redis发出PING包的周期,默认是10秒
- replica-serve-stale-data:设置当从redis失去了与主redis的连接,或者主从同步正在进行中时,redis该如何处理外部发来的访问请求,默认是yes
如果设置为yes(默认),则从redis仍会继续响应客户端的 请求。如果设置为no,则从redis会对客户端的请求返回“SYNC with master in progress”,当然也有例外,当客户端发来INFO 请求和SLAVEOF请求,从redis还是会进行处理。
- repl-timeout:设置主从同步的超时时间,要确保这个时限比 repl-ping-replica-period的值要大,否则每次主redis都会认为从redis超时
- repl-disable-tcp-nodelay:设置在主从同步时是否禁用 TCP_NODELAY,如果开启,那么主redis会使用更少的TCP包和更少的带宽来向从redis传输数据。但是这可能会增加一些同步的延迟 ,大概会达到40毫秒左右。如果关闭,那么数据同步的延迟时间会降低,但是会消耗更多的带宽
- repl-backlog-size:设置同步队列长度。队列长度(backlog)是 主redis中的一个缓冲区,在与从redis断开连接期间,主redis会用这个缓冲区来缓存应该发给从redis的数据。这样的话,当从redis重新连接上之后,就不必重新全量同步数据,只需要同步这部分增量数据即可。默认是1M,可根据实际业务情况进行调整。
- repl-backlog-ttl:设置主redis要等待的时间长度,如果主redis 等了这么长时间之后,还是无法连接到从redis,那么缓冲队列中的数据将被清理掉。设置为0,则表示永远不清理。默认是1个小时,应该足够了。
- replica-priority:设置从redis优先级,在主redis持续工作不 正常的情况,优先级高的从redis将会升级为主redis。而编号越小 ,优先级越高。当优先级被设置为0时,这个从redis将永远也不会 被选中。默认的优先级为100。
- min-replicas-to-write:设置执行写操作所需的最少从服务器数量,如果至少有这么多个从服务器, 并且这些服务器的延迟值都 少于 min-replicas-max-lag 秒, 那么主服务器就会执行客户端 请求的写操作,该配置保证其高可用性,默认是0
- min-replicas-max-lag:设置最大连接延迟的时间, min-replicas-to-write和min-replicas-max-lag中有一个被置为0,则这个特性将被关闭。默认min-replicas-max-lag为10
- repl-diskless-sync:是否开启无盘复制,通过网络完成复制而不是RDB文件,默认是no
- repl-diskless-sync-delay:设置无盘复制延时开始秒数,默认是5秒 ,意思是当PSYNC触发的时候,master延时多少秒开始向slave传送数 据流,以便等待更多的slave连接可以同时传送数据流,因为一旦 PSYNC开始后,如果有新的slave连接master,只能等待下次PSYNC。可以配置为0取消等待,立即开始
- repl-diskless-load:是否使用无磁盘加载,这是个实验性的功能,知道下就可以
附录二:哨兵常用配置
- bind:服务监听地址,用于客户端连接,默认本机地址
- protected-mode:安全保护模式
- port:监听的端口号
- daemonize:是否以后台daemon方式运行
- pidfile:pid文件位置
- logfile:log文件位置
- dir:工作目录
- sentinel monitor :设置要监控的 master服务器
- sentinel auth-pass :连接master服务的密码
- sentinel down-after-milliseconds :指定多少毫秒之后,主节点没有应答哨兵,此时哨兵主观上认为主节点下线
- sentinel parallel-syncs :表示允许并行同步的 slave个数,当Master挂了后,哨兵会选出新的Master,此时剩余的slave会向新的master发起同步数据
- sentinel failover-timeout :故障转移的超时时间,进行故障转移时,如果超过设置的毫秒,表示故障转移失败
- sentinel notification-script
:配置当某一事件发生时所需要执行的脚本 - sentinel client-reconfig-script
:客户 端重新配置主节点参数脚本
附录三:常见集群操作命令
- CLUSTER INFO:获取集群的信息
- CLUSTER NODES:获取集群当前已知的所有节点,以及这些节点的相关信息
- CLUSTER MEET
:将ip和port所指定的节点添加到集群当中 - CLUSTER FORGET
:从集群中移除 node_id 指定的节点 - CLUSTER REPLICATE
:将当前节点设置为 node_id 指定 的节点的从节点 - CLUSTER SAVECONFIG:将节点的配置文件保存到硬盘里面
- CLUSTER ADDSLOTS
[slot ...]:将一个或多个槽分配给当 前节点 - CLUSTER DELSLOTS
[slot ...]:从当前节点移除一个或多 个槽 - CLUSTER FLUSHSLOTS:移除分配给当前节点的所有槽
- CLUSTER SETSLOT
NODE :将槽分配 给 node_id 指定的节点,如果槽已经分配给另一个节点,那么先 让另一个节点删除该槽>,然后再进行分配 - CLUSTER SETSLOT
MIGRATING :将本节点的槽 迁移到指定的节点中 - CLUSTER SETSLOT
IMPORTING :从指定节点导 入槽到本节点 - CLUSTER SETSLOT
STABLE :取消对槽的导入(import)或 迁移(migrate) - CLUSTER KEYSLOT
:计算键 key 应该被放置在哪个槽 - CLUSTER COUNTKEYSINSLOT
:返回槽目前包含的键值对数 量 - CLUSTER GETKEYSINSLOT
:返回count个槽中的键 - migrate 目的节点ip 目的节点port 键名 数据库号码 超时时间 [copy] [replace]:迁移某个键值对
附录四:常见集群配置
- cluster-enabled :是否开启集群模式
- cluster-config-file: 集群配置文件,由Redis集群节点自动维护每次配置的改变
- cluster-node-timeout:超时时间,集群节点不可用的最大时间
- cluster-replica-validity-factor:集群副本有效因子,如果发生故障的Master数据看起来太旧,则其副本将避免进行故障转移
如果设置为0,则slave将总是尝试故障转移。如果设置为一个正数,那么最大失去连接的时间是node timeout乘以这个factor -
cluster-migration-barrier:迁移屏障,一个master最少要有多少个slave的数量,才允许数据迁移
- cluster-require-full-coverage:集群需要全覆盖,故障后是否继续运行
- cluster-replica-no-failover:集群副本无故障转移,此选项设置为yes时,可防止从设备尝试对其进行故障转移
- cluster-allow-reads-when-down:是否允许集群在宕机时读取
附录五:使用命令脚本管理集群
Redis Cluster 在5.0之后取消了ruby脚本 redis-trib.rb 的支持,集成到redis-cli里,直接使用redis-clit的参数-- cluster 来取代
查看命令:redis-cli --cluster help
- create:创建集群,如果指定slave数量,集群会根据redis实例自行划分,但是master和slave配对关系是自由组合的;一般不要指定slave数量,采用add-node方式添加节点,手动指定master和salve的对应关系
- check:检查集群状态
- info:查看集群信息
- fix:修复或恢复集群
- reshard:重分片,插槽迁移
- rebalance:平衡集群节点插槽
- add-node:集群中增加节点,可以指定master和slave对应关系,一般用在手动映射master和slave关系场景中
- del-node:集群中删除节点
- call:执行一些命令
- set-timeout:设置超时时间,单位毫秒
- import:导入数据到集群里来
- backup:备份