Redis集群-官方推荐方案RedisCluster
前情提要
- 理解RedisCluster的原理和容错机制
- 能够配置RedisCluster并使用
redis使用中遇到的瓶颈
我们日常工作中使用Redis,经常会遇到一些问题:
1、高可用问题,如何保证redis的持续高可用性。
2、容量问题,单实例redis内存无法无限扩充,达到32G后就进入了64位世界,性能下降。
3、并发性能问题,redis号称单实例10万并发,但也是有尽头的。
RedisCluster的原理和容错机制
redis的集群策略
redis3.0以后推出的redis cluster 集群方案,redis cluster集群保证了高可用、高性能、高可扩展性。
主要有下面三种集群策略,分别为:
- 推特:twemproxy : 代理式
- 豌豆荚:codis :代理式
- 官方:redis cluster : 非代理
我们主要来学习官方推出的 redis cluster,这也是生产项目中用的最多的。
redis-cluster的优势
1、官方推荐,毋庸置疑。
2、去中心化,集群最大可增加1000个节点,性能随节点增加而线性扩展。
3、管理方便,后续可自行增加或摘除节点,移动分槽等等。
4、简单,易上手。
redis-cluster名词介绍
1、master 主节点、
2、slave 从节点
3、slot 槽,一共有16384数据分槽,分布在集群的所有主节点中。
redis cluster 架构图
架构细节:
-
图中描述的是六个redis实例构成的集群,6379端口为客户端通讯端口,16379端口为集群总线端口
-
集群内部划分为16384个数据分槽,分布在三个主redis中。
-
从redis中没有分槽,不会参与集群投票,也不会帮忙加快读取数据,仅仅作为主机的备份。
-
三个主节点中平均分布着16384数据分槽的三分之一,每个节点中不会存有有重复数据,仅仅有自己的从机帮忙冗余。
-
所有的redis主节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
-
客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
-
节点的fail是通过集群中超过半数的节点检测失效时才生效。
操作原理演示:
Redis 集群中内置了 16384个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
redis cluster 投票:容错
-
节点失效判断: 所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout),认为该master节点挂掉.
-
挂掉主节点的从节点自动升级为主节点,redis集群操作.
集群失效判断: 什么时候整个集群不可用(cluster_state:fail)?
- 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
- 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。 (投票无效)
集群部署
redis 集群最少需要三台服务器,这里我们采用3主3从来配置 redis cluster。端口可以自定定义,我设为 6310、6320、6330、6340、6350、6360
- 第一步,复制安装redis bin目录的 redis.conf 为 6份。eg:redis1.conf …
- 第二步,修改这6份配置文件,如下所示,记着6份配置文件都需要修改哈;
#redis.conf默认配置
daemonize yes
pidfile /var/run/redis/redis.pid #多实例情况下需修改,例如redis_6380.pid
port 6379 #多实例情况下需要修改,例如6380
tcp-backlog 511
bind 0.0.0.0
timeout 0
tcp-keepalive 0
loglevel notice
logfile “” #多实例情况下需要修改,例如6380.log
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb #多实例情况下需要修改,例如dump.6380.rdb
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
appendonly yes
appendfilename "appendonly.aof" #多实例情况下需要修改,例如 appendonly_6380.aof
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
#################自定义配置
#系统配置
#vim /etc/sysctl.conf
#vm.overcommit_memory = 1
aof-rewrite-incremental-fsync yes
maxmemory 4096mb
maxmemory-policy allkeys-lru
dir /opt/redis/data #多实例情况下需要修改,例如/data/6380
#集群配置
cluster-enabled yes #打开集群配置
cluster-config-file /opt/redis/6380/nodes.conf #多实例情况下需要修改,例如/6380/
cluster-node-timeout 5000
#从ping主间隔默认10秒
#复制超时时间
#repl-timeout 60
#远距离主从
#config set client-output-buffer-limit "slave 536870912 536870912 0"
#config set repl-backlog-size 209715200
- 第三步,启动6个实例,
./redis-server redis.conf
注意,redis.conf应为6个不同的修改过的多实例配置文件; - 第四步,创建redis集群;
[root@localhost redis-cluster]# ./redis-cli --cluster create 192.168.137.6:6310 192.168.137.6:6320 192.168.137.6:6330 192.168.137.6:6340 192.168.137.6:6350 192.168.137.6:6360 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.137.6:6350 to 192.168.137.6:6310
Adding replica 192.168.137.6:6360 to 192.168.137.6:6320
Adding replica 192.168.137.6:6340 to 192.168.137.6:6330
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 6de62fad996e29fec56265d6259e0221e07d83f2 192.168.137.6:6310
slots:[0-5460] (5461 slots) master
M: 6b3a0a5826bf5350953a6f1a75954f5d9276b821 192.168.137.6:6320
slots:[5461-10922] (5462 slots) master
M: 305613cdddafbb2fbbe02bda877a9337b88bc8b7 192.168.137.6:6330
slots:[10923-16383] (5461 slots) master
S: 35d42e9e33baaef1f295cf36f88fd49766fa549b 192.168.137.6:6340
replicates 6de62fad996e29fec56265d6259e0221e07d83f2
S: 66864b86e16a139640b52aeb1f8a95297a236edd 192.168.137.6:6350
replicates 6b3a0a5826bf5350953a6f1a75954f5d9276b821
S: a85a97502509a7a5d53266bb9720cb176ec5f957 192.168.137.6:6360
replicates 305613cdddafbb2fbbe02bda877a9337b88bc8b7
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 192.168.137.6:6310)
M: 6de62fad996e29fec56265d6259e0221e07d83f2 192.168.137.6:6310
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 305613cdddafbb2fbbe02bda877a9337b88bc8b7 192.168.137.6:6330
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: 6b3a0a5826bf5350953a6f1a75954f5d9276b821 192.168.137.6:6320
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: a85a97502509a7a5d53266bb9720cb176ec5f957 192.168.137.6:6360
slots: (0 slots) slave
replicates 305613cdddafbb2fbbe02bda877a9337b88bc8b7
S: 35d42e9e33baaef1f295cf36f88fd49766fa549b 192.168.137.6:6340
slots: (0 slots) slave
replicates 6de62fad996e29fec56265d6259e0221e07d83f2
S: 66864b86e16a139640b52aeb1f8a95297a236edd 192.168.137.6:6350
slots: (0 slots) slave
replicates 6b3a0a5826bf5350953a6f1a75954f5d9276b821
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
可以看到,16384个槽已经平均分配给了3台master实例。
命令行客户端连接集群
./redis-cli -p 6310 -c
注意:-c 表示是以redis集群方式进行连接
127.0.0.1:6310> set key1 11
-> Redirected to slot [9189] located at 192.168.137.10:6320
OK
192.168.137.10:6320>
- 查看集群状态
127.0.0.1:6310> 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:777
cluster_stats_messages_pong_sent:772
cluster_stats_messages_sent:1549
cluster_stats_messages_ping_received:767
cluster_stats_messages_pong_received:777
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:1549
- 查看集群中的节点
127.0.0.1:6310> cluster nodes
7bd6149e1350b1494e73d3e0d3623c8a5d0f6578 192.168.137.10:6340@16340 slave 8ab958bf84a6d9d30fe79ff379c9afade2587416 0 1582208322552 4 connected
550dd0cd4939a1a14cbabc7c7cf4c2cb6ce48d47 192.168.137.10:6350@16350 slave ce1707cae8882f129bf00f8b4282be9b8b875adc 0 1582208323555 5 connected
85709be5115d23f9c449a382de119cf189851fd1 192.168.137.10:6330@16330 master - 0 1582208322000 3 connected 10923-16383
ce1707cae8882f129bf00f8b4282be9b8b875adc 192.168.137.10:6320@16320 master - 0 1582208323555 2 connected 5461-10922
8ab958bf84a6d9d30fe79ff379c9afade2587416 192.168.137.10:6310@16310 myself,master - 0 1582208322000 1 connected 0-5460
e31d92d97d96bcbe43c0a1f8c33ab66540db974a 192.168.137.10:6360@16360 slave 85709be5115d23f9c449a382de119cf189851fd1 0 1582208322953 6 connected
维护节点
添加主节点
- 先创建6370节点
- 添加新创建的节点到集群
[root@localhost redis-cluster]# ./redis-cli --cluster add-node 192.168.137.10:6370 192.168.137.10:6320
>>> Adding node 192.168.137.10:6370 to cluster 192.168.137.10:6320
>>> Performing Cluster Check (using node 192.168.137.10:6320)
M: ce1707cae8882f129bf00f8b4282be9b8b875adc 192.168.137.10:6320
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: e31d92d97d96bcbe43c0a1f8c33ab66540db974a 192.168.137.10:6360
slots: (0 slots) slave
replicates 85709be5115d23f9c449a382de119cf189851fd1
M: 85709be5115d23f9c449a382de119cf189851fd1 192.168.137.10:6330
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: 8ab958bf84a6d9d30fe79ff379c9afade2587416 192.168.137.10:6310
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 550dd0cd4939a1a14cbabc7c7cf4c2cb6ce48d47 192.168.137.10:6350
slots: (0 slots) slave
replicates ce1707cae8882f129bf00f8b4282be9b8b875adc
S: 7bd6149e1350b1494e73d3e0d3623c8a5d0f6578 192.168.137.10:6340
slots: (0 slots) slave
replicates 8ab958bf84a6d9d30fe79ff379c9afade2587416
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 192.168.137.10:6370 to make it join the cluster.
[OK] New node added correctly.
新节点必须是空的,不能包含任何数据。请把之前aof和dump文件删掉,并且若有nodes.conf也需要删除。add-node 将一个节点添加到集群里面, 第一个是新节点ip:port, 第二个是任意一个已存在节点ip:port,node:新节点没有包含任何数据, 因为它没有包含任何slot。
- 查看集群节点发现新增的6370节点
127.0.0.1:6310> cluster nodes
e305402550696f152b76d6d77fca3b8ede162033 192.168.137.10:6370@16370 master - 0 1582209128696 0 connected
7bd6149e1350b1494e73d3e0d3623c8a5d0f6578 192.168.137.10:6340@16340 slave 8ab958bf84a6d9d30fe79ff379c9afade2587416 0 1582209129198 4 connected
550dd0cd4939a1a14cbabc7c7cf4c2cb6ce48d47 192.168.137.10:6350@16350 slave ce1707cae8882f129bf00f8b4282be9b8b875adc 0 1582209130601 5 connected
85709be5115d23f9c449a382de119cf189851fd1 192.168.137.10:6330@16330 master - 0 1582209129598 3 connected 10923-16383
ce1707cae8882f129bf00f8b4282be9b8b875adc 192.168.137.10:6320@16320 master - 0 1582209130201 2 connected 5461-10922
8ab958bf84a6d9d30fe79ff379c9afade2587416 192.168.137.10:6310@16310 myself,master - 0 1582209129000 1 connected 0-5460
e31d92d97d96bcbe43c0a1f8c33ab66540db974a 192.168.137.10:6360@16360 slave 85709be5115d23f9c449a382de119cf189851fd1 0 1582209129699 6 connected
Hash槽重新分配
添加完主节点需要对主节点进行hash槽分配,这样该主节才可以存储数据。
通过 cluster nodes
查看集群中槽的占用情况:
给刚添加的6370结点分配槽
- 第一步:连接上集群(连接集群中任意一个可用结点都行),执行下面的命令
./redis-cli -p 6320 --cluster reshard 192.168.137.10:6310
- 第二步:输入要分配的槽数量
输入:3000,表示要给目标节点分配3000个槽
- 第三步:输入接收槽的结点id
输入:e305402550696f152b76d6d77fca3b8ede162033
PS:这里准备给6370分配槽,通过cluster nodes查看7007结点id为:e305402550696f152b76d6d77fca3b8ede162033
- 第四步:输入源结点id
输入all。
ps: 这里输入源节点id,即从此节点取出槽,分配后此节点中将不再有这些槽。输入all 从所有源节点中拿,输入done取消分配。
- 第五步:输入yes开始移动槽到目标结点id
输入:yes
添加从节点
添加 6380 从结点,将 6380 作为 6370 的从结点
命令:
./redis-cli --cluster add-node 新节点的ip : 端口 旧节点ip : 端口 --cluster-slave --
cluster-master-id 主节点id
例如:
./redis-cli --cluster add-node 192.168.137.10:6380 192.168.137.10:6370 --cluster-slave --cluster-master-id e305402550696f152b76d6d77fca3b8ede162033
e305402550696f152b76d6d77fca3b8ede162033 是6370节点的id,可以通过 cluster nodes
命令查看.
上图所示,即已经配置从节点成功了。
注意:如果原来该结点在集群中的配置信息已经生成到 cluster-config-file 指定的配置文件中(如果cluster-config-file 没有指定则默认为nodes.conf),这时可能会报错:
[ERR] Node XXXXXX is not empty. Either the node already knows other nodes (check
with CLUSTER NODES) or contains some key in database 0
解决方法是删除生成的配置文件nodes.conf,删除后再执行 ./redis-cli add-node
指令.
查看集群中的结点,刚添加的 6380 为 6370 的从节点:
删除节点
命令:
./redis-cli --cluster del-node 192.168.137.10:6380 89a4504a36c37bc2f898a977aee1182d40101d49
执行完上面的删除节点命令后,我们可以再次查看集群节点状况:
可以发现6380这台从节点实例已经被删除掉了。
看到这里,认真思考的同学可能会想上面我们是直接删除了从节点实例,如果要删除主节点呢,那么动手去尝试下吧!
如你所愿,确实出错啦,因为 6370 是一台主节点,而且已经被分配了 slot (槽),即会有数据分配到这台机器上,所以是不允许删除有分配槽的节点的。 如果你实在想要下点集群中的实例,那么需要将该节点占用的 hash 槽分配出去,参考 上面我们学习到的 Hash槽重新分配的 命令。
Jedis连接集群
如果你跟着我从上面已经完成了redis集群的搭建,那么接下来是不是就应该想下我们应用中应该如何使用啦?
由于我是搞Java的,这里就写下如果使用Jedis连接RedisCluster的代码,当然主流语言都是有连接Redis的工具包,就不多做赘述了。
- 创建一个Maven项目,这个步骤就不列了,如果有不知道的小伙伴,可以文末关注我,加我好友联系交流哈。
- 添加Jedis客户端jar包,测试如下代码;
# pom文件添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
# 测试代码
@Test
public void testJedisCluster() throws Exception {
//创建一连接,JedisCluster对象,在系统中是单例存在
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.137.10", 6310));
nodes.add(new HostAndPort("192.168.137.10", 6320));
nodes.add(new HostAndPort("192.168.137.10", 6330));
nodes.add(new HostAndPort("192.168.137.10", 6340));
nodes.add(new HostAndPort("192.168.137.10", 6450));
nodes.add(new HostAndPort("192.168.137.10", 6360));
JedisCluster cluster = new JedisCluster(nodes);
//执行JedisCluster对象中的方法,方法和redis一一对应。
cluster.set("cluster-test", "my jedis cluster test");
String result = cluster.get("cluster-test");
System.out.println(result);
//程序结束时需要关闭JedisCluster对象
cluster.close();
}
注意:执行上面代码有可能会报网络不可达,只要关闭redis服务器的防火墙就好了;
systemctl stop firewalld.service # 我的系统是centos7
使用Spring
- 添加xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 连接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大连接数 -->
<property name="maxTotal" value="30"/>
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="10"/>
<!-- 每次释放连接的最大数目 -->
<property name="numTestsPerEvictionRun" value="1024"/>
<!-- 释放连接的扫描间隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="30000"/>
<!-- 连接最小空闲时间 -->
<property name="minEvictableIdleTimeMillis" value="1800000"/>
<!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
<property name="softMinEvictableIdleTimeMillis" value="10000"/>
<!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
<property name="maxWaitMillis" value="1500"/>
<!-- 在获取连接的时候检查有效性, 默认false -->
<property name="testOnBorrow" value="true"/>
<!-- 在空闲时检查有效性, 默认false -->
<property name="testWhileIdle" value="true"/>
<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
<property name="blockWhenExhausted" value="false"/>
</bean>
<!-- redis集群 -->
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg index="0">
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6310"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6320"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6330"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6340"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6350"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.137.10"></constructor-arg>
<constructor-arg index="1" value="6360"></constructor-arg>
</bean>
</set>
</constructor-arg>
<constructor-arg index="1" ref="jedisPoolConfig"></constructor-arg>
</bean>
</beans>
- 添加 spring 的jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
- 测试代码
public class RedisClusterSpring {
private ApplicationContext applicationContext;
@Before
public void init() {
applicationContext = new ClassPathXmlApplicationContext("classpath:application.xml");
}
@Test
public void testJedisCluster() {
JedisCluster jedisCluster = (JedisCluster) applicationContext.getBean("jedisCluster");
jedisCluster.set("name","罗小黑");
String value = jedisCluster.get("name");
System.out.println(value);
}
}
好了,今天主要学了Redis官方推出的集群搭建方式 Redis-Cluster,你学会了? 如果对Redis主从模式、哨兵模式不太了解的同学建议阅读笔者的这两篇文章,学习Redis这几个知识点是必会的,面试中也经常问Redis主从、哨兵、集群模式的区别和共同点之类的。
Redis进阶你不得不了解的知识点-主从复制原理
Redis哨兵机制-你不得不了解的知识点
如果在操作过程中有遇到问题欢迎扫码关注加我好友,一起沟通学习,有问必答!