Redis集群搭建(节点添加删除)

一、Redis Cluster(Redis集群)简介

1、redis是一个开源的key value存储系统,受到了广大互联网公司的青睐。redis3.0版本之前只支持单例模式,在3.0版本及以后才支持集群,我这里用的是redis3.0.0版本;
2、所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。redis集群采用P2P模式,是完全去中心化的,不存在中心节点或者代理节点;
3、redis集群是没有统一的入口的,客户端(client)连接集群的时候连接集群中的任意节点(node)即可,集群内部的节点是相互通信的(PING-PONG机制),每个节点都是一个redis实例;
4、为了实现集群的高可用,即判断节点是否健康(能否正常使用),redis-cluster有这么一个投票容错机制:如果集群中超过半数的节点投票认为某个节点挂了,那么这个节点就挂了(fail)。这是判断节点是否挂了的方法;
5、那么如何判断集群是否挂了呢? -> 如果集群中任意一个节点挂了,而且该节点没有从节点(备份节点),那么这个集群就挂了。这是判断集群是否挂了的方法;

那么为什么任意一个节点挂了(没有从节点)这个集群就挂了呢? -> 因为集群内置了16384个slot(哈希槽),并且把所有的物理节点映射到了这16384[0-16383]个slot上,或者说把这些slot均等的分配给了各个节点。当需要在Redis集群存放一个数据(key-value)时,redis会先对这个key进行crc16算法,然后得到一个结果。再把这个结果对16384进行求余,这个余数会对应[0-16383]其中一个槽,进而决定key-value存储到哪个节点中。所以一旦某个节点挂了,该节点对应的slot就无法使用,那么就会导致集群无法正常工作。

如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态.

综上所述,每个Redis集群理论上最多可以有16384个节点。

关于redis的集群化方案 目前有三种:

(1)Twitter开发的twemproxy
    简介:twemproxy架构简单就是用proxy对后端redis server进行代理 但是由于代理层的消耗性能很低 而且通常涉及多个key的操作都是不支持的 而且本身不支持动态扩容和透明的数据迁移 而且也失去维护 Twitter内部已经不使用了
(2)豌豆荚开发的codis
        codis使用的也是proxy思路,但是做的比较好,是这两种之间的一个中间级,而且支持redis命令是最多的,有图形化GUI管理和监控工具。
(3)redis官方的redis-cluster
        redis-cluster是三个里性能最强大的。因为他使用去中心化的思想,使用hash slot方式,将16348个hash slot 覆盖到所有节点上,对于存储的每个key值 使用CRC16(KEY)&16348=slot 得到他对应的hash slot 并在访问key时就去找他的hash slot在哪一个节点上 然后由当前访问节点从实际被分配了这个hash slot的节点去取数据 节点之间使用轻量协议通信,减少带宽占用,性能很高,自动实现负载均衡与高可用,自动实现failover。并且支持动态扩展,官方已经玩到可以1000个节点 实现的复杂度低,是全新的思路。
        但是它也有一些不足。例如官方没有提供图形化管理工具,运维体验差,全手工数据迁移 ,且自己对自己本身的redis命令支持也不完全等

二、集群搭建需要的环境

Redis集群至少需要3个节点,因为投票容错机制要求超过半数节点认为某个节点挂了该节点才是挂了,所以2个节点无法构成集群。
要保证集群的高可用,需要每个节点都有从节点,也就是备份节点,所以Redis集群至少需要6台服务器。

1、安装ruby

创建集群集群脚本是ruby写的

yum -y install ruby
yum -y install rubygems
gem install redis  #安装Redis和ruby的接口

[root@localhost redis]# gem install redis
ERROR:  Error installing redis:
    redis requires Ruby version >= 2.2.2.

yum库中ruby的版本支持到 2.0.0,可gem 安装redis需要最低是2.2.2。解决办法是采用rvm来更新ruby
安装yum install curl

按照rvm.io提示执行gpg2 –recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl: (35) SSL connectgpg2 –recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
gpg: requesting key D39DC0E3 from hkp server keys.gnupg.net
gpg: requesting key 39499BDB from hkp server keys.gnupg.net
gpg: key D39DC0E3: “Michal Papis (RVM signing) mpapis@gmail.com“ not changed
gpg: key D39DC0E3: “Totally Legit Signing Key mallory@example.org“ not changed
gpg: key 39499BDB: “Piotr Kuczynski piotr.kuczynski@gmail.com“ not changed
gpg: Total number processed: 3
gpg: unchanged: 3

不通过,下一步的\curl -sSL https://get.rvm.io | bash -s stable
或curl -L get.rvm.io | bash -s stable

也就不行。

