#Redis分布式集群知识
随着应用数据越来越大和,对性能的要求越来越高,单机模式很难满足日渐多样化的需求,所以Redis也提供了分布式集群的部署方案,满足当下高并发,高可靠的需求。
Redis是单线程的,集群不仅能更好的利用CPU的资源,还能提高对应用层的高可用,分布式集群还能避免单个Redis发生故障导致整个缓存的崩溃,引起应用的故障。
##Redis集群的基础
Redis本身的一些设计,为Redis的集群打下了坚实的基础。
Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成地数据丢失,当下次重启时利用之前持久化地文件即可实现数据地恢复。
RDB持久化是将当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发。
触发机制
手动触发分别对应save和bgsave命令。
bgsave命令明显是对save阻塞问题的优化,因此Redis内部所有的涉及RDB的操作都采用bgsave的方式,而save命令已经废弃。
Redis自动触发的一些场景:
流程说明
bgsave是主流的触发RDB持久化方式,大体的流程如下:
RDB文件保存在dir配置指定的目录下,文件名通过dbfilename配置指定。可以通过执行config set dir {newDir}和config set dbfilename {newFileName}运行期动态执行,当下次运行时RDB文件会保存到新目录。
优缺点
开启AOF需要设置配置:appendonly yes (默认不开启),AOF文件名可以通过appendfilename配置设置(默认文件名是appendonly.aof)。
触发机制
当开启AOF时,每次命令写入时都会进行AOF持久化。
流程说明
AOF工作流程分为四个操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)和重启加载(load)
随着命令的不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF文件重写就是Redis把进程内的数据转化为写命令同步到新的AOF文件。重写后的AOF文件变小了,因为在重新过程中,抛弃了超时的数据,无效的命令;多条写命令合并成一个。
重写过程触发:手动触发(调用bgrewriteaof命令)和自动触发(根据auto-aof-rewrite-min-size(默认64MB)和auto-aof-rewrite-percentage参数确定自动触发时机)
AOF会在fork操作和AOF追加到持久化时消耗大量性能造成阻塞,针对fork操作我们可以控制Redis实例的最大内存,使用对fork操作高效支持的机器等优化,AOF追加合理控制每次持久化数据的大小。
Redis为我们提供了复制功能,实现了相同数据的多个Redis副本。
参与复制的Redis实例划分为主节点(master)和从节点(slave)。默认情况下,Redis都是主节点。每个从节点只能有一个主节点,而主节点可以同时具有多个从节点。默认情况下,为了保证主从数据一致性,从节点使用slave-read-only=yes配置为只读模式,如果主节点设置requirepass参数进行密码验证,那么从节点的masterauth参数与主机点密码保持一致。
配置复制的方式有以下三种:
断开复制:在从节点执行slaveof no one,先断开与主节点的复制关系,再将从节点晋升为主节点。
切换主节点:断开与旧主节点的复制关系;与新节点建立复制关系;删除从节点当前所有的数据;对新主节点进行复制操作。
传输延迟:主从节点一般布置在不同的机器上,复制时网络延迟就成为需要考虑的问题,Redis通过参数repl-disable-tcp-nodelay用于控制是否关闭TCP_NODELAY,默认关闭。关闭时主机点产生的命令都会实时发送给子节点,主从延迟小,但增加了网络开销;开启时,主节点会合并较小的tcp数据包从而节省宽带,但增加了主从延迟,适用于主从网络环境复杂或宽带紧张的场景,如跨机房部署。
Redis的复制拓扑可以支持单层或者多层复制,根据拓扑复杂性可以分为一主一从,一主多从,树状主从三种。
在从节点执行slaveof命令后,复制过程便开始运作,整个流程可以大致分为6个部分,如图所示:
数据复制
运维过程中我们应该尽量避免全量复制和过多从节点对主节点产生的复制风暴。
心跳
主从节点在建立复制后,它们之间维护着长连接并彼此发送心跳命令。主节点通过ping命令判断从节点的存活性和连接状态;从节点通过上报自身的复制偏移量检查数据是否丢失,如果丢失则从主节点缓冲区拉取,较少从节点的数据延迟。
##Redis集群
Redis目前已经提供我们两种方式进行集群部署:Redis Sentinel 和 Redis Cluster
Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为直接点,同时还要通知应用方主节点的变化,这种故障处理方式在很多应用场景下是无法接受的,所以Redis从2.8版本开始提供Redis Sentinel(哨兵)架构来解决这个问题。当主节点发生故障时,Redis Sentinel 能自动完成故障发现和故障转移,并通知业务方,从而实现真正的高可用
ps: Redis Sentinel的分布式是指:Redis数据节点、Sentinel节点集合、客户端分布在 多个物理节点的架构,跟Redis Cluster分布式不同,Redis Cluster是不仅服务分布式的,数据也是分布式的。Redis Sentinel通过自动完成故障发现和故障转移,实现高可用。注意分清两者的区别。
部署拓扑如下:
redis-sentinel redis-sentinel-26379.conf
或 redis-server redis-sentinel-26379.conf --sentinel
,Sentinel本质上是一种特殊的Redis节点。当Sentinel启动后,发现主节点master,发现了它的两个从节点,同时发现Redis Sentinel一共有三个节点(Redis Sentinel能彼此感知,也能感知到Redis节点)。三个定时任务监控
主观下线和客观下线
每个Sentinel节点会每隔1秒对主节点、从节点、其他Sentinel节点发送ping命令做心跳检测,当这些节点超过down-after-milliseconds
没有进行有效回复,Sentinel节点就会对该节点做失败判定,这个行为叫做主观下线。
当Sentinel主观下线的节点是主节点时,该Sentinel节点会通过sentinel is-master-down-by-addr
命令向其他Sentinel节点询问对主节点的判断,当超过
个数,Sentinel节点认为主节点确实有问题,这时该Sentinel节点会做出客观下线的决定。
领导者Sentinel节点选举
当Sentinel节点对于主节点做出客观下线后,Sentinel节点之间选出一个Sentinel节点作为领导者进行故障转移的工作。Redis Sentinel的选举思路大致如下:
sentinel is-master-down-by-addr
命令,要求将自己设为领导者。sentinel is-master-down-by-addr
命令,将同意该请求,否则拒绝。故障转移
领导者选举出的Sentinel节点负责故障转移,具体步骤如下:
Sentinel节点集合具备了监控、通知、自动故障转移、配置提供者若干功能,最为了解Redis节点信息,所有客户端直接连接Redis Sentinel实现对Redis的访问。一个Redis Sentinel客户端基本上要实现:
sentinel get-master-addr-by-name master-name
这个API来获取对应主节点的相关信息。ps :Java操作Redis Sentinel的客户端Jedis
Redis Cluster是Redis的分布式解决方案,在3.0版本正式推出,有效地解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构方案达到负载均衡的目的。
分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集。Redis Cluster的分布式也是采用这样的理论,且Redis Cluster采用哈希分区规则——虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式:slot=CRC16(key)&16383。每一个节点负责维护一部分槽以及槽所映射的键值数据。
Redis集群相对单机在功能上存在一些限制:
Redis Cluster集群搭建分为三个步骤:
cluster-enabled yes
,让Redis运行在集群模式下。集群模式的Redis除了原有的配置文件之外又加了一份集群配置文件。当集群内节点信息发生变化,如添加节点、节点下线、故障转移等。节点会自动保存集群状态到配置文件中。cluster addslots
命令为节点分配槽。Reids节点角色分为主节点和从节点。首次启动的节点和被分配槽的节点都是主节点,从节点负责复制主节点槽信息和相关的数据。使用cluster replicate {nodeId}
命令让一个节点成为从节点。我们可以使用redis-trib.rb帮助我们搭建集群:redis-trib.rb是采用Ruby实现的Redis集群管理工具。内部通过Cluster相关命令帮我们简化集群创建、检查、槽迁移和均衡等常见运维操作,使用之前需要安装Ruby依赖环境。
Redis Cluster集群包含集群伸缩、请求路由和故障转移三个方面。Redis集群数据分区规则采用虚拟槽方式,所有的键映射到16384个槽中,每个节点负责一部分槽和相关数据,实现数据和请求的负载均衡。
Redis集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。
集群扩容:
cluster setslot {slot} importing {sourceNodeId}
命令,让目标节点准备导入槽的数据。cluster setslot {slot} migrating {targetNodeId}
命令,让源节点准备迁出槽的数据。cluster getkeysinslot {slot} {count}
命令,获取count个属于槽{slot}的键。cluster setslot {slot} node {targetNodeId}
命令,通知槽分配给目标节点。ps:使用redis-trib.rb可以快速帮助我们完成集群收缩
集群收缩:
收缩集群意味着缩减规模,需要从现有集群中安全下线部分节点。
1. 首先需要确定下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性。收缩正好和扩容迁移方向相反。
2. 当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭。Redis提供了cluster forget {downNodeId}
命令实现该功能。
熟练掌握集群伸缩技巧后,可以针对线上的数据规模和并发量做到从容应对。
客户端去通过请求路由去操作集群。
ps :集群环境下对于使用批量操作的场景,建议优先使用Pipeline方式,在客户端实现对ASK重定向的正确处理,这样既可以受益于批量操作的IO优化,又可以兼容slot迁移场景。
Redis集群自身实现了高可用。高可用首先需要解决集群部分失败的场景:当集群内少量节点出现故障时通过自动故障转移保证集群可以正常对外提供服务。
故障发现:
Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。故障发现也是通过消息传播机制实现的,主要环节包括:主观下线(pfail)和客观下线(fail)。
故障恢复:
故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它的从节点中选出一个替换它,从而保证集群的高可用。流程如下:
为了保证集群完整性,默认情况下当集群16384个槽任何一个没有指派到节点时整个集群不可用。这是对集群完整性的一种保护措施,保证所有的槽都指派给在线的节点。但是当持有槽的主节点下线时,从故障发现到自动完成转移期间整个集群是不可用状态,这样是很多应用服务不可接受的,所以将参数cluster-require-full-coverage配置为no,当主节点故障时只影响它负责槽的相关命令执行,不会影响其他主节点的可用性。