redis cluster搭建与配置
众所周知redis cluster在运行过程中如果有master节点宕机,会通过集群选举而选举出新的master而替代故障节点,然而这个选举的过程到底是怎样的呢?网上有些文章写得有些出入,所以我从官方及实例入手实验分析一下。
官方redis cluster 教程地址:https://redis.io/topics/cluster-tutorial/
首先,我们从教程中了解到以下几点:
1.每个redis实例必须开放两个TCP端口,服务接口与服务端口值+10000,如:服务端口为6379,那么另一个端口即为16379.
服务端口自不必说,而另一端口为节点间的通信端口,采用二进制协议。
文中还提到,这个端口是cluster bus的通信端口,而cluster bus用于失败检测、配置更新、故障转移等,客户端永远不要尝试去连接此端口,并且确保防火墙已放开此端口权限。所以说redis cluster无法创建成功,很有可能是端口未开放。
2.redis cluster采用hash slot(槽)数据分片概念。一个集群的slot总数为16384,当用户set一个key时,会调用CRC16算法,将这个key用CRC16计算后与16384进行取模(HASH_SLOT = CRC16(key) mod 16384),最终定位到某个节点。
例如:有三台master节点,A、B、C,则slot分布如下:
节点 A hash slots 从 0 --> 5500.
节点 B hash slots 从 5501 --> 11000.
节点 C hash slots 从 11001 --> 16383.
并且添加与删除节点是非常容易的,而且不会造成“数据丢失”(这部分在后面会验证)。
3.redis的主从模型,与其他主从结构类似,同步过程这里就不细说了。
4.主从间不是强一致性的,例如:用户将某值写入节点A,节点A收到指令并执行,用户此时获得返回结果为“成功”。然而,主从间同步是需要耗时的,正好此时节点A宕机,而这部分数据又未同步到从节点上,集群通过选举将从节点选举为主节点,就导致了这部分未同步的数据丢失。
所以节点相关超时的配置就非常重要。
了解了以上相关概念,有助于我们接下来的实验分析。
一、搭建集群
我这里使用的是CentOS6.5,Redis版本为3.2.13,实例为6个,3主、3从。端口分别为6379、6380、6381、6382、6383、6384.
第一步:编译redis原代码
直接make即可,如果编译过程中缺少gcc等组件,安装即可。
编译完成后,进入redis/src/目录下,将相关执行文件拷贝至我们的redis安装目录下,这里为/usr/redis/bin/
如图所示,绿色的部分:
这里会用到redis-cli、redis-server与redis-trib.rb,其他自行处理。
第二步:修改配置文件
返回上级目录(即redis编译目录),找到redis.conf文件,复制到redis安装目录,这里为/urs/redis/conf/下。
如图所示:
然后进入redis安装目录,这里为/urs/redis/conf/,将此文件复制6份,分别命名为:
redis-6379.conf
redis-6380.conf
redis-6381.conf
redis-6382.conf
redis-6383.conf
redis-6384.conf
然后根据配置文件不同,修改以下相关参数:
#端口 port 6379 #开启集群模式 cluster-enabled yes #节点信息配置文件,如果在同一目录夏这里须指定为不同名称,无需手动创建 cluster-config-file nodes-6739.conf #节点超时时间,这个值比较重要 cluster-node-timeout 5000 #开启aof日志,需要开启,否则无法主从同步 appendonly yes #aof文件名,如果在不同环境下无需更改 appendfilename "appendonly-6379.aof" #pid文件名,如果在不同环境下无需更改 pidfile /usr/redis/pid/redis_6379.pid #rdb文件名,如果在不同环境下无需更改 dbfilename dump-6379.rdb #持久化路径,与dbfilename一同使用,这里为目录 dir /usr/redis/data/ #日志文件 logfile /usr/redis/logs/redis-6379.log #日志级别 loglevel notice #守护状态,即后台运行 protected-mode yes
注意:有些参数默认即可,因为我这里都部署在同一个环境下,所以将文件名修改为了不同。
第三步:启动redis
进入redis-server所在目录,这里为/usr/redis/bin/,执行以下命令:
./redis-server /usr/redis/conf/redis-6380.conf ./redis-server /usr/redis/conf/redis-6380.conf ./redis-server /usr/redis/conf/redis-6381.conf ./redis-server /usr/redis/conf/redis-6382.conf ./redis-server /usr/redis/conf/redis-6383.conf ./redis-server /usr/redis/conf/redis-6384.conf
启动成功后会显示相关进程,如下所示:
然后我们观察日志文件,发现输出了类似内容,如下:
21365:M 14 Jun 22:44:53.656 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
21365:M 14 Jun 22:44:53.656 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
21365:M 14 Jun 22:44:53.657 * No cluster configuration found, I'm b36dd72383d277c5b8a717bf942e168b64a5ad37
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 3.2.13 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in cluster mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 21365
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
21365:M 14 Jun 22:44:53.665 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
21365:M 14 Jun 22:44:53.666 # Server started, Redis version 3.2.13
21365:M 14 Jun 22:44:53.667 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
21365:M 14 Jun 22:44:53.667 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
21365:M 14 Jun 22:44:53.667 * The server is now ready to accept connections on port 6379
证明我们的redis实例已经启动成了,但是日志提示有些参数配置不合理,大家可以根据提示自行修改,这里就不示范了。
启动成功的同时在data与nodes目录下还产生了相关文件,如下所示:
├── bin
│ ├── redis-cli
│ ├── redis-server
│ └── redis-trib.rb
├── conf
│ ├── redis-6379.conf
│ ├── redis-6380.conf
│ ├── redis-6381.conf
│ ├── redis-6382.conf
│ ├── redis-6383.conf
│ └── redis-6384.conf
├── data
│ ├── appendonly-6379.aof
│ ├── appendonly-6380.aof
│ ├── appendonly-6381.aof
│ ├── appendonly-6382.aof
│ ├── appendonly-6383.aof
│ ├── appendonly-6384.aof
│ ├── dump-6379.rdb
│ ├── dump-6380.rdb
│ ├── dump-6381.rdb
│ ├── dump-6382.rdb
│ ├── dump-6383.rdb
│ └── dump-6384.rdb
├── logs
│ ├── redis-6379.log
│ ├── redis-6380.log
│ ├── redis-6381.log
│ ├── redis-6382.log
│ ├── redis-6383.log
│ └── redis-6384.log
├── nodes
│ ├── nodes-6379.conf
│ ├── nodes-6380.conf
│ ├── nodes-6381.conf
│ ├── nodes-6382.conf
│ ├── nodes-6383.conf
│ └── nodes-6384.conf
└── pid
└── redis_6379.pid
6 directories, 34 files
此时nodes的内容如下:
16c9b97246458952bd7c70c2f6cdd712c8581a7b :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
nodes-6380.conf:
d8c35ca14124060b7fab138f21d43fdcaa9cb2e4 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
nodes-6381.conf:
5d482c2bf6446ec31e31bb2ec6ac2271c1abfc87 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
nodes-6382.conf:
101413d85ccfeeae1f789eb111dca9bf3b809674 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
nodes-6383.conf:
285eee4004c3e664c9d363d65592ba74d39be4a2 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
nodes-6384.conf:
07b5c1b48ad68a9e5f9eb06602afad8f069a3965 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
然后通过命令:
./redis-cli -c -p 6379 -h 192.168.80.129
连接上redis,之后输入命令:
cluster slots
返回的结果为:(empty list or set),如下所示:
说明虽然启动成功,但集群还未建立。
第四步:建立集群
1)安装相关yum install ruby rubygems -y
2)安装gem-redisgem-redis下载地址:https://rubygems.org/gems/redis/versions
我这里选择的是3.2.1版本。
gem install -l redis-3.2.1.gem安装成功后会有如下提示:
3)复制redis-trib.rb文件
复制redis源代码目录下的redis-trib.rb文件至redis安装目录,因为之前我们已经复制过了,所以这里就不就赘述。
4)构建集群
进入redis-trib.rb所在目录,这里为/usr/redis/bin/,执行以下命令:
./redis-trib.rb create --replicas 1 192.168.80.129:6379 192.168.80.129:6380 192.168.80.129:6381 192.168.80.129:6382 192.168.80.129:6383 192.168.80.129:6384其中各参数的意义:
create:创建新的集群。
--replicas 1:每个master节点有1个从节点。
最后是参与集群的各节点IP与端口。
执行完命令后会打印出如下日志信息:
[admin@localhost bin]$ ./redis-trib.rb create --replicas 1 192.168.80.129:6379 192.168.80.129:6380 192.168.80.129:6381 192.168.80.129:6382 192.168.80.129:6383 192.168.80.129:6384 >>> Creating cluster >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 192.168.80.129:6379 192.168.80.129:6380 192.168.80.129:6381 Adding replica 192.168.80.129:6382 to 192.168.80.129:6379 Adding replica 192.168.80.129:6383 to 192.168.80.129:6380 Adding replica 192.168.80.129:6384 to 192.168.80.129:6381 M: b36dd72383d277c5b8a717bf942e168b64a5ad37 192.168.80.129:6379 slots:0-5460 (5461 slots) master M: f67b894fee139763fd06ab78c0c7b28b49523abb 192.168.80.129:6380 slots:5461-10922 (5462 slots) master M: 7fc6dfebba9c301b22b477037c1de811002d8cde 192.168.80.129:6381 slots:10923-16383 (5461 slots) master S: c171e677c203839cdb5224e1a90f660a41f84166 192.168.80.129:6382 replicates b36dd72383d277c5b8a717bf942e168b64a5ad37 S: 745801e34457d3e6bc4926a457a582627de28b5b 192.168.80.129:6383 replicates f67b894fee139763fd06ab78c0c7b28b49523abb S: b8b00b4e0f622211fa4a44aaa25ce25fc8738895 192.168.80.129:6384 replicates 7fc6dfebba9c301b22b477037c1de811002d8cde 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.80.129:6379) M: b36dd72383d277c5b8a717bf942e168b64a5ad37 192.168.80.129:6379 slots:0-5460 (5461 slots) master 1 additional replica(s) S: 745801e34457d3e6bc4926a457a582627de28b5b 192.168.80.129:6383 slots: (0 slots) slave replicates f67b894fee139763fd06ab78c0c7b28b49523abb S: c171e677c203839cdb5224e1a90f660a41f84166 192.168.80.129:6382 slots: (0 slots) slave replicates b36dd72383d277c5b8a717bf942e168b64a5ad37 S: b8b00b4e0f622211fa4a44aaa25ce25fc8738895 192.168.80.129:6384 slots: (0 slots) slave replicates 7fc6dfebba9c301b22b477037c1de811002d8cde M: 7fc6dfebba9c301b22b477037c1de811002d8cde 192.168.80.129:6381 slots:10923-16383 (5461 slots) master 1 additional replica(s) M: f67b894fee139763fd06ab78c0c7b28b49523abb 192.168.80.129:6380 slots:5461-10922 (5462 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
期间会提示我们输入一次yes,最终打印出[OK] All 16384 slots covered.即证明集群已经创建成功。
其中这部分内容比较关键:
显示出集群有6个节点,其中3个master节点,分别为:
192.168.80.129:6379
192.168.80.129:6380
192.168.80.129:6381
另外3个为从节点,主从关系如上图所示。
二、查看redis cluster相关信息
通过上面的操作,我们已经成功的创建了一个3主3从6个节点的redis cluster,然而他们是如何工作的,我们还不是特别了解,接下来我们就从相关日志及配置信息入手,查看redis集群的工作流程。
1.日志信息
通过上文,我们了解到6379、6380、6381这三台为master。那么我们就观察一下他们的日志情况,以6379为例:
21365:M 14 Jun 22:45:25.213 # IP address for this node updated to 192.168.80.129
21365:M 14 Jun 22:45:28.949 * Slave 192.168.80.129:6382 asks for synchronization
21365:M 14 Jun 22:45:28.949 * Full resync requested by slave 192.168.80.129:6382
21365:M 14 Jun 22:45:28.949 * Starting BGSAVE for SYNC with target: disk
21365:M 14 Jun 22:45:28.950 * Background saving started by pid 21375
21375:C 14 Jun 22:45:28.970 * DB saved on disk
21375:C 14 Jun 22:45:28.970 * RDB: 6 MB of memory used by copy-on-write
21365:M 14 Jun 22:45:29.050 * Background saving terminated with success
21365:M 14 Jun 22:45:29.052 * Synchronization with slave 192.168.80.129:6382 succeeded
21365:M 14 Jun 22:45:30.174 # Cluster state changed: ok
以上部分是创建集群过程中打印出的相关信息。
日志的主要内容为为192.168.80.129:6382从节点请求同步数据。然后开始bgsave,将数据保存。最后同步至从节点。
而此时从节点的日志如下:
21353:M 14 Jun 22:45:25.387 # IP address for this node updated to 192.168.80.129
21353:S 14 Jun 22:45:28.227 # Cluster state changed: ok
21353:S 14 Jun 22:45:28.948 * Connecting to MASTER 192.168.80.129:6379
21353:S 14 Jun 22:45:28.948 * MASTER <-> SLAVE sync started
21353:S 14 Jun 22:45:28.948 * Non blocking connect for SYNC fired the event.
21353:S 14 Jun 22:45:28.948 * Master replied to PING, replication can continue...
21353:S 14 Jun 22:45:28.949 * Partial resynchronization not possible (no cached master)
21353:S 14 Jun 22:45:28.951 * Full resync from master: c97db39d353882801e5ecd85641f52c7bb5e4abe:1
21353:S 14 Jun 22:45:29.050 * MASTER <-> SLAVE sync: receiving 77 bytes from master
21353:S 14 Jun 22:45:29.050 * MASTER <-> SLAVE sync: Flushing old data
21353:S 14 Jun 22:45:29.050 * MASTER <-> SLAVE sync: Loading DB in memory
21353:S 14 Jun 22:45:29.050 * MASTER <-> SLAVE sync: Finished with success
21353:S 14 Jun 22:45:29.051 * Background append only file rewriting started by pid 21376
21353:S 14 Jun 22:45:29.095 * AOF rewrite child asks to stop sending diffs.
21376:C 14 Jun 22:45:29.095 * Parent agreed to stop sending diffs. Finalizing AOF...
21376:C 14 Jun 22:45:29.095 * Concatenating 0.00 MB of AOF diff received from parent.
21376:C 14 Jun 22:45:29.095 * SYNC append only file rewrite performed
21376:C 14 Jun 22:45:29.095 * AOF rewrite: 6 MB of memory used by copy-on-write
21353:S 14 Jun 22:45:29.152 * Background AOF rewrite terminated with success
21353:S 14 Jun 22:45:29.153 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB)
21353:S 14 Jun 22:45:29.153 * Background AOF rewrite finished successfully
描述了从服务器通过NIO请求master进行同步的过程,值得注意的是,同步过程不仅仅包括rdb的内容,还包括aof部分内容。
2.nodes配置信息
c171e677c203839cdb5224e1a90f660a41f84166 192.168.80.129:6382 slave b36dd72383d277c5b8a717bf942e168b64a5ad37 0 1560566728243 4 connected
b8b00b4e0f622211fa4a44aaa25ce25fc8738895 192.168.80.129:6384 slave 7fc6dfebba9c301b22b477037c1de811002d8cde 0 1560566728750 6 connected
b36dd72383d277c5b8a717bf942e168b64a5ad37 192.168.80.129:6379 myself,master - 0 0 1 connected 0-5460
7fc6dfebba9c301b22b477037c1de811002d8cde 192.168.80.129:6381 master - 0 1560566727936 3 connected 10923-16383
f67b894fee139763fd06ab78c0c7b28b49523abb 192.168.80.129:6380 master - 0 1560566727936 2 connected 5461-10922
vars currentEpoch 6 lastVoteEpoch 0
f67b894fee139763fd06ab78c0c7b28b49523abb 192.168.80.129:6380 master - 0 1560566728847 2 connected 5461-10922
b36dd72383d277c5b8a717bf942e168b64a5ad37 192.168.80.129:6379 master - 0 1560566729867 1 connected 0-5460
745801e34457d3e6bc4926a457a582627de28b5b 192.168.80.129:6383 slave f67b894fee139763fd06ab78c0c7b28b49523abb 0 1560566730584 5 connected
b8b00b4e0f622211fa4a44aaa25ce25fc8738895 192.168.80.129:6384 slave 7fc6dfebba9c301b22b477037c1de811002d8cde 0 1560566728341 6 connected
c171e677c203839cdb5224e1a90f660a41f84166 192.168.80.129:6382 myself,slave b36dd72383d277c5b8a717bf942e168b64a5ad37 0 0 4 connected
vars currentEpoch 6 lastVoteEpoch 0
配置文件中配置了集群中所有节点的信息。
估计大家看到这里已经晕了,这nodes信息根本看不懂。其实不然,既然是配置信息,就注定有特定格式,而nodes的配置就遵循如下结构(以空格为分隔符):
<id> <ip:port> <flags> <master> <ping-sent> <pong-recv> <config-epoch> <link-state> <slot> <slot> ... <slot>解释如下:
id: 节点ID, 在节点创建是随机生成的一个40位的随即字符串,一旦被创建就无法再修改(除非调用CLUSTER RESET HARD命令重置集群)。
ip:port: 节点IP地址与端口地址。
flags: 标志列表,多个标志以逗号分隔(myself, master, slave, fail?, fail, handshake, noaddr, noflags)。
master: 如果为从节点,并且它的主节点已知,此参数为主节点的Node ID,否则为“-”。
ping-sent: Ping发起时的时间戳(单位:毫秒),如果为0,则无Ping操作。
pong-recv: 收到Pong时的时间戳(单位:毫秒)。
config-epoch: 该节点的epoch配置信息。
link-state: 连接状态(connected、disconnected)。
slot:被操作的slot信息。
一共这9个参数,我们重点学习一下flags、config-epoch与slot这三个重要参数。
1)flags
有如下参数:
myself:正在通信的节点。
master:节点为主节点。
slave: 节点为从节点。
fail?: 节点通信失败,但在逻辑上可能仍可以访问。也就是说,本次探测节点失败很有可能是其他原因造成的。
fail: 节点通信失败,并且多个其他节点也无法访问该节点。
noaddr: 该节点没有明确的地址。
noflags:无标志。
2)config-epoch
Redis Cluster 使用了类似于 Raft 算法 term(任期)的概念,Redis称为 epoch(纪元),用来给事件增加版本号。Redis 集群中的纪元主要是两种:currentEpoch 和 configEpoch。
这是一个集群节点配置相关的概念,每个集群节点都有自己独一无二的 configepoch。所谓的节点配置,实际上是指节点所负责的槽位信息。
每一个 master 在向其他节点发送包时,都会附带其 configEpoch 信息,以及一份表示它所负责的 slots 信息。而 slave 向其他节点发送包时,其包中的 configEpoch 和负责槽位信息,是其 master 的 configEpoch 和负责的 slot 信息。节点收到包之后,就会根据包中的 configEpoch 和负责的 slots 信息,记录到相应节点属性中。
更多内容请参考:https://redis.io/topics/cluster-spec
3)slot
slot有两种取值,一个是单一固定值,如:3987;另一种为范围值,如:1500-4000。
估计大家看过解释,还是有些懵,那么我们就举例说明一下,抽取几条现有的配置,与之对应就可以很轻松的解读出来了。
1)745801e34457d3e6bc4926a457a582627de28b5b
#节点IP+端口
2)192.168.80.129:6383
#节点类型,通过上文我们知道,6383是6380的从节点
3)slave
#因为本节点为从节点,所以该ID为主节点的ID,即6380的Node ID
4)f67b894fee139763fd06ab78c0c7b28b49523abb
#无Ping操作
5)0
#Pong返回时的时间戳,格式化后为:2019-06-15 10:45:29.255
6)1560566729255
#节点的epoch信息
7)5
#状态我已连接
8)connected
b36dd72383d277c5b8a717bf942e168b64a5ad37
#节点IP+端口
2)192.168.80.129:6379
#节点类型,6379为主节点,并且为自己
3)myself,master
#因为本节点为主节点,所以此项为“-”
4)-
#无Ping操作
5)0
#无Pong操作
6)0
#节点的epoch信息
7)1
#状态我已连接
8)connected
#slot信息
9)0-5460
除了查看配置文件外,我们还可以使用以下命令查看nodes信息:
./redis-cli -h 192.168.80.129 -p 6379 cluster nodes
更多redis nodes的说明请参考:https://redis.io/commands/cluster-nodes