寻找了很多招办法都不行,只能通过手动下载ruby高版本手动安装,或者通过另外一个工具rbenv安装

手动下载高版本rub安装

解压

[root@localhost tmp]# tar -zxvf ruby-2.6.1.tar.gz -C ../ruby-2.6
[root@localhost tmp]# ./configure --prefix=/usr/local/ruby -prefix是将ruby安装到指定目录,也可以自定义
[root@localhost tmp]# make && make install

安装完成

[root@localhost ruby]# bin/ruby -v
ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-linux]

把ruby加入环境变量

[root@localhost ruby]# vim /etc/profile
[root@localhost ruby]# source /etc/profile
[root@localhost ruby]# echo $PATH
/usr/local/ruby/bin:/usr/local/jdk8/bin:/usr/local/jdk8/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

其中设置PATH路径,把安装的ruby放在系统PATH前面,避免调用操作系统自带的ruby

这回执行gem install redis #安装Redis和ruby的接口
成功

2、配置集群

由于没有这么多Linux,就在同一台上模拟配置6个Redis实例作为六个节点。

在/usr/local/下新建redis_cluster文件,里边新建六个目录node6001~node6006当做六个节点

拷贝创建集群的脚本文件redis-trib.rb到redis_cluster

在分别拷贝配置文件Redis.conf到这些目录下,并且依次修改为自己对应的节点信息。

依次修改信息如下:

port  6001    
bind 192.168.18.140                           #默认ip为127.0.0.1 需要改为其他节点机器可访问的ip 否则创建集群时无法访问对应的端口,无法创建集群
daemonize    yes                               #redis后台运行
pidfile /var/run/redis_6001.pid          #PID
cluster-enabled  yes                        #开启集群  把注释#去掉
cluster-config-file nodes-6001.conf   #集群的配置  配置文件首次启动自动生成6001/6002...
cluster-node-timeout  15000               #请求超时  默认15秒,可自行设置
appendonly  yes                           #aof日志开启  有需要就开启,它会每次写操作都记录一条日志
dir /usr/local/redis-cluster/node6001/    #默认是./,分别修改为当前节点下

启动并检查六个节点是否启动

编写脚本\start-all.sh

#start 6 redis
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/node6001/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/node6002/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/node6003/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/node6004/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/node6005/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/node6006/redis.conf

stop-all.sh
#stop 6 redis
/usr/local/redis/bin/redis-cli -h 192.168.18.140 -p 6001 shutdown
/usr/local/redis/bin/redis-cli -h 192.168.18.140 -p 6002 shutdown
/usr/local/redis/bin/redis-cli -h 192.168.18.140 -p 6003 shutdown
/usr/local/redis/bin/redis-cli -h 192.168.18.140 -p 6004 shutdown
/usr/local/redis/bin/redis-cli -h 192.168.18.140 -p 6005 shutdown
/usr/local/redis/bin/redis-cli -h 192.168.18.140 -p 6006 shutdown

赋权:chmod 777 start-all.sh stop-all.sh

执行启动脚本,并检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

[root@localhost redis-cluster]# ./start-all.sh
[root@localhost redis-cluster]# netstat -tnlp | grep redis
tcp 0 0 192.168.18.140:6001 0.0.0.0:* LISTEN 34204/redis-server
tcp 0 0 192.168.18.140:6002 0.0.0.0:* LISTEN 34206/redis-server
tcp 0 0 192.168.18.140:6003 0.0.0.0:* LISTEN 34208/redis-server
tcp 0 0 192.168.18.140:6004 0.0.0.0:* LISTEN 34213/redis-server
tcp 0 0 192.168.18.140:6005 0.0.0.0:* LISTEN 34218/redis-server
tcp 0 0 192.168.18.140:6006 0.0.0.0:* LISTEN 34223/redis-server
tcp 0 0 192.168.18.140:16001 0.0.0.0:* LISTEN 34204/redis-server
tcp 0 0 192.168.18.140:16002 0.0.0.0:* LISTEN 34206/redis-server
tcp 0 0 192.168.18.140:16003 0.0.0.0:* LISTEN 34208/redis-server
tcp 0 0 192.168.18.140:16004 0.0.0.0:* LISTEN 34213/redis-server
tcp 0 0 192.168.18.140:16005 0.0.0.0:* LISTEN 34218/redis-server
tcp 0 0 192.168.18.140:16006 0.0.0.0:* LISTEN 34223/redis-server

[root@localhost redis-cluster]# ps -el | grep redis
5 S 0 34204 1 0 80 0 - 36491 ep_pol ? 00:00:00 redis-server
5 S 0 34206 1 0 80 0 - 36491 ep_pol ? 00:00:00 redis-server
5 S 0 34208 1 0 80 0 - 35979 ep_pol ? 00:00:00 redis-server
5 S 0 34213 1 0 80 0 - 35979 ep_pol ? 00:00:00 redis-server
5 S 0 34218 1 0 80 0 - 36491 ep_pol ? 00:00:00 redis-server
5 S 0 34223 1 0 80 0 - 36491 ep_pol ? 00:00:00 redis-server

