Redis 是一个开源的 key-value 存储系统,由于出众的性能,大部分互联网企业都用来做服务器端缓存。Redis 在3.0版本前只支持单实例模式,虽然支持主从模式、哨兵模式部署来解决单点故障,但是现在互联网企业动辄大几百G的数据,可完全是没法满足业务的需求,所以,Redis 在 3.0 版本以后就推出了集群模式。

 

Redis 集群采用了P2P的模式,完全去中心化。Redis 把所有的 Key 分成了 16384 个 slot,每个 Redis 实例负责其中一部分 slot 。集群中的所有信息(节点、端口、slot等),都通过节点之间定期的数据交换而更新。

Redis 客户端可以在任意一个 Redis 实例发出请求,如果所需数据不在该实例中,通过重定向命令引导客户端访问所需的实例。

 

安装环境

  • OS: centos 7.2.1511

  • redis : 最新版 v4.0.1

 

单台redis安装

下载

wget http://download.redis.io/releases/redis-4.0.10.tar.gz
tar zxvf redis-4.0.10.tar.gz
mv redis-4.0.10 /usr/local/redis/

如果是离线安装,则需要从官网下载指定版本,然后上传到生产环境。这里安装的是最新版redis,指定安装路径 /usr/local/redis/

 

编译安装

cd /usr/local/redis
make
make install

 

安装完成,这时候会在/usr/local/bin/目录下看到redis-server、redis-cli等可执行脚本,进入看一下,如果没有,就要去解压目录复制进去了。

Redis集群搭建

 

配置redis.conf

redis.conf在默认在安装目录下

$ vim /usr/local/redis/redis.conf

这里要修改两个地方,一个bind和daemonize就行。

 

要注意的点:

  • bind这里配置要注意,默认是只有一个127.0.0.1,这个时候只能自己连接,其他局域网内是连接不上的。所以,需要配置多个 IP ,这样就可以局域网内进行连接了。我设置的0.0.0.0,虽然这不太安全,方便我在其它地方连接。

  • daemonize是设置是否后台启动 Redis,默认no,正常都需要以服务形式启动 Redis,所以这里设置为yes。

  • 可以设置密码 requirepass mypassord

  • redis v3.0+版本增加了保护机制,默认protected-mode yes, 这样目标机器调用可能会报错,需要设置为no

 

关于更多配置文件参数的解释,可以参考:

https://github.com/linli8/cnblogs/blob/master/redis%E5%89%AF%E6%9C%AC.conf

 

启动redis

cd /usr/local/bin/
redis-server /usr/local/redis/redis.conf

 

测试redis服务

端口已开启:

netstat -anp | grep 6379

 

命令行连接:

➜  redis redis-cli
127.0.0.1:6379> keys *
1) "city"
2) "usage"
3) "idc"
4) "region"
127.0.0.1:6379> set name jzhou
OK
127.0.0.1:6379> get name
"jzhou"
127.0.0.1:6379>

 

关闭服务

可以通过杀进程的方式强制关闭服务,也可以通过命令:

redis-cli shutdown

通过 netstat 可以看出来端口已经是TIME_WAIT状态了

 

以上是centos7单机部署redis的过程,下面集群搭建和上述类似,不过在目录结构和配置文件不同,当然,遇到的坑也多,主要配置集群依赖一些外部包,但说实话光搭建个集群是没有啥技术含量但.....

 

redis集群搭建

简介

redis-cluster架构设计

Redis集群搭建

架构细节:

  1. 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

  2. 节点的fail是通过集群中超过半数的节点检测失效时才生效.

  3. 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

  4. redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key

  5. Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。

 

环境准备

Redis集群中要求奇数节点,所以至少要有三个节点,并且每个节点至少有一备份节点,所以至少需要6个redis服务实例。

 

这里演示的是我搭的生产环境redis集群,3台服务器,每台起3个服务,共9个节点,生产环境搭建的很顺利,但测试环境一台服务器开6个端口遇到了很多问题,各种依赖包问题,后续会说明。

 

