java 培训 redis 的集群策略

redis

redis 是单线程的,但是一般的作为缓存使用的话,redis 足够了,因为它的读写速度太快了。官方的一个简单测试:测试完成了 50 个并发执行 100000 个请求。设置和获取的值是一个 256 字节字符串。结果:读的速度是 110000 次/s,写的速度是 81000 次/s 但对于访问量特别大的服务来说,还是稍有不足。那么,如何提升 redis 的性能呢?搭建集群。

redis 主要提供三种集群策略:

  1. 主从复制

  2. 集群

  3. 哨兵

一、主从复制

在主从复制中,数据库分为俩类,主数据库(master)和从数据库(slave)。

1.1 主从复制有如下特点:

  1. 主数据库可以进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库

  2. 从数据库一般都是只读的,并且接收主数据库同步过来的数据

  3. 一个 master 可以拥有多个 slave,但是一个 slave 只能对应一个 master

1.2 工作机制

  1. slave 从节点服务启动并连接到 Master 之后,它将主动发送一个 SYNC 命令。Master 服务主节点收到同步命令后将启动后台存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后,Master 将传送整个数据库文件到 Slave,以完成一次完全同步。而 Slave 从节点服务在接收到数据库文件数据之后将其存盘并加载到内存中。此后,Master 主节点继续将所有已经收集到的修改命令,和新的修改命令依次传送给 Slaves,Slave 将在本次执行这些数据修改命令,从而达到最终的数据同步。

  2. 复制初始化后,master 每次接收到的写命令都会同步发送给 slave,保证主从数据一致性。

  3. 如果 Master 和 Slave 之间的链接出现断连现象,Slave 可以自动重连 Master,但是在连接成功之后,一次完全同步将被自动执行。

1.3 主从配置

redis 默认是主数据,所以 master 无需配置,我们只需要修改 slave 的配置即可。设置需要连接的 master 的 ip 端口:slaveof 192.168.0.107 6379如果 master 设置了密码。需要配置:masterauth master-password连接成功进入命令行后,可以通过以下命令行查看连接该数据库的其他库信息:info replication

1.3 优点

  1. 同一个 Master 可以同步多个 Slaves。

  2. Slave 同样可以接受其它 Slaves 的连接和同步请求,这样可以有效的分载 Master 的同步压力。因此我们可以将 Redis 的 Replication 架构视为图结构。

  3. Master Server 是以非阻塞的方式为 Slaves 提供服务。所以在 Master-Slave 同步期间,客户端仍然可以提交查询或修改请求。

  4. Slave Server 同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis 则返回同步之前的数据

  5. 为了分载 Master 的读操作压力,Slave 服务器可以为客户端提供只读操作的服务,写服务仍然必须由 Master 来完成。即便如此,系统的伸缩性还是得到了很大的提高。

  6. Master 可以将数据保存操作交给 Slaves 完成,从而避免了在 Master 中要有独立的进程来完成此操作。

  7. 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。

1.4 缺点

  1. Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的 IP 才能恢复。

  2. 主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP 后还会引入数据不一致的问题,降低了系统的可用性。

  3. Redis 的主从复制采用全量复制,复制过程中主机会 fork 出一个子进程对内存做一份快照,并将子进程的内存快照保存为文件发送给从机,这一过程需要确保主机有足够多的空余内存。若快照文件较大,对集群的服务能力会产生较大的影响,而且复制过程是在从机新加入集群或者从机和主机网络断开重连时都会进行,也就是网络波动都会造成主机和从机间的一次全量的数据复制,这对实际的系统运营造成了不小的麻烦。

  4. Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。

  5. 其实 redis 的主从模式很简单,在实际的生产环境中是很少使用的,我也不建议在实际的生产环境中使用主从模式来提供系统的高可用性,之所以不建议使用都是由它的缺点造成的,在数据量非常大的情况,或者对系统的高可用性要求很高的情况下,主从模式也是不稳定的。

二、哨兵

该模式是从 Redis 的 2.6 版本开始提供的,但是当时这个版本的模式是不稳定的,直到 Redis 的 2.8 版本以后,这个哨兵模式才稳定下来,无论是主从模式,还是哨兵模式,这两个模式都有一个问题,不能水平扩容,并且这两个模式的高可用特性都会受到 Master 主节点内存的限制_java培训

2.1 哨兵的作用是监控 redis 系统的运行状况,功能如下

  1. 监控主从数据库是否正常运行。

  2. master 出现故障时,自动将它的其中一个 slave 转化为 master。

  3. master 和 slave 服务器切换后,master 的 redis.conf、slave 的 redis.conf 和 sentinel.conf 的配置文件的内容都会发生相应的改变,即,saster 主服务器的 redis.conf 配置文件中会多一行 slaveof 的配置,sentinel.conf 的监控目标会随之调换。

  4. 当被监控的某个 Redis 节点出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。

  5. 多哨兵配置的时候,哨兵之间也会自动监控。

  6. 多个哨兵可以监控同一个 redis。