执行关闭脚本并检查
[root@localhost redis-cluster]# ./stop-all.sh
[root@localhost redis-cluster]# netstat -tnlp | grep redis
[root@localhost redis-cluster]# ps -el | grep redis

一切没问题。

查看集群包括各个节点文件得到如下,文件结构没有问题:

../redis-cluster/
├── node6001
│  ├── appendonly.aof
│  ├── dump.rdb
│  ├── nodes-6001.conf
│  └── redis.conf
├── node6002
│  ├── appendonly.aof
│  ├── dump.rdb
│  ├── nodes-6002.conf
│  └── redis.conf
├── node6003
│  ├── appendonly.aof
│  ├── dump.rdb
│  ├── nodes-6003.conf
│  └── redis.conf
├── node6004
│  ├── appendonly.aof
│  ├── dump.rdb
│  ├── nodes-6004.conf
│  └── redis.conf
├── node6005
│  ├── appendonly.aof
│  ├── dump.rdb
│  ├── nodes-6005.conf
│  └── redis.conf
├── node6006
│  ├── appendonly.aof
│  ├── dump.rdb
│  ├── nodes-6006.conf
│  └── redis.conf
├── redis-trib.rb
├── start-all.sh
└── stop-all.sh

检查都没有问题,继续下一步。
nodes-*.conf这个文件代表集群文件

3、创建集群

执行创建脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@localhost redis-cluster]# ./redis-trib.rb create --replicas 1 192.168.18.140:6001 192.168.18.140:6002 192.168.18.140:6003 192.168.18.140:6004 192.168.18.140:6005 192.168.18.140:6006
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
192.168.18.140:6001
192.168.18.140:6002
192.168.18.140:6003
Adding replica 192.168.18.140:6005 to 192.168.18.140:6001
Adding replica 192.168.18.140:6006 to 192.168.18.140:6002
Adding replica 192.168.18.140:6004 to 192.168.18.140:6003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 9a21d2551ce8531661be5ee5f57eea2c61f03dba 192.168.18.140:6001
slots:0-5460 (5461 slots) master
M: 050dc8a4a6c58ebe7deda200463a035d19637ced 192.168.18.140:6002
slots:5461-10922 (5462 slots) master
M: 1642f2248dd4e98bdd81971613f0c2afb1531127 192.168.18.140:6003
slots:10923-16383 (5461 slots) master
S: 5b5bf9e6f0f4c84abbaa93a61a9b3d9168411a09 192.168.18.140:6004
replicates 050dc8a4a6c58ebe7deda200463a035d19637ced
S: ff5f6edf1fe07f953796ecd19dda50d3a934ca3f 192.168.18.140:6005
replicates 1642f2248dd4e98bdd81971613f0c2afb1531127
S: b71c3f2524dd94c801cf84a9c90577991e98d34f 192.168.18.140:6006
replicates 9a21d2551ce8531661be5ee5f57eea2c61f03dba
Can I set the above configuration? (type 'yes' to accept):

以上信息说明了集群配置OK,master和salve都分配好,是否确认创建集群。yes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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.18.140:6001)
M: 9a21d2551ce8531661be5ee5f57eea2c61f03dba 192.168.18.140:6001
slots:0-5460 (5461 slots) master
1 additional replica(s)
M: 050dc8a4a6c58ebe7deda200463a035d19637ced 192.168.18.140:6002
slots:5461-10922 (5462 slots) master
1 additional replica(s)
S: 5b5bf9e6f0f4c84abbaa93a61a9b3d9168411a09 192.168.18.140:6004
slots: (0 slots) slave
replicates 050dc8a4a6c58ebe7deda200463a035d19637ced
M: 1642f2248dd4e98bdd81971613f0c2afb1531127 192.168.18.140:6003
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: ff5f6edf1fe07f953796ecd19dda50d3a934ca3f 192.168.18.140:6005
slots: (0 slots) slave
replicates 1642f2248dd4e98bdd81971613f0c2afb1531127
S: b71c3f2524dd94c801cf84a9c90577991e98d34f 192.168.18.140:6006
slots: (0 slots) slave
replicates 9a21d2551ce8531661be5ee5f57eea2c61f03dba
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

集群创建成功!

验证、登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
redis-cli -c -h 192.168.37.131 -p 7001 ,其中-c表示以集群方式连接redis,-h指定ip地址,-p指定端口号
[root@localhost redis-cluster]# ../redis/bin/redis-cli -c -h 192.168.18.140 -p 6001