三台服务器

172.28.37.29
172.28.37.30
172.18.38.219
(每台服务器redis三个端口号 7000~7002)

按照上面搭建单台redis服务的方式在三台服务器分别安装redis,从配置文件开始会有所不同。下面会说明搭建过程。

 

依赖包安装

注意,下述均安装最新版本,否则会和redis版本不匹配

lib
zlib
ruby
rubugems

 

开始集群搭建

1. 准备目录结构

三台机器一样,建立如下目录结构:

$ mkdir -p /usr/local/redis/redis-cluster/{7000,7001,7002}

 

分别进入每个端口目录创建配置文件:

cd  /usr/local/redis/redis-cluster/7000 && touch redis.conf
port 7000   # 端口7000,7001,7002,与目录对应
bind 172.28.37.29 #默认ip为127.0.0.1,需要改为其他节点机器可访问的ip,否则创建集群时无法访问对应的端口,无法创建集群
daemonize yes   #redis后台运行
cluster-enabled yes  #开启集群
cluster-config-file nodes_7000.conf  #集群的配置,配置文件首次启动自动生成 7000,7001,7002  
cluster-node-timeout 8000   #请求超时,默认15秒,可自行设置
appendonly yes  #开启aof持久化模式,每次写操作请求都追加到appendonly.aof文件中
appendfsync always  #每次有写操作的时候都同步
logfile "/data/redis/logs/redis.log" #redis服务日志
pidfile /var/run/redis_7000.pid  #pidfile文件对应7000,7001,7002

注意,上述有些配置项要对应服务和目录,三个目录按照上述配置好后,启动服务

 

3.启动/关闭集群服务

可以在每个服务器上写一个启动脚本start-redis.sh:

for((i=0;i<3;i++)); 
do /usr/local/bin/redis-server /usr/local/redis/redis-cluster/700$i/redis.conf; 
done

 

关闭服务类似:

for((i=0;i<=2;i++));
do /usr/local/bin/redis-cli -c -h $IP -p 700$i shutdown; 
done

$IP分别为三台服务器IP。

 

这时只是启动了9个单独的redis服务,它们还不是一个集群,下面就说明创建集群

 

4.创建集群

注意:在任意一台上运行 不要在每台机器上都运行,一台就够了

 

Redis 官方提供了 redis-trib.rb 这个工具,就在解压目录的 src 目录中

 

在其中一台执行:

$ cd /root/redis-4.0.10/src
$ ./redis-trib.rb create --replicas 1 172.28.37.29:7000 172.28.37.29:7001 172.28.37.29:7002 172.28.37.30:7000 172.28.37.30:7001 172.28.37.30:7002 172.18.38.219:7000 172.18.38.219:7001 172.18.38.219:7002

 

敲完这个命令后会提示是否按照默认的推荐方式配置集群主从,一般选yes就行了

Redis集群搭建

截图中看出,推荐了4个masters,5个从节点

 

>>> Creating cluster
>>> Performing hash slots allocation on 9 nodes...
Using 4 masters:
172.28.37.29:7000
172.28.37.30:7000
172.18.38.219:7000
172.28.37.29:7001
Adding replica 172.18.38.219:7001 to 172.28.37.29:7000
Adding replica 172.28.37.29:7002 to 172.28.37.30:7000
Adding replica 172.28.37.30:7002 to 172.18.38.219:7000
Adding replica 172.18.38.219:7002 to 172.28.37.29:7001
Adding replica 172.28.37.30:7001 to 172.28.37.29:7000

 

下面这个显示了集群和slot分配结果

Redis集群搭建

 

5.集群验证

参数 -C 可连接到集群,因为 redis.conf 将 bind 改为了ip地址,所以 -h 参数不可以省略,-p 参数为端口号

[root@172-28-37-29 src]# redis-cli -c -p 7000 -h 172.28.37.29
172.28.37.29:7000> set name zhoujie
-> Redirected to slot [5798] located at 172.28.37.30:7000
OK
172.28.37.30:7000> get name
"zhoujie"
172.28.37.30:7000>