2.2 哨兵工作机制

哨兵进程启动时会读取配置文件的内容,通过 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 的身份继续运行。

2.3 哨兵配置

哨兵配置的配置文件为 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

### Sentinel

sentinel_masters:1

sentinel_tilt:0

sentinel_running_scripts:0

sentinel_scripts_queue_length:0

master0:name=mymaster,status=ok,address=192.168.0.107:6379,slaves=2,sentinels=1

三、集群

3.1 特点

3.0 版本之前的 redis 是不支持集群的,那个时候,我们的 redis 如果想要集群的话,就需要一个中间件,然后这个中间件负责将我们需要存入 redis 中的数据的 key 通过一套算法计算得出一个值。然后根据这个值找到对应的 redis 节点,将这些数据存在这个 redis 的节点中。

在取值的时候,同样先将 key 进行计算,得到对应的值,然后就去找对应的 redis 节点,从对应的节点中取出对应的值。这样做有很多不好的地方,比如说我们的这些计算都需要在系统中去进行,所以会增加系统的负担。还有就是这种集群模式下,某个节点挂掉,其他的节点无法知道。而且也不容易对每个节点进行负载均衡。

从 redis 3.0 版本开始支持 redis-cluster 集群,redis-cluster 采用无中心结构,每一个节点都保存有这个集群所有主节点以及从节点的信息,及集群状态,每个节点都和其他节点连接。所以 redis-cluster 是一种服务端分片技术。

  1. 每个节点都和 n-1 个节点通信,这被称为集群总线(cluster bus)。它们使用特殊的端口号,即对外服务端口号加 10000。所以要维护好这个集群的每个节点信息,不然会导致整个集群不可用,其内部采用特殊的二进制协议优化传输速度和带宽。

  2. redis-cluster 把所有的物理节点映射到[0,16383]slot(槽)上,cluster 负责维护 node--slot--value。

  3. 集群预先给所有节点分好 16384 个桶,每个节点得到部分桶,当需要在 redis 集群中插入数据时,根据 CRC16(KEY) mod 16384 的值,决定将一个 key 放到哪个桶中。

  4. 客户端与 redis 节点直连,不需要连接集群所有的节点,连接集群中任何一个可用节点即可。整个 cluster 被看做是一个整体,客户端可连接任意一个节点进行操作,当客户端操作的 key 没有分配在该节点上时,redis 会返回转向指令,指向正确的节点。

  5. redis-trib.rb 脚本(rub 语言)为集群的管理工具,比如自动添加节点,规划槽位,迁移数据等一系列操作。

  6. 节点的 fail 是通过集群中超过半数的节点检测失效时才生效。集群节点之间通过互相的 ping-pong 判断是否可以连接上。如果有一半以上的节点去 ping 一个节点的时候没有回应,集群就认为这个节点宕机了,然后去连接它的备用节点。如果某个节点和所有从节点全部挂掉,集群就进入 fail 状态,也可以理解成集群的 slot 映射[0-16383]不完整时进入 fail 状态。如果有一半以上的主节点宕机,那么无论这些节点有没有从节点,集群同样进入 fail 状态。这就是 redis 的投票机制。

  7. 为了增加集群的可访问性,官方推荐的方案是将 node 配置成主从结构,即一个 master 主节点,挂 n 个 slave 从节点。如果主节点失效,redis cluster 会根据选举算法从 slave 节点中选择一个上升为 master 节点,整个集群继续对外提供服务。

3.2 配置

1.redis 集群依赖 ruby,需安装 ruby 环境,ruby 版本需高于 2.2

yum install ruby

yum install rubygems

gem install redis

2.修改配置文件

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

3.开启集群中的所有节点

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

4.将节点加入集群中,中途需要输入 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

5.进入集群中任何一个节点 redis-cli -c -h 192.168.0.107 -p 6381

6.查看集群中的节点 cluster nodes

[root@buke107 src]### redis-cli -c -h 192.168.0.107 -p 6381

192.168.0.107:6381> cluster nodes

868456121fa4e6c8e7abe235a88b51d354a944b5 192.168.0.107:6382 master - 0 1523609792598 3 connected 10923-16383

d6d01fd8f1e5b9f8fc0c748e08248a358da3638d 192.168.0.107:6385 slave 868456121fa4e6c8e7abe235a88b51d354a944b5 0 1523609795616 6 connected