查看集群节点

192.168.18.140:6001> cluster nodes
050dc8a4a6c58ebe7deda200463a035d19637ced 192.168.18.140:6002@16002 master - 0 1550668881045 2 connected 5461-10922
5b5bf9e6f0f4c84abbaa93a61a9b3d9168411a09 192.168.18.140:6004@16004 slave 050dc8a4a6c58ebe7deda200463a035d19637ced 0 1550668879000 4 connected
9a21d2551ce8531661be5ee5f57eea2c61f03dba 192.168.18.140:6001@16001 myself,master - 0 1550668878000 1 connected 0-5460
1642f2248dd4e98bdd81971613f0c2afb1531127 192.168.18.140:6003@16003 master - 0 1550668879026 3 connected 10923-16383
ff5f6edf1fe07f953796ecd19dda50d3a934ca3f 192.168.18.140:6005@16005 slave 1642f2248dd4e98bdd81971613f0c2afb1531127 0 1550668879000 5 connected
b71c3f2524dd94c801cf84a9c90577991e98d34f 192.168.18.140:6006@16006 slave 9a21d2551ce8531661be5ee5f57eea2c61f03dba 0 1550668880036 6 connected

查看集群信息

192.168.18.140:6001> 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:829
cluster_stats_messages_pong_sent:843
cluster_stats_messages_sent:1672
cluster_stats_messages_ping_received:838
cluster_stats_messages_pong_received:829
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:1672
192.168.18.140:6001>

4、新添加主节点

准备好Redis节点192.168.18.140:6007并启动起来,执行添加节点命令如下:
./redis-trib.rb add-node 192.168.18.140:6007 192.168.18.140:6001

添加后的节点设置主节点或从节点

hash槽重新分配

添加完主节点需要对主节点进行hash槽分配这样该主节才可以存数据,就成为主节点。

redis集群有16384个槽,集群中的每个master结点分配一些槽,通过查看集群结点可以看到槽占用情况。
./redis-trib.rb reshard 192.168.18.140:6001

提示移动多少槽,输入值1000。

之后提示移动到那个节点,输入节点ID

之后提示我们指定转移哪几个几点的哈希槽,输入all 表示从所有的主节点中随机转移,凑够1000个哈希槽

然后再输入yes,redis集群就开始分配哈希槽了。

分配完成后这个节点也就成为主节点。

如果是把这个节点变成从节点,比如让新节点成为6001的从节点:

redis-cli -c -p 6007 cluster replicate 0b00721a509444db793d28448d8

命令后面的ID就是6001的ID

使用下面命令来确认一下6007是否已经成为6001的从节点

redis-cli -p 6001 cluster nodes | grep slave | grep 6001ID

5、删除节点

如果删除主节点:该节点有100个哈希槽,首先把节点的哈希槽转移到其他主节点,命令:
./redis-trib.rb reshard 192.168.18.140:6001

系统会提示我们要移动多少哈希槽,这里移动1000个,因为新节点有1000个哈希槽
然后系统提示我们输入要接收这些哈希槽的节点的ID
然后要我们选择从那些节点中转出哈希槽,这里一定要输入6007这个节点的ID,最后输入 done  表示输入完毕
最后使用命令删除节点
./redis-trib.rb del-node 192.168.18.140:6001  需要删除的节点ID

如果节点是从节点,直接使用删除命令即可。

redis-trib.rb具有以下功能:

create:创建集群 --replicas可以指定从节点个数
check:检查集群
info:查看集群信息
fix:修复集群
reshard:在线迁移slot
rebalance:平衡集群节点slot数量
add-node:将新节点加入集群
del-node:从集群中删除节点
set-timeout:设置集群节点间心跳连接的超时时间
call:在集群全部节点上执行命令
import:将外部redis数据导入集群

以集群方式登录任意节点,即可以使用这些命令

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testRedis() throws Exception {
// 创建并填充节点信息
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.18.140", 6001));
nodes.add(new HostAndPort("192.168.18.140", 6002));
nodes.add(new HostAndPort("192.168.18.140", 6003));
nodes.add(new HostAndPort("192.168.18.140", 6004));
nodes.add(new HostAndPort("192.168.18.140", 6005));
nodes.add(new HostAndPort("192.168.18.140", 6006));
// 创建集群对象
JedisCluster jedisCluster = new JedisCluster(nodes);
// 使用jedisCluster操作redis
String key = "cluster";
String setResult = jedisCluster.set(key, "hello redis cluster ");
System.out.println(setResult);
String getResult = jedisCluster.get(key);
System.out.println(getResult);
//关闭连接
jedisCluster.close();
}