Redis集群搭建与使用
前端时间开发中需要用到redis缓存数据,考虑到单台redis的不稳定性,后采用redis集群的方式来实现,由于之前没有接触过,过程中也是踩了不少的坑,拖了三天总算是搞定了,最近公司比较闲,总结了一下供大家参考,如有错误,请及时与我联系,万分感谢!
话不多说,开始正题。
一. 集群简单概念。
Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施(installation)。
Redis 集群不支持那些需要同时处理多个键的 Redis 命令, 因为执行这些命令需要在多个 Redis 节点之间移动数据, 并且在高负载的情况下, 这些命令将降低 Redis 集群的性能, 并导致不可预测的行为。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
Redis 集群提供了以下两个好处:
- 将数据自动切分(split)到多个节点的能力。
- 当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。
Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分哈希槽。 举个例子, 一个集群可以有三个哈希槽, 其中:
- 节点 A 负责处理 0 号至 5500 号哈希槽。
- 节点 B 负责处理 5501 号至 11000 号哈希槽。
- 节点 C 负责处理 11001 号至 16384 号哈希槽。
这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:
- 如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。
- 与此类似, 如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C , 然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。
因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。
为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作, Redis 集群对节点使用了主从复制功能: 集群中的每个节点都有 1 个至 N 个复制品(replica), 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。
在之前列举的节点 A 、B 、C 的例子中, 如果节点 B 下线了, 那么集群将无法正常运行, 因为集群找不到节点来处理 5501 号至 11000号的哈希槽。
另一方面, 假如在创建集群的时候(或者至少在节点 B 下线之前), 我们为主节点 B 添加了从节点 B1 , 那么当主节点 B 下线的时候, 集群就会将 B1 设置为新的主节点, 并让它代替下线的主节点 B , 继续处理 5501 号至 11000 号的哈希槽, 这样集群就不会因为主节点 B 的下线而无法正常运作了。
不过如果节点 B 和 B1 都下线的话, Redis 集群还是会停止运作。
Redis-cluster 架构图如下:
架构细节:
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
二. Redis Cluster搭建使用
要让集群正常工作至少需要3个主节点,在这里我们要创建6个redis节点,其中三个为主节点,三个为从节点,对应的redis节点的ip和端口对应关系如下(为了简单演示都在同一台机器上面)
127.0.0.1:7000
127.0.0.1:7001
127.0.0.1:7002
127.0.0.1:7003
127.0.0.1:7004
127.0.0.1:7005
1. 下载最新版redis。
wget http://download.redis.io/releases/redis-3.0.0.tar.gz
2. 解压,安装
tar xf redis-3.0.0.tar.gz cd redis-3.0.0 make && make install
3.创建存放多个实例的目录
mkdir /data/cluster -p cd /data/cluster mkdir 7000 7001 7002 7003 7004 7005
4.修改配置文件
cp redis-3.0.0/redis.conf /data/cluster/7000/
修改配置文件中下面选项
port 7000
daemonize yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
文件中的 cluster-enabled 选项用于开实例的集群模式, 而 cluster-conf-file 选项则设定了保存节点配置文件的路径, 默认值为nodes.conf 。其他参数相信童鞋们都知道。节点配置文件无须人为修改, 它由 Redis 集群在启动时创建, 并在有需要时自动进行更新。
修改完成后,把修改完成的redis.conf复制到7001-7005目录下,并且端口修改成和文件夹对应。
5.分别启动6个redis实例。
cd /data/cluster/7000 redis-server redis.conf cd /data/cluster/7001 redis-server redis.conf cd /data/cluster/7002 redis-server redis.conf cd /data/cluster/7003 redis-server redis.conf cd /data/cluster/7004 redis-server redis.conf cd /data/cluster/7005 redis-server redis.conf
查看进程否存在。
[root@redis-server 7005]# ps -ef | grep redis root 4168 1 0 11:49 ? 00:00:00 redis-server *:7000 [cluster] root 4176 1 0 11:49 ? 00:00:00 redis-server *:7001 [cluster] root 4186 1 0 11:50 ? 00:00:00 redis-server *:7002 [cluster] root 4194 1 0 11:50 ? 00:00:00 redis-server *:7003 [cluster] root 4202 1 0 11:50 ? 00:00:00 redis-server *:7004 [cluster] root 4210 1 0 11:50 ? 00:00:00 redis-server *:7005 [cluster] root 4219 4075 0 11:50 pts/2 00:00:00 grep redis
6.执行命令创建集群,首先安装依赖,否则创建集群失败。
apt-get install ruby rubygems
安装gem-redis
下载地址:https://rubygems.org/gems/redis/versions/3.0.0
gem install redis
复制集群管理程序到/usr/local/bin
cp redis-3.0.0/src/redis-trib.rb /usr/local/bin/redis-trib
创建集群:
redis-trib create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
命令的意义如下:
- 给定 redis-trib.rb 程序的命令是 create , 这表示我们希望创建一个新的集群。
- 选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
- 之后跟着的其他参数则是实例的地址列表, 我们希望程序使用这些地址所指示的实例来创建新集群。
简单来说, 以上命令的意思就是让 redis-trib 程序创建一个包含三个主节点和三个从节点的集群。
接着, redis-trib 会打印出一份预想中的配置给你看, 如果你觉得没问题的话, 就可以输入 yes , redis-trib 就会将这份配置应用到集群当中:
>>> Creating cluster Connecting to node 127.0.0.1:7000: OK Connecting to node 127.0.0.1:7001: OK Connecting to node 127.0.0.1:7002: OK Connecting to node 127.0.0.1:7003: OK Connecting to node 127.0.0.1:7004: OK Connecting to node 127.0.0.1:7005: OK >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 Adding replica 127.0.0.1:7003 to 127.0.0.1:7000 Adding replica 127.0.0.1:7004 to 127.0.0.1:7001 Adding replica 127.0.0.1:7005 to 127.0.0.1:7002 M: 2774f156af482b4f76a5c0bda8ec561a8a1719c2 127.0.0.1:7000 slots:0-5460 (5461 slots) master M: 2d03b862083ee1b1785dba5db2987739cf3a80eb 127.0.0.1:7001 slots:5461-10922 (5462 slots) master M: 0456869a2c2359c3e06e065a09de86df2e3135ac 127.0.0.1:7002 slots:10923-16383 (5461 slots) master S: 37b251500385929d5c54a005809377681b95ca90 127.0.0.1:7003 replicates 2774f156af482b4f76a5c0bda8ec561a8a1719c2 S: e2e2e692c40fc34f700762d1fe3a8df94816a062 127.0.0.1:7004 replicates 2d03b862083ee1b1785dba5db2987739cf3a80eb S: 9923235f8f2b2587407350b1d8b887a7a59de8db 127.0.0.1:7005 replicates 0456869a2c2359c3e06e065a09de86df2e3135ac Can I set the above configuration? (type 'yes' to accept):
输入 yes 并按下回车确认之后, 集群就会将配置应用到各个节点, 并连接起(join)各个节点 —— 也即是, 让各个节点开始互相通讯:
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 127.0.0.1:7000) M: 2774f156af482b4f76a5c0bda8ec561a8a1719c2 127.0.0.1:7000 slots:0-5460 (5461 slots) master M: 2d03b862083ee1b1785dba5db2987739cf3a80eb 127.0.0.1:7001 slots:5461-10922 (5462 slots) master M: 0456869a2c2359c3e06e065a09de86df2e3135ac 127.0.0.1:7002 slots:10923-16383 (5461 slots) master M: 37b251500385929d5c54a005809377681b95ca90 127.0.0.1:7003 slots: (0 slots) master replicates 2774f156af482b4f76a5c0bda8ec561a8a1719c2 M: e2e2e692c40fc34f700762d1fe3a8df94816a062 127.0.0.1:7004 slots: (0 slots) master replicates 2d03b862083ee1b1785dba5db2987739cf3a80eb M: 9923235f8f2b2587407350b1d8b887a7a59de8db 127.0.0.1:7005 slots: (0 slots) master replicates 0456869a2c2359c3e06e065a09de86df2e3135ac [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
一切正常输出以下信息:
[OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
集群的客户端
Redis 集群现阶段的一个问题是客户端实现很少。 以下是一些我知道的实现:
- redis-rb-cluster 是 Ruby 实现, 用于作为其他实现的参考。 该实现是对 redis-rb 的一个简单包装, 高效地实现了与集群进行通讯所需的最少语义(semantic)。
- redis-py-cluster 看上去是 redis-rb-cluster 的一个 Python 版本, 这个项目有一段时间没有更新了(最后一次提交是在六个月之前), 不过可以将这个项目用作学习集群的起点。
- 流行的 Predis 曾经对早期的 Redis 集群有过一定的支持, 但我不确定它对集群的支持是否完整, 也不清楚它是否和最新版本的 Redis 集群兼容 (因为新版的 Redis 集群将槽的数量从 4k 改为 16k 了)。
- Redis unstable 分支中的 redis-cli 程序实现了非常基本的集群支持, 可以使用命令 redis-cli -c 来启动。
测试 Redis 集群比较简单的办法就是使用 redis-rb-cluster 或者 redis-cli , 接下来我们将使用 redis-cli 为例来进行演示:
[root@redis-server ~]# redis-cli -c -p 7001 127.0.0.1:7001> set name yayun OK 127.0.0.1:7001> get name "yayun" 127.0.0.1:7001>
我们可以看看还有哪些命令可以用:
[root@redis-server ~]# redis-trib help Usage: redis-trib <command> <options> <arguments ...> set-timeout host:port milliseconds add-node new_host:new_port existing_host:existing_port --master-id <arg> --slave fix host:port help (show this help) del-node host:port node_id import host:port --from <arg> check host:port call host:port command arg arg .. arg create host1:port1 ... hostN:portN --replicas <arg> reshard host:port --yes --to <arg> --from <arg> --slots <arg> For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster. [root@redis-server ~]#
可以看见有add-node,不用想了,肯定是添加节点。那么del-node就是删除节点。还有check肯定就是检查状态了。
[root@redis-server ~]# redis-cli -p 7000 cluster nodes 2d03b862083ee1b1785dba5db2987739cf3a80eb 127.0.0.1:7001 master - 0 1428293673322 2 connected 5461-10922 37b251500385929d5c54a005809377681b95ca90 127.0.0.1:7003 slave 2774f156af482b4f76a5c0bda8ec561a8a1719c2 0 1428293672305 4 connected e2e2e692c40fc34f700762d1fe3a8df94816a062 127.0.0.1:7004 slave 2d03b862083ee1b1785dba5db2987739cf3a80eb 0 1428293674340 5 connected 0456869a2c2359c3e06e065a09de86df2e3135ac 127.0.0.1:7002 master - 0 1428293670262 3 connected 10923-16383 2774f156af482b4f76a5c0bda8ec561a8a1719c2 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460 9923235f8f2b2587407350b1d8b887a7a59de8db 127.0.0.1:7005 slave 0456869a2c2359c3e06e065a09de86df2e3135ac 0 1428293675362 6 connected [root@redis-server ~]#
可以看到7000-7002是master,7003-7005是slave。
故障转移测试:
127.0.0.1:7001> KEYS * 1) "name" 127.0.0.1:7001> get name "yayun" 127.0.0.1:7001>
可以看见7001是正常的,并且获取到了key,value,现在kill掉7000实例,再进行查询。
[root@redis-server ~]# ps -ef | grep 7000 root 4168 1 0 11:49 ? 00:00:03 redis-server *:7000 [cluster] root 4385 4361 0 12:39 pts/3 00:00:00 grep 7000 [root@redis-server ~]# kill 4168 [root@redis-server ~]# ps -ef | grep 7000 root 4387 4361 0 12:39 pts/3 00:00:00 grep 7000 [root@redis-server ~]# redis-cli -c -p 7001 127.0.0.1:7001> get name "yayun" 127.0.0.1:7001>
可以正常获取到value,现在看看状态。
[root@redis-server ~]# redis-cli -c -p 7001 cluster nodes 2d03b862083ee1b1785dba5db2987739cf3a80eb 127.0.0.1:7001 myself,master - 0 0 2 connected 5461-10922 0456869a2c2359c3e06e065a09de86df2e3135ac 127.0.0.1:7002 master - 0 1428295271619 3 connected 10923-16383 37b251500385929d5c54a005809377681b95ca90 127.0.0.1:7003 master - 0 1428295270603 7 connected 0-5460 e2e2e692c40fc34f700762d1fe3a8df94816a062 127.0.0.1:7004 slave 2d03b862083ee1b1785dba5db2987739cf3a80eb 0 1428295272642 5 connected 2774f156af482b4f76a5c0bda8ec561a8a1719c2 127.0.0.1:7000 master,fail - 1428295159553 1428295157205 1 disconnected 9923235f8f2b2587407350b1d8b887a7a59de8db 127.0.0.1:7005 slave 0456869a2c2359c3e06e065a09de86df2e3135ac 0 1428295269587 6 connected [root@redis-server ~]#
原来的7000端口实例已经显示fail,原来的7003是slave,现在自动提升为master。
注:因本人技术有限,暂时能想到的也只有这些了,后续有什么新的发现会及时更新到这篇随笔的最后面。(纯个人理解,如有错误请及时与我联系,谢谢)