5cd3ed3a84ead41a765abd3781b98950d452c958 192.168.0.107:6380 master - 0 1523609794610 1 connected 0-5460

b8e047aeacb9398c3f58f96d0602efbbea2078e2 192.168.0.107:6383 slave 5cd3ed3a84ead41a765abd3781b98950d452c958 0 1523609797629 1 connected

68cf66359318b26df16ebf95ba0c00d9f6b2c63e 192.168.0.107:6384 slave 90b4b326d579f9b5e181e3df95578bceba29b204 0 1523609796622 5 connected

90b4b326d579f9b5e181e3df95578bceba29b204 192.168.0.107:6381 myself,master - 0 0 2 connected 5461-10922

如上所示,三主三从节点已创建成功

7.增加集群节点 cluster meet ip port


其他集群实现方式

中间件

一、twemproxy

  1. twemproxy 又称 nutcracker,起源于推特系统中 redis、memcached 集群的轻量级代理。

  2. Redis 代理中间件 twemproxy 是一种利用中间件做分片的技术。twemproxy 处于客户端和服务器的中间,将客户端发来的请求,进行一定的处理后(sharding),再转发给后端真正的 redis 服务器。也就是说,客户端不直接访问 redis 服务器,而是通过 twemproxy 代理中间件间接访问。降低了客户端直连后端服务器的连接数量,并且支持服务器集群水平扩展。

  3. twemproxy 中间件的内部处理是无状态的,它本身可以很轻松地集群,这样可以避免单点压力或故障。

  4. 从下面架构图看到 twemproxy 是一个单点,很容易对其造成很大的压力,所以通常会结合 keepalived 来实现 twemproy 的高可用。这时,通常只有一台 twemproxy 在工作,另外一台处于备机,当一台挂掉以后,vip 自动漂移,备机接替工作。关于 keepalived 的用法可自行网上查阅资料。

二、codiscodis 是一个分布式的 Redis 解决方案,由豌豆荚开源,对于上层的应用来说,连接 codis proxy 和连接原生的 redis server 没什么明显的区别,上层应用可以像使用单机的 redis 一样使用,codis 底层会处理请求的转发,不停机的数据迁移等工作,所有后边的事情,对于前面的客户端来说是透明的,可以简单的认为后边连接的是一个内存无限大的 redis 服务。

客户端分片

分区的逻辑在客户端实现,由客户端自己选择请求到哪个节点。方案可参考一致性哈希,这种方案通常适用于用户对客户端的行为有完全控制能力的场景。一、Jedis sharding 集群 Redis Sharding 可以说是在 Redis cluster 出来之前业界普遍的采用方式,其主要思想是采用 hash 算法将存储数据的 key 进行 hash 散列,这样特定的 key 会被定为到特定的节点上。庆幸的是,Java Redis 客户端驱动 Jedis 已支持 Redis Sharding 功能,即 ShardedJedis 以及结合缓存池的 ShardedJedisPoolJedis 的 Redis Sharding 实现具有如下特点:

  1. 采用一致性哈希算法,将 key 和节点 name 同时 hashing,然后进行映射匹配,采用的算法是 MURMUR_HASH。采用一致性哈希而不是采用简单类似哈希求模映射的主要原因是当增加或减少节点时,不会产生由于重新匹配造成的 rehashing。一致性哈希只影响相邻节点 key 分配,影响量小。

  2. 为了避免一致性哈希只影响相邻节点造成节点分配压力,ShardedJedis 会对每个 Redis 节点根据名字(没有,Jedis 会赋予缺省名字)会虚拟化出 160 个虚拟节点进行散列。根据权重 weight,也可虚拟化出 160 倍数的虚拟节点。用虚拟节点做映射匹配,可以在增加或减少 Redis 节点时,key 在各 Redis 节点移动再分配更均匀,而不是只有相邻节点受影响。

  3. ShardedJedis 支持 keyTagPattern 模式,即抽取 key 的一部分 keyTag 做 sharding,这样通过合理命名 key,可以将一组相关联的 key 放入同一个 Redis 节点,这在避免跨节点访问相关数据时很重要。

  4. 当然,Redis Sharding 这种轻量灵活方式必然在集群其它能力方面做出妥协。比如扩容,当想要增加 Redis 节点时,尽管采用一致性哈希,毕竟还是会有 key 匹配不到而丢失,这时需要键值迁移。

  5. 作为轻量级客户端 sharding,处理 Redis 键值迁移是不现实的,这就要求应用层面允许 Redis 中数据丢失或从后端数据库重新加载数据。但有些时候,击穿缓存层,直接访问数据库层,会对系统访问造成很大压力。

 小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。

原文出处:xie.infoq.cn/article/e75a716781eb70d16193b580d


版权声明:本文为m0_70565884原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。