Redis 三种集群策略
前言
Redis 是单线程的,但是一般的作为缓存使用的话,速度已经足够使用。
官方有一个简单测试:测试完成 50 个并发执行 100000 个请求,设置和获取的值是一个 256 字节字符串。结果:读的速度 110000次/s,写的速度81000次/s。
不过对于访问量特别大的服务来说,还是稍有不足。这时就需要考虑搭建集群。
Redis 主要提供三种集群策略
-
主从复制
-
集群
-
哨兵
一、主从复制
在主从复制中,数据库分为俩类,主数据库(master)和从数据库(slave)。
主从复制特点:
-
主数据库可以进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库
-
从数据库一般都是只读,并且接收主数据库同步过来的数据
-
一个 Master 可以拥有多个 Slave,但是一个 Slave 只能对应一个 Master
工作机制
-
Slave 从节点服务启动并连接到 Master 之后,主动发送一个 SYNC 命令。Master 服务主节点收到同步命令后将启动后台存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后,Master 将传送整个数据库文件到 Slave,以完成一次完全同步。而 Slave 从节点服务在接收到数据库文件数据之后将其存盘并加载到内存中。此后,Master 主节点继续将所有已经收集到的修改命令,和新的修改命令依次传送给 Slave,Slave 将在本次执行这些数据修改命令,从而达到最终的数据同步。
-
复制初始化后,Master 每次接收到的写命令都会同步发送给 Slave,保证主从数据一致性。
-
如果 Master 和 Slave 之间的链接出现断连现象,Slave 可以自动重连 Master,但是在连接成功之后,一次完全同步将被自动执行。
主从配置
Redis 默认是主数据,所以 Master 无需配置,只需要修改 Slave 的配置即可。
设置需要连接的 Master 的ip端口
slaveof 192.168.0.107 6379
如果 Master 设置了密码。需要配置:
masterauth master-password
连接成功进入命令行后,可以通过以下命令行查看连接该数据库的其他库信息:
info replication
优点
-
同一个 Master 可以同步多个 Slaves。
-
Slave 同样可以接受其它 Slaves 的连接和同步请求,这样可以有效的分载 Master 同步压力。因此我们可以将 Redis 的 Replication 架构视为图结构。
-
Master Server是 以非阻塞的方式为 Slaves 提供服务。所以在 Master-Slave 同步期间,客户端仍然可以提交查询或修改请求。
-
Slave Server 同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis 则返回同步之前的数据
-
为了分载 Master 的读操作压力,Slave 服务器可以为客户端提供只读操作的服务,写服务仍然必须由 Master 来完成。即便如此,系统的伸缩性还是得到了很大的提高。
-
Master可 以将数据保存操作交给 Slaves 完成,从而避免了在Master中要有独立的进程来完成此操作。
-
支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
缺点
-
Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的 IP 才能恢复。
-
主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP 后还会引入数据不一致的问题,降低了系统的可用性。
- Redis 的主从复制采用全量复制,复制过程中主机会 fork 出一个子进程对内存做一份快照,并将子进程的内存快照保存为文件发送给从机,这一过程需要确保主机有足够多的空余内存。若快照文件较大,对集群的服务能力会产生较大的影响,而且复制过程是在从机新加入集群或者从机和主机网络断开重连时都会进行,也就是网络波动都会造成主机和从机间的一次全量的数据复制,这对实际的系统运营造成了不小的麻烦。
- Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
- 其实 Redis 的主从模式很简单,在实际的生产环境中是很少使用的,我也不建议在实际的生产环境中使用主从模式来提供系统的高可用性,之所以不建议使用都是由它的缺点造成的,在数据量非常大的情况,或者对系统的高可用性要求很高的情况下,主从模式也是不稳定的。
二、哨兵
该模式是从 Redis 的 2.6 版本开始提供的,但是当时这个版本的模式是不稳定的,直到 Redis 的 2.8 版本以后,这个哨兵模式才稳定下来,无论是主从模式,还是哨兵模式,这两个模式都有一个问题,不能水平扩容,并且这两个模式的高可用特性都会受到Master主节点内存的限制。
哨兵的作用
监控 Redis 系统的运行状况,功能如下
-
监控主从数据库是否正常运行。
-
master 出现故障时,自动将它的其中一个 slave 转化为 master。
-
master 和 slave 服务器切换后,master 的 redis.conf、slave 的 redis.conf 和 sentinel.conf 的配置文件的内容都会发生相应的改变,即 master主服务器的 redis.conf 配置文件中会多一行 slaveof 的配置,sentinel.conf 的监控目标会随之调换。
-
当被监控的某个 Redis 节点出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
-
多哨兵配置的时候,哨兵之间也会自动监控。
-
多个哨兵可以监控同一个 Redis 。
工作机制
-
哨兵进程启动时会读取配置文件的内容,通过
sentinel monitor master-name ip port quorum
查找到master的ip端口。一个哨兵可以监控多个master数据库,只需要提供多个该配置项即可。 -
配置文件还定义了与监控相关的参数,比如master多长时间无响应即即判定位为下线。另外,搜索公众号技术社区后台回复“思维”,获取一份惊喜礼包。
-
哨兵启动后,会与要监控的master建立俩条连接:
3.1 一条连接用来订阅master的sentinel:hello频道,并获取其他监控该master的哨兵节点信息
3.2 另一条连接定期向master发送INFO等命令获取master本身的信息 -
与master建立连接后,哨兵会执行三个操作,这三个操作的发送频率都可以在配置文件中配置:
4.1 定期向master和slave发送INFO命令
4.2 定期向master和slave的sentinel:hello频道发送自己的信息
4.3 定期向master、slave和其他哨兵发送PING命令
这三个操作的意义非常重大,发送INFO命令可以获取当前数据库的相关信息从而实现新节点的自动发现。所以说哨兵只需要配置master数据库信息就可以自动发现其slave信息。获取到slave信息后,哨兵也会与slave建立俩条连接执行监控。通过INFO命令,哨兵可以获取主从数据库的最新信息,并进行相应的操作,比如角色变更等。 -
接下来哨兵向主从数据库的sentinel:hello频道发送信息,并与同样监控这些数据库的哨兵共享自己的信息,发送内容为哨兵的ip端口、运行id、配置版本、master名字、master的ip端口还有master的配置版本。这些信息有以下用处:
5.1 其他哨兵可以通过该信息判断发送者是否是新发现的哨兵,如果是的话会创建一个到该哨兵的连接用于发送ping命令。
5.2 其他哨兵通过该信息可以判断master的版本,如果该版本高于直接记录的版本,将会更新 -
当实现了自动发现slave和其他哨兵节点后,哨兵就可以通过定期发送ping命令定时监控这些数据库和节点有没有停止服务。发送频率可以配置,但是最长间隔时间为1s,可以通过
sentinel down-after-milliseconds mymaster 600
设置。 -
如果被ping的数据库或者节点超时未回复,哨兵认为其主观下线。如果下线的是master,哨兵会向其他哨兵点发送命令询问他们是否也认为该master主观下线。如果一个master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态。如果达到一定数目(即配置文件中的quorum)投票,哨兵会认为该master已经客观下线(ODOWN),并选举领头的哨兵节点对主从系统发起故障恢复。
-
如上文所说,哨兵认为master客观下线后,故障恢复的操作需要由选举的领头哨兵执行,选举采用Raft算法:
8.1 发现master下线的哨兵节点(我们称他为A)向每个哨兵发送命令,要求对方选自己为领头哨兵
8.2 如果目标哨兵节点没有选过其他人,则会同意选举A为领头哨兵
8.3 如果有超过一半的哨兵同意选举A为领头,则A当选
8.4 如果有多个哨兵节点同时参选领头,此时有可能存在一轮投票无竞选者胜出,此时每个参选的节点等待一个随机时间后再次发起参选请求,进行下一轮投票精选,直至选举出领头哨兵
8.5 选出领头哨兵后,领头者开始对进行故障恢复,从出现故障的master的从数据库slave中挑选一个来当选新的master,选择规则如下:
8.5.1 所有在线的slave中选择优先级最高的,优先级可以通过slave-priority配置
8.5.2 如果有多个最高优先级的slave,则选取复制偏移量最大(即复制越完整)的当选
8.5.3 如果以上条件都一样,选取id最小的slave -
挑选出需要继任的slaver后,领头哨兵向该数据库发送命令使其升格为master,然后再向其他slave发送命令接受新的master,最后更新数据。将已经停止的旧的master更新为新的master的从数据库,使其恢复服务后以slave的身份继续运行。
哨兵配置
哨兵配置的配置文件为sentinel.conf,设置主机名称,地址,端口,以及选举票数即恢复时最少需要几个哨兵节点同意。只要配置需要监控的master就可以了,哨兵会监控连接该master的slave。
sentinel monitor mymaster 192.168.0.107 6379 1
启动哨兵节点
redis-server sentinel.conf --sentinel & ''' 出现以下类似信息即启动哨兵成功 3072:X 12 Apr 22:40:02.554 ### Sentinel runid is e510bd95d4deba3261de72272130322b2ba650e7 3072:X 12 Apr 22:40:02.554 ### +monitor master mymaster 192.168.0.107 6379 quorum 1 3072:X 12 Apr 22:40:03.516 * +slave slave 192.168.0.108:6379 192.168.0.108 6379 @ mymaster 192.168.0.107 6379 3072:X 12 Apr 22:40:03.516 * +slave slave 192.168.0.109:6379 192.168.0.109 6379 @ mymaster 192.168.0.107 6379 '''
可以在任何一台服务器上查看指定哨兵节点信息:
bin/redis-cli -h 192.168.0.110 -p 26379 info Sentinel
控制台输出哨兵信息
redis-cli -h 192.168.0.110 -p 26379 info Sentinel
三、集群
特点
3.0 版本之前的 Redis 是不支持集群的,那个时候,Redis 如果想要集群的话,就需要一个中间件,然后这个中间件负责将需要存入 Redis 中的数据的 key 通过一套算法计算得出一个值。然后根据这个值找到对应的 Redis 节点,将这些数据存在这个 Redis 的节点中。在取值的时候,同样先将 key 进行计算,得到对应的值,然后就去找对应的 Redis 节点,从对应的节点中取出对应的值。
这样做有很多不好的地方,比如说计算都需要在系统中去进行,会增加系统的负担。还有就是这种集群模式下,某个节点挂掉,其他的节点无法知道。而且也不容易对每个节点进行负载均衡。
从 Redis 3.0版本开始支持 redis-cluster 集群。redis-cluster 采用无中心结构,每一个节点都保存有这个集群所有主节点以及从节点的信息,及集群状态,每个节点都和其他节点连接。所以 redis-cluster 是一种服务端分片技术。
-
每个节点都和 n-1 个节点通信,被称为集群总线(cluster bus)。它们使用特殊的端口号,即对外服务端口号加 10000。要维护好这个集群的每个节点信息,不然会导致整个集群不可用,其内部采用特殊的二进制协议优化传输速度和带宽。
-
redis-cluster 把所有的物理节点映射到 [0,16383]slot(槽)上,cluster 负责维护 node--slot--value。
-
集群预先给所有节点分好 16384 个桶,每个节点得到部分桶,当需要在 Redis 集群中插入数据时,根据 CRC16(KEY) mod 16384 的值,决定将一个 key放 到哪个桶中。
-
客户端与 Redis 节点直连,不需要连接集群所有的节点,连接集群中任何一个可用节点即可。整个 cluster 被看做是一个整体,客户端可连接任意一个节点进行操作,当客户端操作的 key 没有分配在该节点上时,Redis 会返回转向指令,指向正确的节点。
-
redis-trib.rb 脚本(rub 语言)为集群的管理工具,比如自动添加节点,规划槽位,迁移数据等一系列操作。
-
节点的 fail 是通过集群中超过半数的节点检测失效时才生效。集群节点之间通过互相的 ping-pong 判断是否可以连接上。如果有一半以上的节点去 ping 一个节点的时候没有回应,集群就认为这个节点宕机,然后去连接它的备用节点。如果某个节点和所有从节点全部挂掉,集群就进入 fail 状态,也可以理解成集群的 slot 映射 [0-16383] 不完整时进入 fail 状态。如果有一半以上的主节点宕机,那么无论这些节点有没有从节点,集群同样进入fail状态。这就是redis的投票机制。
-
为了增加集群的可访问性,官方推荐的方案是将 node 配置成主从结构,即一个 master 主节点,挂 n 个 slave 从节点。如果主节点失效,redis cluster 会根据选举算法从 slave 节点中选择一个上升为 master 节点,整个集群继续对外提供服务。
配置
-
Redis 集群依赖 ruby,需安装 ruby 环境,ruby 版本需高于 2.2
yum install ruby yum install rubygems gem install redis
-
修改配置文件
bind 192.168.0.107 ### 配置端口 port 6380 ### 配置快照保存路径,6个节点配置不同路径 dir /usr/local/redis-cluster/6380/ ### 开启集群 cluster-enabled yes ### 为节点设置不同的工作目录,6个节点配置不同目录 cluster-config-file nodes-6380.conf ### 集群失效时间 cluster-node-timeout 15000
- 开启集群中的所有节点
redis-service …/6380/redis.conf redis-service …/6381/redis.conf redis-service …/6382/redis.conf redis-service …/6383/redis.conf redis-service …/6384/redis.conf redis-service …/6385/redis.conf
- 节点加入集群中,中途需要输入 yes 确定创建集群
redis-trib.rb create --replicas 1 192.168.0.107:6380 192.168.0.107:6381 192.168.0.107:6382 192.168.0.107:6383 192.168.0.107:6384 192.168.0.107:6385
- 进入集群中任何一个节点
redis-cli -c -h 192.168.0.107 -p 6381
- 查看集群中的节点
cluster nodes
- 增加集群节点
cluster meet ip port
其他集群实现方式
一、twemproxy 中间件
twemproxy 又称 nutcracker,起源于推特系统中 redis、memcached 集群的轻量级代理。
Redis 代理中间件 twemproxy 是一种利用中间件做分片的技术。twemproxy 处于客户端和服务器的中间,将客户端发来的请求,进行一定的处理后(sharding),再转发给后端真正的 redis 服务器。也就是说,客户端不直接访问redis服务器,而是通过t wemproxy 代理中间件间接访问。降低了客户端直连后端服务器的连接数量,并且支持服务器集群水平扩展。
twemproxy 中间件的内部处理是无状态的,它本身可以很轻松地集群,这样可以避免单点压力或故障。
twemproxy 是一个单点,很容易对其造成很大的压力,所以通常会结合 keepalived 来实现 twemproy 的高可用。这时,通常只有一台 twemproxy 在工作,另外一台处于备机,当一台挂掉以后,vip 自动漂移,备机接替工作。关于 keepalived 的用法可自行网上查阅资料。
二、codis 中间件
codis 是一个分布式的 Redis 解决方案,由豌豆荚开源,对于上层的应用来说,连接 codis proxy 和连接原生的 redis server 没什么明显的区别,上层应用可以像使用单机的 redis 一样使用,codis底 层会处理请求的转发,不停机的数据迁移等工作,所有后边的事情,对于前面的客户端来说是透明的,可以简单的认为后边连接的是一个内存无限大的 redis 服务。
客户端分片
分区的逻辑在客户端实现,由客户端自己选择请求到哪个节点。方案可参考一致性哈希,这种方案通常适用于用户对客户端的行为有完全控制能力的场景。
三、Jedis sharding集群
Redis Sharding 可以说是在 Redis cluster 出来之前业界普遍的采用方式,其主要思想是采用 hash 算法将存储数据的 key 进行 hash 散列,这样特定的 key 会被定为到特定的节点上。
庆幸的是,Java Redis 客户端驱动 Jedis 已支持 Redis Sharding 功能,即 ShardedJedis 以及结合缓存池的 ShardedJedisPool。
Jedis 的 Redis Sharding 实现具有如下特点:
-
采用一致性哈希算法,将 key 和节点 name 同时 hashing,然后进行映射匹配,采用的算法是 MURMUR_HASH。采用一致性哈希而不是采用简单类似哈希求模映射的主要原因是当增加或减少节点时,不会产生由于重新匹配造成的r ehashing。一致性哈希只影响相邻节点 key 分配,影响量小。
-
为了避免一致性哈希只影响相邻节点造成节点分配压力,ShardedJedis 会对每个 Redis 节点根据名字(没有,Jedis 会赋予缺省名字)会虚拟化出 160个 虚拟节点进行散列。根据权重weight,也可虚拟化出 160倍 数的虚拟节点。用虚拟节点做映射匹配,可以在增加或减少 Redis 节点时,key 在各 Redis 节点移动再分配更均匀,而不是只有相邻节点受影响。
-
ShardedJedis 支持 keyTagPattern 模式,即抽取 key 的一部分 keyTag 做 sharding,这样通过合理命名 key,可以将一组相关联的 key 放入同一个 Redis 节点,这在避免跨节点访问相关数据时很重要。
-
当然,Redis Sharding这 种轻量灵活方式必然在集群其它能力方面做出妥协。比如扩容,当想要增加 Redis 节点时,尽管采用一致性哈希,毕竟还是会有 key 匹配不到而丢失,这时需要键值迁移。
-
作为轻量级客户端 sharding,处理 Redis 键值迁移是不现实的,这就要求应用层面允许 Redis 中数据丢失或从后端数据库重新加载数据。但有些时候,击穿缓存层,直接访问数据库层,会对系统访问造成很大压力。