可以看到在29的7000上设置了name,重定向到了30的7000节点。到此为止集群搭建成功!

 

友情提示:

当出现集群无法启动时,删除集群配置文件,再次重新启动每一个redis服务,然后重新构件集群环境。

 

redis-trib.rb命令常见用法

1)列出集群节点-cluster nodes

[root@172-28-37-30 src]# redis-cli -h 172.28.37.30  -c -p 7000
172.28.37.30:7000> cluster nodes
0d260c47f10ecbbfd9c3c1707da82a3dd7951313 172.18.38.219:7001@17001 slave f85a9a80aca5e4c4a1437c7b58abd5895ee66855 1531497002061 8 connected
6d3db545319a8d41f4ad0666885856257fc2ab5f 172.18.38.219:7002@17002 slave 0cdeba26690238582ad7705abac6de71d1817c9e 1531497002062 9 connected
3f6c1449e60fe0d868e0bdc655165419ed7cd193 172.18.38.219:7000@17000 master - 1531497003000 7 connected 8192-12287
4004ad507da2e7cbae465d7f01864d53972f595c 172.28.37.30:7002@17002 slave 3f6c1449e60fe0d868e0bdc655165419ed7cd193 1531497003062 7 connected
f85a9a80aca5e4c4a1437c7b58abd5895ee66855 172.28.37.29:7000@17000 master - 1531497004064 1 connected 0-4095
7fc5072406e47cd58822f17f5e1ce3b15328352e 172.28.37.30:7000@17000 myself,master - 1531497002000 4 connected 4096-8191
fb4554a4fa2bd4af1c213b90ec32d3f7fb12e87b 172.28.37.30:7001@17001 slave f85a9a80aca5e4c4a1437c7b58abd5895ee66855 1531497005066 5 connected
967243e807a1c0c32ad56db47007c54a2d1d6e2e 172.28.37.29:7002@17002 slave 7fc5072406e47cd58822f17f5e1ce3b15328352e 1531497004064 4 connected
0cdeba26690238582ad7705abac6de71d1817c9e 172.28.37.29:7001@17001 master - 1531497004064 2 connected 12288-16383
172.28.37.30:7000>

 

2)查看集群信息- cluster info

172.28.37.30:7000> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:9
cluster_size:4
cluster_current_epoch:9
cluster_my_epoch:4
cluster_stats_messages_ping_sent:2243335
cluster_stats_messages_pong_sent:2220197
cluster_stats_messages_meet_sent:4
cluster_stats_messages_sent:4463536
cluster_stats_messages_ping_received:2220192
cluster_stats_messages_pong_received:2243339
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:4463536
172.28.37.30:7000>

 

3)检查集群状态-check

下面内容是测试环境搭建的,一台服务器6个节点

[root@192-168-151-110 redis]# /root/redis-4.0.10/src/redis-trib.rb check 192.168.151.110:7000
>>> Performing Cluster Check (using node 192.168.151.110:7000)
M: 7bf58adaafbfee1643785ea7b5da723d6595bbaf 192.168.151.110:7000
  slots:0-5460 (5461 slots) master
additional replica(s)
S: 90072487e6227544c079b2d8214ef5fd050575b5 192.168.151.110:7004
  slots: (0 slots) slave
  replicates f7374d1b48562c9e54523948915c99bb00a12ba7
M: c6cf17370fc94aabc88e862fa72229ebd9b94166 192.168.151.110:7002
  slots:10923-16383 (5461 slots) master
additional replica(s)
S: 59d25294cbfb63f2d4d75410e66ce58899c65469 192.168.151.110:7003
  slots: (0 slots) slave
  replicates 7bf58adaafbfee1643785ea7b5da723d6595bbaf
S: 6256051389337640691e4b44d7de2b7b8c8fa25f 192.168.151.110:7005
  slots: (0 slots) slave
  replicates c6cf17370fc94aabc88e862fa72229ebd9b94166
