本文使用的redis版本为5.0.3,不适用4.x/3.x版本,4.x/3.x版本请使用redis-trib.rb脚本
写本文的目的
网上有很多的redis集群的方案,大体分为两类:一类是基于redis官方的cluster方案,一类是各家一线互联网大厂的内部方案。在满足项目需求的情况下,自然是选择官方的方案。理由很简单:有持续的维护,官方背景,文档资料充足,学习曲线平滑。但是网上大部分文章都是讲理论,很少有实战性的内容,因此基于目前项目的实战,整理此文。希望对有实战需求的同学有所帮助,能力有限,望大家多多提供好的建议!
术语
redis实例 = 平常大家开发时使用的单个redis服务实例。
redis节点 = 本文指的节点是一主一从两个配对的redis实例。
仿高吞吐量环境
为了验证集群节点变更情况下的可用性,我们使用一个程序来模拟高吞吐量的环境,这个程序会一直跑,我们做一个全程可用性监控:
@Service
public class CacheService {
private AtomicLong atomicLong;
private ReentrantLock reentrantLock;
@Autowired
private RedisTemplate redisTemplate;
@PostConstruct
public void init() {
atomicLong = new AtomicLong();
reentrantLock = new ReentrantLock();
}
public Object r(String k) {
Object v = redisTemplate.opsForValue().get(k);
if (v == null) {
reentrantLock.lock();
try {
Object vv = redisTemplate.opsForValue().get(k);
if (vv != null) {
System.out.println("THEAD HAS WRITTEN KEY: "+ k);
return vv;
}
return w(k, "AABBCCDD");
} finally {
reentrantLock.unlock();
}
}
return v;
}
public Object w(String k, Object v) {
long calls = atomicLong.incrementAndGet();
redisTemplate.opsForValue().set(k, v/*, Duration.ofSeconds(60*10)*/);
if (calls%10000 ==0) {
System.out.println("write call "+calls+" times.");
}
System.out.println("write KEY: "+k);
return v;
}
}
初始化部署
官方建议集群数量3节点起步,那么我们规划的目标机器如下,每个机器都部署一主一从两个redis实例
172.22.122.22 #初始部署
172.22.122.23 #初始部署
172.22.122.24 #初始部署
172.22.122.25 #扩容测试用
查阅官方提供的初始化部署方法如下:
每个节点启动2点redis实例,最后,运行如下命令
其中的参数--cluster-replicas 1
指的是每个主实例附带一个从实例,构成主从。
redis-cli --cluster create --cluster-replicas 1 172.22.122.22:7000 172.22.122.22:7001 172.22.122.23:7002 172.22.122.23:7003 172.22.122.24:7004 172.22.122.24:7005
然后可以看到redis自动帮我们确定主实例和从实例,最后还自动分配好了三个节点各自的slot区间,这些slot区间都是连续的。
平滑新增节点
接下来,我们在172.22.122.25
机器上启动两个redis实例。根据redis-cluster官方手册,我们做如下配置:
#在这里,172.22.122.25:7006节点为我们需要新加入的节点,而172.22.122.22:7000为6个老节点之一,可以从6个老节点中随意选一个
./redis-cli --cluster add-node 172.22.122.25:7006 172.22.122.22:7000
#在这里,172.22.122.25:7006节点为我们需要新加入的节点,而172.22.122.22:7000为6个老节点之一,可以从6个老节点中随意选一个
#而--cluster-master-id a35dce8fbe264d9f952ba9ae30700bf5869694e6 则为上面加入的节点的master-id
./redis-cli --cluster add-node 172.22.122.25:7007 172.22.122.22:7000 --cluster-slave --cluster-master-id 6b6b877d529f6661da049eb4e97e6a46d593a0bf
[root@yyy src] ./redis-cli --cluster add-node 172.22.122.25:7007 172.22.122.22:7000 --cluster-slave --cluster-master-id a35dce8fbe264d9f952ba9ae30700bf5869694e6
>>> Adding node 172.22.122.25:7007 to cluster 172.22.122.22:7000
>>> Performing Cluster Check (using node 172.22.122.22:7000)
M: bd49cb987a00ca814d5df1afa43c014fa982ddb4 172.22.122.22:7000
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: a35dce8fbe264d9f952ba9ae30700bf5869694e6 172.22.122.25:7006
slots: (0 slots) master
M: 718a893d8af2a1c0df09eabe5f46e9bb06681202 172.22.122.23:7002
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: 335a6f75823dd396d12183bb122c8ed080fbd413 172.22.122.24:7005
slots: (0 slots) slave
replicates bd49cb987a00ca814d5df1afa43c014fa982ddb4
M: 5ba08a9724cc6f9fd261bba1d5021972192f5f12 172.22.122.24:7004
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: aa82018ca28eaeab19e9282d3144ce68e1c3150f 172.22.122.22:7001
slots: (0 slots) slave
replicates 718a893d8af2a1c0df09eabe5f46e9bb06681202
S: f0cd581693a3bbf3636228e6dbb2c9f98f1462ed 172.22.122.23:7003
slots: (0 slots) slave
replicates 5ba08a9724cc6f9fd261bba1d5021972192f5f12
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 172.22.122.25:7007 to make it join the cluster.
Waiting for the cluster to join
>>> Configure node as replica of 172.22.122.25:7006.
[OK] New node added correctly.
添加完节点之后不会自动生成slot,需要做集群间的slot迁移,这块可以做热迁移,在不停服的情况下完成。测试在小批量数据下(万条)毫无影响,在比较大的数据量情况下,例如亿条级别,需要做谨慎的测试,这块留到今后补充。
分配slot尽量防止碎片化
一种简单的重新分配slot的方法如下命令:
./redis-cli --cluster reshard 172.22.122.22:7000
# 需要输入槽数量信息等,
最终重分配结果如下:
172.22.122.22:7000> cluster slots
1) 1) (integer) 0
2) (integer) 665
3) 1) "172.22.122.25"
2) (integer) 7006
3) "a35dce8fbe264d9f952ba9ae30700bf5869694e6"
4) 1) "172.22.122.25"
2) (integer) 7007
3) "78f99e7847b257807e58b6efc5614cb6934fc76d"
2) 1) (integer) 5461
2) (integer) 6127
3) 1) "172.22.122.25"
2) (integer) 7006
3) "a35dce8fbe264d9f952ba9ae30700bf5869694e6"
4) 1) "172.22.122.25"
2) (integer) 7007
3) "78f99e7847b257807e58b6efc5614cb6934fc76d"
3) 1) (integer) 10923
2) (integer) 11588
3) 1) "172.22.122.25"
2) (integer) 7006
3) "a35dce8fbe264d9f952ba9ae30700bf5869694e6"
4) 1) "172.22.122.25"
2) (integer) 7007
3) "78f99e7847b257807e58b6efc5614cb6934fc76d"
4) 1) (integer) 6128
2) (integer) 10922
3) 1) "172.22.122.23"
2) (integer) 7002
3) "718a893d8af2a1c0df09eabe5f46e9bb06681202"
4) 1) "172.22.122.22"
2) (integer) 7001
3) "aa82018ca28eaeab19e9282d3144ce68e1c3150f"
5) 1) (integer) 11589
2) (integer) 16383
3) 1) "172.22.122.24"
2) (integer) 7004
3) "5ba08a9724cc6f9fd261bba1d5021972192f5f12"
4) 1) "172.22.122.23"
2) (integer) 7003
3) "f0cd581693a3bbf3636228e6dbb2c9f98f1462ed"
6) 1) (integer) 666
2) (integer) 5460
3) 1) "172.22.122.22"
2) (integer) 7000
3) "bd49cb987a00ca814d5df1afa43c014fa982ddb4"
4) 1) "172.22.122.24"
2) (integer) 7005
3) "335a6f75823dd396d12183bb122c8ed080fbd413"
这种分配方式会随机的从已经在运行的节点中获取一部分slot分配到新的主从节点中,容易造成slot的碎片化,对今后的迁移下线会造成更加复杂的操作。因此在这里,个人推荐增加主从节点时,只从一个老的节点中分配一半的slot数量,新增节点数按双倍的方式扩容。优点是不会产生碎片,slot分配连续,回收slot也更加方便。
我们使用以下命令就可以做到在节点间的连续slot迁移:
./redis-cli --cluster reshard --cluster-from bd49cb987a00ca814d5df1afa43c014fa982ddb4 --cluster-to 718a893d8af2a1c0df09eabe5f46e9bb06681202 --cluster-slots 5460 --cluster-yes 172.22.122.22:7000
接下来我们模拟某台机器挂了,直接选择
192.168.1.4
停掉这台机器上的redis主从实例,然后我们选择任意一台存活机器查看集群信息,可以看到标注为fail的节点:
172.22.122.24:7005> cluster nodes
f0cd581693a3bbf3636228e6dbb2c9f98f1462ed 172.22.122.23:7003@17003 slave 5ba08a9724cc6f9fd261bba1d5021972192f5f12 0 1547709963620 5 connected
78f99e7847b257807e58b6efc5614cb6934fc76d 172.22.122.25:7007@17007 slave,fail a35dce8fbe264d9f952ba9ae30700bf5869694e6 1547702940350 1547702939850 8 disconnected
718a893d8af2a1c0df09eabe5f46e9bb06681202 172.22.122.23:7002@17002 master - 0 1547709962616 3 connected 6128-10922
335a6f75823dd396d12183bb122c8ed080fbd413 172.22.122.24:7005@17005 myself,slave bd49cb987a00ca814d5df1afa43c014fa982ddb4 0 1547709961000 6 connected
a35dce8fbe264d9f952ba9ae30700bf5869694e6 172.22.122.25:7006@17006 master,fail - 1547702940350 1547702939000 8 disconnected 0-665 5461-6127 10923-11588
bd49cb987a00ca814d5df1afa43c014fa982ddb4 172.22.122.22:7000@17000 master - 0 1547709962113 1 connected 666-5460
5ba08a9724cc6f9fd261bba1d5021972192f5f12 172.22.122.24:7004@17004 master - 0 1547709960000 5 connected 11589-16383
aa82018ca28eaeab19e9282d3144ce68e1c3150f 172.22.122.22:7001@17001 slave 718a893d8af2a1c0df09eabe5f46e9bb06681202 0 1547709963000 3 connected
此时因为192.168.1.4
上的主从节点都被停掉了,因此其实这个集群已经不可用了(slot已经不再完整,因此Java客户端的连接都会报集群不可用的错误),我们需要想办法恢复集群。
查阅redis的官方手册,我们发现有个命令,CLUSTER FORGET可以让集群下线fail的节点,需要注意的是这个命令要在每个redis实例上执行,主节点,从节点都需要。
所有的实例都执行这个命令之后,我们再次检查集群状态:
172.22.122.24:7004> cluster nodes
bd49cb987a00ca814d5df1afa43c014fa982ddb4 172.22.122.22:7000@17000 master - 0 1547709998840 1 connected 666-5460
aa82018ca28eaeab19e9282d3144ce68e1c3150f 172.22.122.22:7001@17001 slave 718a893d8af2a1c0df09eabe5f46e9bb06681202 0 1547709997135 3 connected
335a6f75823dd396d12183bb122c8ed080fbd413 172.22.122.24:7005@17005 slave bd49cb987a00ca814d5df1afa43c014fa982ddb4 0 1547709996131 6 connected
718a893d8af2a1c0df09eabe5f46e9bb06681202 172.22.122.23:7002@17002 master - 0 1547709998000 3 connected 6128-10922
f0cd581693a3bbf3636228e6dbb2c9f98f1462ed 172.22.122.23:7003@17003 slave 5ba08a9724cc6f9fd261bba1d5021972192f5f12 0 1547709996833 5 connected
5ba08a9724cc6f9fd261bba1d5021972192f5f12 172.22.122.24:7004@17004 myself,master - 0 1547709997000 5 connected 11589-16383
但是集群的状态还是fail的,这是因为,我们简单的下线了那台机器,却没有将损失的虚拟slot回收,导致整个集群依旧处于缺失slot状态,我们需要将丢失的slot再找回来。
./redis-cli --cluster fix 172.22.122.24:7004
使用修复命令,将16384个slot重新分配到三点集群中
再平衡:
./redis-cli --cluster reshard --cluster-from bd49cb987a00ca814d5df1afa43c014fa982ddb4 --cluster-to 718a893d8af2a1c0df09eabe5f46e9bb06681202 --cluster-slots 5460 --cluster-yes 172.22.122.22:7000
登陆集群,查看节点生存状况
[root@172 src]# ./redis-cli -h 172.22.122.22 -p 7000 -c
172.22.122.22:7000> cluster nodes