一、概述

Redis3.0版本之后支持Cluster.

1.1、redis cluster的现状

目前redis支持的cluster特性:

  • 1):节点自动发现
  • 2):slave->master 选举,集群容错
  • 3):Hot resharding:在线分片
  • 4):集群管理:cluster xxx
  • 5):基于配置(nodes-port.conf)的集群管理
  • 6):ASK 转向/MOVED 转向机制.

1.2、redis cluster 架构

1、redis-cluster架构图

架构细节:

  • (1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
  • (2)节点的fail是通过集群中超过半数的节点检测失效时才生效.
  • (3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
  • (4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value

2、redis-cluster选举:容错

  1. 领着选举过程是集群中所有master参与,如果半数以上master节点与master节点通信超过(cluster-node-timeout),认为当前master节点挂掉.
  2. 什么时候整个集群不可用(cluster_state:fail),当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误
  • a:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成进群的slot映射[0-16383]不完成时进入fail状态.
  • b:如果进群超过半数以上master挂掉,无论是否有slave集群进入fail状态.

1.3、Redis-Cluster集群缺点

  • 数据通过异步复制,不保证数据的强一致性。
  • Redis Cluster 不支持像 Redis 的独立版本那样支持多个数据库。只有数据库 0 并且不允许使用SELECT命令。
  • Key 批量操作限制,如使用 mset、mget 目前只支持具有相同 slot 值的 Key 执行批量操作。对于映射为不同 slot 值的 Key 由于 Keys 不支持跨 slot 查询,所以执行 mset、mget、sunion 等操作支持不友好。
  • Key 事务操作支持有限,只支持多 key 在同一节点上的事务操作,当多个 Key 分布于不同的节点上时无法使用事务功能。

二、redis安装

Redis 集群至少需要 3 个主节点。本文档配置6节点,3主3从

安装文档请参考:点击这里

主机名 节点IP 端口号
redis01-server 172.28.254.221 7001
redis01-server 172.28.254.221 7002
redis01-server 172.28.254.221 7003
redis02-server 172.28.254.27 7004
redis02-server 1172.28.254.27 7005
redis02-server 1172.28.254.27 7006

三、redis cluster配置

1、编辑各节点的 Redis 配置文件:

###以下内容在所有节点上配置!!!
################################ REDIS CLUSTER  ###############################
cluster-enabled yes
cluster-config-file  ./nodes_6379.conf
cluster-node-timeout 5000
# appendonly yes
# cluster-slave-validity-factor 10
# cluster-migration-barrier 1
# cluster-require-full-coverage yes

2、修改配置文件

sed -i  's/6379/7001/g' /etc/redis/7001.conf
sed -i  's/6379/7002/g' /etc/redis/7002.conf
sed -i  's/6379/7003/g' /etc/redis/7003.conf
sed -i  's/6379/7004/g' /etc/redis/7004.conf
sed -i  's/6379/7005/g' /etc/redis/7005.conf
sed -i  's/6379/7006/g' /etc/redis/7006.conf
  • 如果设置密码,需修改启动脚本
sed -i 's#^CLIEXEC=.*$#CLIEXEC="/usr/local/redis/bin/redis-cli -a abc123"#g' /etc/rc.d/init.d/redis_7001
sed -i 's#^CLIEXEC=.*$#CLIEXEC="/usr/local/redis/bin/redis-cli -a abc123"#g' /etc/rc.d/init.d/redis_7002
sed -i 's#^CLIEXEC=.*$#CLIEXEC="/usr/local/redis/bin/redis-cli -a abc123"#g' /etc/rc.d/init.d/redis_7003
sed -i 's#^CLIEXEC=.*$#CLIEXEC="/usr/local/redis/bin/redis-cli -a abc123"#g' /etc/rc.d/init.d/redis_7004
sed -i 's#^CLIEXEC=.*$#CLIEXEC="/usr/local/redis/bin/redis-cli -a abc123"#g' /etc/rc.d/init.d/redis_7005
sed -i 's#^CLIEXEC=.*$#CLIEXEC="/usr/local/redis/bin/redis-cli -a abc123"#g' /etc/rc.d/init.d/redis_7006

从节点增加以下 Redis 配置(redis cluster跳过此步骤!):

###以下内容只在从节点上配置!!!
################################# REPLICATION #################################
masterauth abc123    #配置 masterauth(如果有 requirepass)

slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no

repl-diskless-sync-delay 5
# repl-ping-slave-period 10
# repl-timeout 60
repl-disable-tcp-nodelay no

# repl-backlog-size 1mb

# repl-backlog-ttl 3600
slave-priority 100
# min-slaves-max-lag 10
# min-slaves-max-lag is set to 10.

# slave-announce-ip 5.5.5.5
# slave-announce-port 1234

配置信息:

  • masterauth #主数据库连接需要的密码验证
  • cluster-enabled #开启集群
  • cluster-config-file #指定集群配置文件
  • slave-serve-stale-data #在Redis主从同步中,当Redis从服务器发现Redis主服务器不可用时,无法判断自己的数据是否已经过期,如果此时Redis从服务器收到读的请求,则可以会响应过期的数据。如果此参数为yes,则表示Redis从服务器仍然会以可能过期的数据进行响应,但是如果此参数为no,从服务器将阻塞所有请求,有客户端请求时返回“SYNC with master in progress”;。
  • slave-read-only #当此参数为yes时,则此Redis服务器将只会响应读请求,该参数默认关闭,在Redis从库上回默认开启。
  • repl-diskless-sync #该参数表示是否开启无磁盘交互模式,默认为no,即不开启,也可以手动更改为yes,表示开启。所谓无磁盘交互模式,就是指Redis主和Redis从在进行主从同步时,Redis主同步给Redis从的数据文件不会被写入磁盘中,Redis从服务器会直接将该数据文件读取处理后放入内存,而如果该参数不开启,则Redis从库会将数据文件放入磁盘中,然后再读入内存。
  • repl-diskless-sync-delay #该参数表示在进行无磁盘交互模式下Redis从库的延迟时间,该参数默认单位为秒,默认为5.
  • slave-priority #该参数指定Redis从库的优先级,通常用于主从同步结合Redis sentinel进行高可用配置时使用,该优先级数值越小,表示优先级越高,在默认情况下,该参数值为100.
  • min-slaves-to-write #该参数表示在Redis主从同步过程中,一个Redis主库所对应的最少的从库数量,当少于该参数指定的数量时,Redis主库将拒绝从库读取数据的请求。该参数在默认情况下处于关闭状态。
  • min-slaves-max-lag #表示Redis主库与Redis从库之间的最大时差,当时差超过该值时,就无法进行主从同步。该参数在默认情况下处于关闭状态。

3、各节点启动Redis:

#关闭默认端口
killall redis-server

#172.28.254.221
/etc/init.d/redis_7001 restart
/etc/init.d/redis_7002 restart
/etc/init.d/redis_7003 restart

#172.28.254.27
/etc/init.d/redis_7004 restart
/etc/init.d/redis_7005 restart
/etc/init.d/redis_7006 restart

问题:

  • 如果修改过监听地址,则修改redis启动脚本
# sed -i 's/$CLIEXEC -p/$CLIEXEC -h 172.28.254.221 -p/g' /etc/init.d/redis_7001

四、配置redis-trib (Redis 5以上版本跳过此步骤,可以直接使用 redis-cli 创建)

前面已经准备好了搭建集群的redis节点,接下来我们要把这些节点都串连起来搭建集群。官方提供了一个工具:redis-trib.rb( /usr/local/redis-3.2.8/src/redis-trib.rb) 看后缀就知道这鸟东西不能直接执行,它是用ruby写的一个程序,所以我们还得安装ruby.

1、安装ruby

yum -y install ruby ruby-devel rubygems rpm-build 

2、gem 安装 redis接口

gem是ruby的一个工具包.

gem install redis     #等一会儿就好了

如果无法在线安装 Ruby 的 Redis 模块,可以先下载 Redis 模块(https://rubygems.org/gems/redis),再离线安装:

#gem install -l redis-3.3.0.gem
  • 创建 redis-trib.rb 软链接:
# ln -sv /data/apps/redis-6.2.6/src/redis-trib.rb /usr/bin/redis-trib
"/usr/bin/redis-trib" -> "/data/apps/redis-6.2.6/src/redis-trib.rb"

注意:在执行gem install redis时,报ERROR:Error installing redis: redis requires Ruby version >= 2.2.2异常。

解决: 点击此处查看解决方案

3、运行redis-trib.rb

查看 redis-trib.rb 使用帮助:

[root@redis01-server ~]# redis-trib 
Usage: redis-trib <command> <options> <arguments ...>

  create          host1:port1 ... hostN:portN    # 创建集群,指定集群中的节点;
                  --replicas <arg>               # 指定每个Master的副本数量(即一个Master有几个Slave);
  check           host:port						 # 检查集群状态
  info            host:port						 # 查看集群信息
  fix             host:port						 # 修复集群
                  --timeout <arg>
  reshard         host:port						 # 重新分片(热迁移slots);
                  --from <arg>
                  --to <arg>
                  --slots <arg>
                  --yes
                  --timeout <arg>
                  --pipeline <arg>
  rebalance       host:port						 # 平衡集群中各节点的slot数量;
                  --weight <arg>
                  --auto-weights
                  --use-empty-masters
                  --timeout <arg>
                  --simulate
                  --pipeline <arg>
                  --threshold <arg>
  add-node        new_host:new_port existing_host:existing_port
  												 # 向集群中添加节点;
                  --slave
                  --master-id <arg>
  del-node        host:port node_id				 # 从集群中删除节点;
  set-timeout     host:port milliseconds		 # 设置节点的超时时间(单位毫秒);
  call            host:port command arg arg .. arg
  												 # 在集群的所有节点上执行命令;
  import          host:port						 # 导入外部redis服务器的数据到当前集群;
                  --from <arg>
                  --copy
                  --replace
  help            (show this help)

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

4、为 Ruby Redis 模块配置 Redis 连接密码

[root@redis01-server ~]# find / -name 'client.rb'
/usr/share/ruby/xmlrpc/client.rb
/usr/local/rvm/src/ruby-2.4.10/gems/xmlrpc-0.2.1/lib/xmlrpc/client.rb
/usr/local/rvm/gems/ruby-2.4.10/gems/redis-4.6.0/lib/redis/client.rb
/usr/local/rvm/rubies/ruby-2.4.10/lib/ruby/gems/2.4.0/gems/xmlrpc-0.2.1/lib/xmlrpc/client.rb

[root@redis01-server ~]# vim /usr/local/rvm/gems/ruby-2.4.10/gems/redis-4.6.0/lib/redis/client.rb
class Redis
  class Client
    # Defaults are also used for converting string keys to symbols.
    DEFAULTS = {
... ... ...
      password: abc123,  #修改此行

五、创建集群

Redis 集群至少需要 3 个主节点。 确认所有的节点都启动

1、使用参数 create 创建集群

前面已经提醒过的 防火墙一定要开放监听的端口,否则会创建失败。

  • Redis 5 版本以下,使用redis-trib 创建
[root@redis01-server ~]# redis-trib create --replicas 172.28.254.221:7001 172.28.254.221:7002 172.28.254.221:7003 172.28.254.27:7004 172.28.254.27:7005 172.28.254.27:7006

解释下,--replicas 1 表示 自动为每一个master节点分配一个slave节点 上面有6个节点,程序会按照一定规则生成 3个master(主)3个slave(从)。其他参数是实例的地址集合。

  • Redis 5 版本可以直接使用 redis-cli 创建
[root@redis01-server ~]# redis-cli -a abc123 --cluster-replicas 1 --cluster create  172.28.254.221:7001 172.28.254.221:7002 172.28.254.221:7003 172.28.254.27:7004 172.28.254.27:7005 172.28.254.27:7006 

005 172.28.254.27:7006
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.28.254.27:7006 to 172.28.254.221:7001
Adding replica 172.28.254.221:7003 to 172.28.254.27:7004
Adding replica 172.28.254.27:7005 to 172.28.254.221:7002
M: bb73c60e00c4ebd4ac33db0542ec684a8d201d8e 172.28.254.221:7001
   slots:[0-5460] (5461 slots) master
M: c261ce0177a72e0007f4903f8ce5f6e3bee0294f 172.28.254.221:7002
   slots:[10923-16383] (5461 slots) master
S: d2d369de207775a5f2e13f3580037b8acb53eeff 172.28.254.221:7003
   replicates 27581beea51e7b6e1052ef3675dac6647fdb8355
M: 27581beea51e7b6e1052ef3675dac6647fdb8355 172.28.254.27:7004
   slots:[5461-10922] (5462 slots) master
S: d277d6288442806f4893a9ae51ff792b6326a656 172.28.254.27:7005
   replicates c261ce0177a72e0007f4903f8ce5f6e3bee0294f
S: 66d4816fcd50da1872d4dbca8312551159e72e3d 172.28.254.27:7006
   replicates bb73c60e00c4ebd4ac33db0542ec684a8d201d8e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.
>>> Performing Cluster Check (using node 172.28.254.221:7001)
M: bb73c60e00c4ebd4ac33db0542ec684a8d201d8e 172.28.254.221:7001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: c261ce0177a72e0007f4903f8ce5f6e3bee0294f 172.28.254.221:7002
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: d2d369de207775a5f2e13f3580037b8acb53eeff 172.28.254.221:7003
   slots: (0 slots) slave
   replicates 27581beea51e7b6e1052ef3675dac6647fdb8355
M: 27581beea51e7b6e1052ef3675dac6647fdb8355 172.28.254.27:7004
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 66d4816fcd50da1872d4dbca8312551159e72e3d 172.28.254.27:7006
   slots: (0 slots) slave
   replicates bb73c60e00c4ebd4ac33db0542ec684a8d201d8e
S: d277d6288442806f4893a9ae51ff792b6326a656 172.28.254.27:7005
   slots: (0 slots) slave
   replicates c261ce0177a72e0007f4903f8ce5f6e3bee0294f
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

六、验证集群创建

集群中任一节点均可查看集群状态和信息;

  • redis-cli(Redis-5)
#查看集群状态:
[root@redis01-server ~]# redis-cli -a abc123 -p 7001
127.0.0.1:7001> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:725
cluster_stats_messages_pong_sent:738
cluster_stats_messages_sent:1463
cluster_stats_messages_ping_received:733
cluster_stats_messages_pong_received:725
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:1463

#查看 Node 对应关系:
127.0.0.1:7001> CLUSTER NODES
bb73c60e00c4ebd4ac33db0542ec684a8d201d8e 172.28.254.221:7001@17001 myself,master - 0 1650545949000 1 connected 0-5460
c261ce0177a72e0007f4903f8ce5f6e3bee0294f 172.28.254.221:7002@17002 master - 0 1650545950551 2 connected 10923-16383
d2d369de207775a5f2e13f3580037b8acb53eeff 172.28.254.221:7003@17003 slave 27581beea51e7b6e1052ef3675dac6647fdb8355 0 1650545949344 4 connected
27581beea51e7b6e1052ef3675dac6647fdb8355 172.28.254.27:7004@17004 master - 0 1650545948840 4 connected 5461-10922
66d4816fcd50da1872d4dbca8312551159e72e3d 172.28.254.27:7006@17006 slave bb73c60e00c4ebd4ac33db0542ec684a8d201d8e 0 1650545950555 1 connected
d277d6288442806f4893a9ae51ff792b6326a656 172.28.254.27:7005@17005 slave c261ce0177a72e0007f4903f8ce5f6e3bee0294f 0 1650545949854 2 connected
127.0.0.1:7001> exit

#检查检点
[root@redis-server ~]# redis-cli -a abc123 --cluster check 172.28.254.221:7001
172.28.254.221:7001 (bb73c60e...) -> 0 keys | 5461 slots | 1 slaves.
172.28.254.221:7002 (c261ce01...) -> 0 keys | 5461 slots | 1 slaves.
172.28.254.27:7004 (27581bee...) -> 0 keys | 5462 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
... ... ...
  • redis-trib.rb 指定集群中的任一节点均可查看集群的状态信息;
#查看集群信息:
[root@redis01-server ~]# redis-trib info 172.28.254.221:7001

#检查集群状态:
[root@redis01-server ~]# redis-trib check 172.28.254.221:7001
  • 查看集群配置文件
[root@redis01-server ~]# ]# cat /data/redis/7001/nodes_7001.conf
bb73c60e00c4ebd4ac33db0542ec684a8d201d8e 172.28.254.221:7001@17001 myself,master - 0 1650545543000 1 connected 0-5460
c261ce0177a72e0007f4903f8ce5f6e3bee0294f 172.28.254.221:7002@17002 master - 0 1650545541561 2 connected 10923-16383
d2d369de207775a5f2e13f3580037b8acb53eeff 172.28.254.221:7003@17003 slave 27581beea51e7b6e1052ef3675dac6647fdb8355 0 1650545543569 4 connected
27581beea51e7b6e1052ef3675dac6647fdb8355 172.28.254.27:7004@17004 master - 0 1650545543000 4 connected 5461-10922
66d4816fcd50da1872d4dbca8312551159e72e3d 172.28.254.27:7006@17006 slave bb73c60e00c4ebd4ac33db0542ec684a8d201d8e 0 1650545543569 1 connected
d277d6288442806f4893a9ae51ff792b6326a656 172.28.254.27:7005@17005 slave c261ce0177a72e0007f4903f8ce5f6e3bee0294f 0 1650545543000 2 connected
vars currentEpoch 6 lastVoteEpoch 0

七、测试集群读写

  • 集群数据写入 172.28.254.221:7001节点上尝试写入:

提示 foo1 这个 key 的 CRC 结果被调度到了 13431 槽位,该槽位在 172.28.254.221:7002 节点上;

[root@redis01-server ~]# redis-cli -a abc123 -p 7001
127.0.0.1:7001> SET foo1 bar1
(error) MOVED 13431 172.28.254.221:7002

172.28.254.221:7002节点 写入 foo1:

[root@redis01-server ~]# redis-cli -a abc123 -p 7002
127.0.0.1:7002> SET foo1 bar1
OK
127.0.0.1:7002> KEYS foo1
1) "foo1"

各节点只保存各自槽位的数据,所以 7001 和 7003 都没有刚刚写入的 foo1:

[root@redis01-server ~]# redis-cli -a abc123 -p 7003
127.0.0.1:7003> KEYS foo1
(empty array)
  • 集群数据读取 因为各节点只保存各自槽位的数据,所以数据的读取也只能到相应的节点上进行; 而且集群中的 Slave 节点,读写服务均不提供;

查看 172.28.254.221:7002 的 Slave 节点:

172.28.254.221:7002 的 ID 为:c261ce0177a72e0007f4903f8ce5f6e3bee0294f; 可以查到对应的 Slave 为 172.28.254.27:7005;

[root@redis01-server ~]# cat /data/redis/7002/nodes_7002.conf |grep 'c261ce0177a72e0007f4903f8ce5f6e3bee0294f'
c261ce0177a72e0007f4903f8ce5f6e3bee0294f 172.28.254.221:7002@17002 myself,master - 0 1650545541000 2 connected 10923-16383
d277d6288442806f4893a9ae51ff792b6326a656 172.28.254.27:7005@17005 slave c261ce0177a72e0007f4903f8ce5f6e3bee0294f 0 1650545541000 2 connected

到 172.28.254.27:7005 读取刚刚写入到 172.28.254.221:7002 的 foo1: 可以查到有 foo1 的数据,但是读取还是得到 172.28.254.221:7002 上;

[root@redis02-server ~]# redis-cli -a abc123 -p 7005
127.0.0.1:7005> KEYS foo1
1) "foo1"
127.0.0.1:7005> GET foo1
(error) MOVED 13431 172.28.254.221:7002

八、集群扩容

[详情参考博客] https://www.cnblogs.com/PatrickLiu/p/8473135.html

#新增Master主节点
 redis-cli --cluster add-node 120.78.204.98:9007 120.78.204.98:9001

#为新节点分配槽位
redis-cli --cluster reshard 120.78.204.98:9007

#新增Slave从节点
##将9008端口redis实例添加为cluster集群中9007端口redis实例的从节点
redis-cli --cluster add-node 120.78.204.98:9008 120.78.204.98:9007 --cluster-slave

九、Cluster集群删除操作

#删除的顺序是先删除Slave从节点,然后在删除Master主节点
##1、动态删除Slave从服务器节点
redis-cli --cluster del-node 192.168.127.130:7007 991ed242102aaa08873eb9404a18e0618a4e37bd

#2、动态删除Master主服务器节点
#要想删除Master主节点,可能要繁琐一些。因为在Master主节点上有数据槽(slots),为了保证数据的不丢失,必须把这些数据槽迁移到其他Master主节点上,然后在删除主节点。

##2.1、重新分片
##把要删除的Master主节点的数据槽移动到其他Master主节点上,以免数据丢失。
redis-cli --cluster reshard 192.168.127.130:7006

##将此节点的槽重新分配给其他节点后再删除
redis-cli --cluster del-node 192.168.127.130:7006 71ecd970838e9b400a2a6a15cd30a94ab96203bf

十、SpringBoot整合Redis-Cluster

1、Pom文件新增依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

2、新增配置

#集群节点
spring.redis.cluster.nodes=120.78.204.98:9001,120.78.204.98:9002,120.78.204.98:9003,120.78.204.98:9004,120.78.204.98:9005,120.78.204.98:9006
#最大重定向数
spring.redis.cluster.max-redirects=6
​
# 连接池最大连接数(使用负值表示没有限制) 默认为8
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认为-1
spring.redis.jedis.pool.max-wait=-1ms
# 连接池中的最大空闲连接 默认为8
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接 默认为 0
spring.redis.jedis.pool.min-idle=0