本文以手动操作redis cluster
集群为例, 讲解并证明redis cluster
的增容和缩容。
redis分三种模式:
主从模式不是分布式一致性里面的主从,需要手动指定Mater
(在slave
机器上通过slaveof
命令指定)。主负责读写,从负责读。
针对主动模式发生宕机,需要手动切换主, Sentinel通过集群监控, 来修复这个问题。
cluster模式, redis 3.0
提供,非一致性hash
(https://www.jianshu.com/p/e968c081f563), 使用slots槽
。
笔者的理解, 类似于Java的
ConcurrentHash
(JDK1.8-ConcurrentHashMap的 rehash 扩容逻辑 ,
slots
类似于JavaConcurrentHash
里的Node
(仅仅是像,数据结构完全不一样), 迁移的时候逐个slot
迁移, 这样集群的可用性可得到保障。
Redis cluster
使用 slot
存储数据。默认16384
个。
存储的数据, 存储位置在:CRC16(key) mod 16384的值
上, 里面又会有细分数据结构。例如kv
、Hash
等
unsigned int keyHashSlot(char *key, int keylen) {
int s, e; /* start-end indexes of { and } */
for (s = 0; s < keylen; s++)
if (key[s] == '{') break;
if (s == keylen) return crc16(key,keylen) & 0x3FFF;
for (e = s+1; e < keylen; e++)
if (key[e] == '}') break;
if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;
return crc16(key+s+1,e-s-1) & 0x3FFF;
}
在redis集群中, 默认会分配连续的slot
空间给每个结点。但是这只是默认分配而已, 后续扩容的时候会做调整(例如下文,7台机器的集群, slot大致连续, 但是当slot迁移到新增服务器后, 每台的slot数字就不连续了):
127.0.0.1:7006> cluster slots
1) 1) (integer) 0
2) (integer) 865
3) 1) "127.0.0.1"
2) (integer) 7000
3) "ecee2fce5ddc618ad4e9d1738eb546653f4abd6f"
4) 1) "127.0.0.1"
2) (integer) 7001
3) "4dd154308c6af771857d82de5785d0fedc32a224"
2) 1) (integer) 867
2) (integer) 6666
3) 1) "127.0.0.1"
2) (integer) 7000
3) "ecee2fce5ddc618ad4e9d1738eb546653f4abd6f"
4) 1) "127.0.0.1"
2) (integer) 7001
3) "4dd154308c6af771857d82de5785d0fedc32a224"
3) 1) (integer) 6667
2) (integer) 12306
3) 1) "127.0.0.1"
2) (integer) 7002
3) "08da27b70687df11f480aed5d6e6dc58f0aa819e"
4) 1) "127.0.0.1"
2) (integer) 7003
3) "c04d2e2a661dba2624e0c84c237bd24aa418d1c3"
4) 1) (integer) 12307
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 7004
3) "666868d5d51e060de165ec52d412926d1404af5a"
4) 1) "127.0.0.1"
2) (integer) 7005
3) "800552c4c96fd79dbf8bbf5ff37fb4174d4a166b"
5) 1) (integer) 866
2) (integer) 866
3) 1) "127.0.0.1"
2) (integer) 7006
3) "22e6f70ab45662aef226d83c6b4a7b2eabb28ad1"
4) 1) "127.0.0.1"
2) (integer) 7007
3) "62c5b7b83fcd86e7861a32c25d0d87c3998c5440"
redis
扩容设计巧妙, 扩容迁移即为slot
迁移. 只是承担slot
的机器变多了,slot槽
本身结构并无变化
- 针对待迁移的
slot槽
, 原机器设定为导出- 针对待迁移的
slot槽
, 目标机器设定为导入- 执行数据迁移, 此时所有对该
迁移中的slot槽
请求不可用, 转为ask
状态- 周知集群其它结点,
slot槽
被迁移到了新的机器
四步走理解之后就很简单。 相较于一致性hash
算法, 影响面大大降低(单个slot槽
), 平滑程度增加(简单的槽迁移)。
在迁移过程中, 如果访问到正在被迁移中的slot槽
, redis
返回ASK
转向。
集群本身会维护slot槽
对应关系, 每个客户端的实现, 也大多会维护这个映射关系。
下载在redis官网 redis.io
wget http://download.redis.io/releases/redis-5.0.4.tar.gz
安装make && make install
make -j && make install
它会要求你运行make test
, 可以尝试。
最好将其拷贝到你期望的目录中, 例如我默认喜欢/opt
mv * /opt/redis-5.0.4/
重点的两个文件redis-cli
和 redis-server
在 src
目录下
➜ conf ls /opt/redis-5.0.4/src/redis-cli
/opt/redis-5.0.4/src/redis-cli
➜ conf ls /opt/redis-5.0.4/src/redis-server
/opt/redis-5.0.4/src/redis-server
启动很简单, ./redis-server /opt/redis-5.0.4/redis.conf
(redis.conf默认目录在根目录下)
通过ps -ef | grep redis
可看到进程。
纯手动操作, 新建一个redis的运行文件夹, 例如名叫
cluster-test
, 在它下面建立三个文件夹:
- conf
- data
- logs
➜ conf ls /opt/redis-5.0.4/cluster-test
conf data logs
进入conf
目录操作, 你需要很多的redis-conf, 例如
你期望启动8个redis进程组成cluster
,
偶数为master
,
基数为slave
,
先启动6台组成cluster
, 三主三从, 然后启动两台作为增容测试。
从端口7000开始:
vim redis-7000.conf
## 指定运行端口
port 7000
daemonize yes
dir "/opt/redis-5.0.4/cluster-test/data"
logfile "/opt/redis-5.0.4/cluster-test/logs/7000.log"
#dbfilename不能配置为路径
dbfilename "dump-7000.rdb"
cluster-enabled yes
cluster-config-file nodes-7000.conf
#是否需要每个节点都可用,集群才算可用,关闭
cluster-require-full-coverage no
然后通过如下命令生成8个配置文件;
sed "s/7000/7001/g" redis-7000.conf > redis-7001.conf
sed "s/7000/7002/g" redis-7000.conf > redis-7002.conf
sed "s/7000/7003/g" redis-7000.conf > redis-7003.conf
sed "s/7000/7004/g" redis-7000.conf > redis-7004.conf
sed "s/7000/7005/g" redis-7000.conf > redis-7005.conf
sed "s/7000/7006/g" redis-7000.conf > redis-7006.conf
sed "s/7000/7007/g" redis-7000.conf > redis-7007.conf
再分别通过每个命令启动服务
./redis-server ../cluster-test/conf/ redis-7000.conf
简单几个命令:
登陆某台机器:
➜ src ./redis-cli -p 7000
127.0.0.1:7000> cluster meet 127.0.0.1 7001
OK
依次对其他7002 7003 7004 7005机器执行该命令即可(暂时不要执行6、7两台, 他们作为扩容用)
此时查询集群状态:
127.0.0.1:7000> cluster info
cluster_state:fail(如果你启动过一次,并且分配了slot, data目录有数据,再次启动集群,此处将是ok)
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:0
cluster_current_epoch:0
cluster_my_epoch:0
cluster_stats_messages_sent:0
cluster_stats_messages_received:0
然后配置主从模式执行 cluster nodes
查询id:
127.0.0.1:7000> cluster nodes
62c5b7b83fcd86e7861a32c25d0d87c3998c5440 127.0.0.1:7007@17007 master - 0 1589044372000 7 connected
22e6f70ab45662aef226d83c6b4a7b2eabb28ad1 127.0.0.1:7006@17006 master - 0 1589044373669 5 connected
08da27b70687df11f480aed5d6e6dc58f0aa819e 127.0.0.1:7002@17002 master - 0 1589044374679 2 connected
c04d2e2a661dba2624e0c84c237bd24aa418d1c3 127.0.0.1:7003@17003 master - 0 1589044372664 3 connected
ecee2fce5ddc618ad4e9d1738eb546653f4abd6f 127.0.0.1:7000@17000 myself,master - 0 1589044369000 0 connected
800552c4c96fd79dbf8bbf5ff37fb4174d4a166b 127.0.0.1:7005@17005 master - 0 1589044373000 4 connected
666868d5d51e060de165ec52d412926d1404af5a 127.0.0.1:7004@17004 master - 0 1589044374000 6 connected
4dd154308c6af771857d82de5785d0fedc32a224 127.0.0.1:7001@17001 master - 0 1589044375690 1 connected
然后使用命令设定主从(表示将7001设置为7000的从, 下面的id对应7000的id):
./redis-cli -p 7001 cluster replicate ecee2fce5ddc618ad4e9d1738eb546653f4abd6f
如上操作的效果就是:
你需要给每个redis
进程分配slot
:
/opt/redis-5.0.4/src/redis-cli -p 7000 cluster addslots 0
因为要分配16384
个, 你可以使用脚本来分配vim addslots.sh
:
start=$1
end=$2
port=$3
for slot in `seq ${start} ${end}`
do
/opt/redis-5.0.4/src/redis-cli -p ${port} cluster addslots ${slot}
done
然后执行:
./addslots.sh 0 6666 7000
表示将0~6666
这么多号槽分配给端口为7000
这个进程。
如上, 你的redis cluster搭建完毕, 三主三从,
slot
也分配完毕, 可以接活了:
127.0.0.1:7000> set hello world
OK
127.0.0.1:7000> set hello1 world
(error) MOVED 11613 127.0.0.1:7002
127.0.0.1:7000> set hello2 world
(error) MOVED 7486 127.0.0.1:7002
127.0.0.1:7000> set hello3 world
OK
127.0.0.1:7000> get hello
"world"
127.0.0.1:7000> get hello1
(error) MOVED 11613 127.0.0.1:7002
127.0.0.1:7000>
127.0.0.1:7000> cluster keyslot hello
(integer) 866
127.0.0.1:7000> cluster keyslot hello1
(integer) 11613
目前有
7000~7007
八个redis
进程, 前六个三主三从。 准备加入7006
127.0.0.1:7006> cluster meet 127.0.0.1 7000
OK
然后开始迁移866这个slot
(就是hello
)。
1.在导入机器使用importing
将目标slot
设置为导入状态,id为原进程的id
127.0.0.1:7006> CLUSTER SETSLOT 866 importing ecee2fce5ddc618ad4e9d1738eb546653f4abd6f
OK
2.在导出机器使用migrating
将目标slot
设置为导出状态,id为被导入机器的id
127.0.0.1:7000> CLUSTER SETSLOT 866 migrating 22e6f70ab45662aef226d83c6b4a7b2eabb28ad1
OK
3.在导出机器执行migrate
数据迁移
127.0.0.1:7000> MIGRATE 127.0.0.1 7006 "" 0 1000 keys hello
OK
4.最后,在任意机器执行告知slot
迁移动作
127.0.0.1:7006> CLUSTER SETSLOT 866 node 22e6f70ab45662aef226d83c6b4a7b2eabb28ad1
OK
懒得写, 类似增容的反方向。