M: f7374d1b48562c9e54523948915c99bb00a12ba7 192.168.151.110:7001
  slots:5461-10922 (5462 slots) master
additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
[root@192-168-151-110 redis]#

 

4)其它常用命令

  • create:创建一个集群环境host1:port1 ... hostN:portN(集群中的主从节点比例)

  • call:可以执行redis命令

  • add-node:将一个节点添加到集群里,第一个参数为新节点的ip:port,第二个参数为集群中任意一个已经存在的节点的ip:port

  • del-node:移除一个节点

  • reshard:重新分片

 

集群操作

//集群(cluster)

CLUSTER INFO 打印集群的信息

CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。

//节点(node)

CLUSTER MEET <ip> <port> 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。

CLUSTER FORGET <node_id> 从集群中移除 node_id 指定的节点。

CLUSTER REPLICATE <node_id> 将当前节点设置为 node_id 指定的节点的从节点。

CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。

//槽(slot)

CLUSTER ADDSLOTS <slot> [slot ...] 将一个或多个槽(slot)指派(assign)给当前节点。

CLUSTER DELSLOTS <slot> [slot ...] 移除一个或多个槽对当前节点的指派。

CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。

CLUSTER SETSLOT <slot> NODE <node_id> 将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。

CLUSTER SETSLOT <slot> MIGRATING <node_id> 将本节点的槽 slot 迁移到 node_id 指定的节点中。

CLUSTER SETSLOT <slot> IMPORTING <node_id> 从 node_id 指定的节点中导入槽 slot 到本节点。

CLUSTER SETSLOT <slot> STABLE 取消对槽 slot 的导入(import)或者迁移(migrate)。

//键 (key)

CLUSTER KEYSLOT <key> 计算键 key 应该被放置在哪个槽上。

CLUSTER COUNTKEYSINSLOT <slot> 返回槽 slot 目前包含的键值对数量。

CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 个 slot 槽中的键。

 

向集群中添加节点或删除节点

  • 我们新建两个服务,按照之前搭建的集群方式新增俩个节点:(一主一从 master、slave)。

  • Mater:7007 slave:7008

  • 创建7007/7008文件夹。拷贝redis.conf文件到对于的7007,7008目录下 ,再进行修改配置文件。

  • 启动7007和7008俩个服务并查看服务状态。

 

1. 集群中添加一个主节点

步骤一:使用add-node命令:绿色为新增节点,红色为已知存在节点

[root@wlan124 local]# /usr/local/redis/src/redis-trib.rb add-node 192.168.151.110:7007   
192.168.151.110:7001

注意:当添加节点成功以后,新增的节点不会有任何数据,因为他没有分配任何slot。需要为新节点手动分配slot。

 

步骤二:reshard命令,分配slot:

[root@wlan124 local]# /usr/local/redis-3.0.0/src/redis-trib.rb reshard  
192.168.151.110:7007

 

提示一

How many slots do you want to move (from to 16384)? 2000

 

提示二

What is the receiving node ID? 382634a4025778c040b7213453fd42a709f79e28

Please enter all the source node IDs.

 Type 'all' to use all the nodes as source nodes for the hash slots.

 Type 'done' once you entered all the source nodes IDs.

Source node #1:all

 

提示三

Do you want to proceed with the proposed reshard plan (yes/no)? yes

 

  1. 提示一:是希望你需要多少个槽移动到新的节点上,可以自己设置,比如200个槽。

  2. 提示二:是你需要把这200个slot槽移动到那个节点上去(需要指定节点id),并且下个提示是输入all为从所有主节点(7001 7002 7003)中分别抽取相应的槽数(一共为200个槽到指定的新节点中!,并且会打印执行分片的计划。)

  3. 提示三:输入yes确认开始执行分片任务

 

2. 集群中添加一个从节点

步骤一:使用add-node命令:绿色为新增节点,红色为已知存在节点

[root@wlan124 local]# /usr/local/redis/src/redis-trib.rb add-node 192.168.151.110:7008  
192.168.151.110:7001

 

步骤二:首先需要登录到7008节点的客户端,然后使用集群命令,执行replicate命令来指定当前节点的主节点id为哪一个。把当前的7008(slave)节点指定到一个主节点下(这里使用之前创建的7007主节点,绿色表示节点id)。

[root@wlan124 ~]# /usr/local/redis/bin/redis-cli -c -h 192.168.151.110 -p 7008

192.168.1.124:7008> cluster replicate 4d4cb840519eef342a5730168b6c7e14dd811542

7007的id)

OK

 

3.集群中删除一个主节点

 如果主节点有从节点,将从节点转移到其他主节点。如果主节点有slot,先将主节点里的slot分配到其他可用节点中,然后再删除节点才行,否则会有数据的丢失。

 

步骤一:删除7007(master)节点之前,我们需要先把其全部的数据(slot槽)移动到其他节点上去(目前只能把master的数据迁移到一个节点上,暂时做不了平均分配功能)。

[root@wlan124 ~]# /usr/local/redis/src/redis-trib.rb reshard 192.168.151.110:7007

How many slots do you want to move (from to 16384)? 1999

(注释:这里不会是正好200个槽)

What is the receiving node ID? 614d0def75663f2620b6402a017014b57c912dad

(注释:这里是需要把数据移动到哪?7001的主节点id)

Please enter all the source node IDs.

 Type 'all' to use all the nodes as source nodes for the hash slots.

 Type 'done' once you entered all the source nodes IDs.

Source node #1:4d4cb840519eef342a5730168b6c7e14dd811542

(注释:这里是需要数据源,也就是我们的7007节点id)

Source node #2:done

(注释:这里直接输入done 开始生成迁移计划)

Do you want to proceed with the proposed reshard plan (yes/no)? yes

(注释:这里输入yes开始迁移)

 

步骤二:最后我们直接使用del-node命令删除7007主节点即可(蓝色表示7007的节点id)。

[root@wlan124 ~]# /usr/local/redis-3.0.0/src/redis-trib.rb del-node 192.168.151.110:7007 4d4cb840519eef342a5730168b6c7e14dd811542

 

4.集群中删除一个从节点

步骤一:删除从节点7008,输入del-node命令,指定删除节点ip和端口,以及节点id(蓝色为7008节点id),移除了7008 slave节点,前节点的服务进程也会随之销毁。

[root@wlan124 ~]# /usr/local/redis-3.0.0/src/redis-trib.rb del-node 192.168.151.110:7008 a78c8a41f6430b51a7eca1fdb50092c463a8f1ac

 

nodejs连接redis集群示例

目前用得最多的 Node.js Redis 库是 node redis,不过这个库基本已经不再维护了,存在很多 bug(在生产环境中碰到过),也缺失了很多功能(如 pipeling 和脚本优化)。而 ioredis 不仅支持了 Cluster 和 Sentinel,还在 API 层面和 node redis 保持了兼容。

const Redis = require('ioredis');
let redis_members = [{
 port: 7000,
 host: '172.28.37.29'
}, {
 port: 7001,
 host: '172.28.37.29'
}, {
 port: 7002,
 host: '172.28.37.29'
}, {
 port: 7000,
 host: '172.28.37.30'
}, {
 port: 7001,
 host: '172.28.37.30'
}, {
 port: 7002,
 host: '172.28.37.30'
}, {
 port: 7000,
 host: '172.18.38.219'
}, {
 port: 7001,
 host: '172.18.38.219'
}, {
 port: 7002,
 host: '172.18.38.219'
}]
var cluster = new Redis.Cluster(redis_members);

cluster.set('foo', 'bar');
cluster.get('foo', function (err, res) {
 console.log('=====',res) //===== bar
});

 

你以为结束了?其实还没有!

 

集群搭建过程的各种奇葩错误这里汇总,当然下面错误不是一定会遇到,但不保证一定不会不遇到。生产环境我搭的挺顺利,但是测试环境几乎所有能遇到的问题全都遇到了,最后都解决了,特此记录!