集群简介
Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施(installation)。
Redis 集群不支持那些需要同时处理多个键的 Redis 命令, 因为执行这些命令需要在多个 Redis 节点之间移动数据, 并且在高负载的情况下, 这些命令将降低 Redis 集群的性能, 并导致不可预测的行为。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
Redis 集群提供了以下两个好处:
- 将数据自动切分(split)到多个节点的能力。
- 当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力
Redis 集群数据共享
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 就可以了。
因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。
Redis 集群中的主从复制
为了使得集群在一部分节点下线或者无法与集群的大多数(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 集群还是会停止运作。
开始搭建
要让集群正常工作至少需要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-stable.tar.gz
2. 解压到/usr/local/redis/安装
# cd /usr/local/redis/
#ls -l
-rw-r--r-- 1 root root 131381 Oct 1 20:41 00-RELEASENOTES
-rw-r--r-- 1 root root 53 Oct 1 20:41 BUGS
-rw-r--r-- 1 root root 1815 Oct 1 20:39 CONTRIBUTING
-rw-r--r-- 1 root root 1487 Oct 1 20:39 COPYING
drwxr-xr-x 6 root root 4096 Oct 1 20:41 deps
-rw-r--r-- 1 root root 11 Oct 1 20:39 INSTALL
-rw-r--r-- 1 root root 151 Oct 1 20:39 Makefile
-rw-r--r-- 1 root root 4223 Oct 1 20:39 MANIFESTO
-rw-r--r-- 1 root root 20530 Oct 1 20:39 README.md
-rw-r--r-- 1 root root 57764 Oct 1 20:39 redis.conf
-rwxr-xr-x 1 root root 271 Oct 1 20:39 runtest
-rwxr-xr-x 1 root root 280 Oct 1 20:39 runtest-cluster
-rwxr-xr-x 1 root root 281 Oct 1 20:39 runtest-sentinel
-rw-r--r-- 1 root root 7606 Oct 1 20:39 sentinel.conf
drwxr-xr-x 3 root root 4096 Oct 1 20:41 src
drwxr-xr-x 10 root root 4096 Oct 1 20:41 tests
drwxr-xr-x 8 root root 4096 Oct 1 20:41 utils
#make && make install
3. 创建目录
mkdir /data/redis/cluster -p
cd /data/redis/cluster
mkdir 7000 7001 7002 7003 7004 7005
4. 修改配置文件
cp /usr/local/redis/redis.conf /data/redis/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目录下,并且端口修改成和文件夹对应。
[root@test 7000]# cp redis.conf ../7001/
[root@test 7000]# cp redis.conf ../7002/
[root@test 7000]# cp redis.conf ../7003/
[root@test 7000]# cp redis.conf ../7004/
[root@test 7000]# cp redis.conf ../7005/
# sed -i 's/port 7000/port 7001/g' /data/redis/cluster/7001/redis.conf
# sed -i 's/port 7000/port 7002/g' /data/redis/cluster/7002/redis.conf
# sed -i 's/port 7000/port 7003/g' /data/redis/cluster/7003/redis.conf
# sed -i 's/port 7000/port 7004/g' /data/redis/cluster/7004/redis.conf
# sed -i 's/port 7000/port 7005/g' /data/redis/cluster/7005/redis.conf
5. 启动6个redis实例
cd /data/redis/cluster/7000
redis-server redis.conf
cd /data/redis/cluster/7001
redis-server redis.conf
cd /data/redis/cluster/7002
redis-server redis.conf
cd /data/redis/cluster/7003
redis-server redis.conf
cd /data/redis/cluster/7004
redis-server redis.conf
cd /data/redis/cluster/7005
redis-server redis.conf
查看进程状态
[root@test 7005]# ps -ef|grep redis
root 24873 1 0 21:00 ? 00:00:00 redis-server 127.0.0.1:7001 [cluster]
root 24898 1 0 21:01 ? 00:00:00 redis-server 127.0.0.1:7000 [cluster]
root 24905 1 0 21:01 ? 00:00:00 redis-server 127.0.0.1:7002 [cluster]
root 24910 1 0 21:01 ? 00:00:00 redis-server 127.0.0.1:7003 [cluster]
root 24915 1 0 21:01 ? 00:00:00 redis-server 127.0.0.1:7004 [cluster]
root 24930 1 0 21:02 ? 00:00:00 redis-server 127.0.0.1:7005 [cluster]
6. 执行命令创建集群,首先安装依赖
首先要升级Ruby 到2.2 以上的版本
yum remove ruby
wget http://cache.ruby-lang.org/pub/ruby/ruby-2.4.2.zip 按照说明编译安装
安装rubygems
yum install rubygems -y
安装gem-redis (下载地址:https://rubygems.org/gems/redis/versions/4.0.1)
这里发现有报错
[root@test /]# gem install -l redis-4.0.1.gem
ERROR: Loading command: install (LoadError) ERROR: While executing gem ... (NoMethodError)
undefined method `invoke_with_build_args' for nil:NilClass
网上Google 搜到一堆apt-get 的Ubantu 解决方案, 最后在git 上面找到解决办法
git clone https://github.com/ruby/zlib.git
然后安装zlib 包
[root@test zlib]# ruby extconf.rb
checking for deflateReset() in -lz... yes
checking for zlib.h... yes
checking for crc32_combine() in zlib.h... yes
checking for adler32_combine() in zlib.h... yes
checking for z_crc_t in zlib.h... no
creating Makefile
[root@test zlib]#
[root@test zlib]# make
compiling zlib.c
linking shared-object zlib.so
[root@test zlib]#
[root@test zlib]# make install
/usr/bin/install -c -m 0755 zlib.so /usr/local/lib/ruby/site_ruby/2.4.0/x86_64-linux
解决这个问题之后就可以安装 redis-4.0.1.gem 了
[root@test/]# gem install -l redis-4.0.1.gem
Successfully installed redis-4.0.1
Parsing documentation for redis-4.0.1
Installing ri documentation for redis-4.0.1
Done installing documentation for redis after 2 seconds
7. 使用redis-trib 创建集群
cp /usr/local/redis/src/redis-trib.rb /usr/local/bin/redis-trib.rb
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 就会将这份配置应用到集群当中:
> 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
>>> Creating cluster
>>> 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: 2909642e736c8510a6b284139de164e5192749f4 127.0.0.1:7000
slots:0-5460 (5461 slots) master
M: 67c520e08194014965474ffb6ac8e30c8f39fb63 127.0.0.1:7001
slots:5461-10922 (5462 slots) master
M: d94fc8802b29df1c8601b337df927420492e7179 127.0.0.1:7002
slots:10923-16383 (5461 slots) master
S: 045c473cb8e416f39444e455151eeff8168bff69 127.0.0.1:7003
replicates 2909642e736c8510a6b284139de164e5192749f4
S: 079ea3c2c8a4d75cf4e19e6cb6d4a0ccbbb6cd80 127.0.0.1:7004
replicates 67c520e08194014965474ffb6ac8e30c8f39fb63
S: 6bdaac36d8ea69968b061b44bb47657000340e4c 127.0.0.1:7005
replicates d94fc8802b29df1c8601b337df927420492e7179
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: 2909642e736c8510a6b284139de164e5192749f4 127.0.0.1:7000
slots:0-5460 (5461 slots) master
1 additional replica(s)
M: d94fc8802b29df1c8601b337df927420492e7179 127.0.0.1:7002
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: 045c473cb8e416f39444e455151eeff8168bff69 127.0.0.1:7003
slots: (0 slots) slave
replicates 2909642e736c8510a6b284139de164e5192749f4
S: 6bdaac36d8ea69968b061b44bb47657000340e4c 127.0.0.1:7005
slots: (0 slots) slave
replicates d94fc8802b29df1c8601b337df927420492e7179
S: 079ea3c2c8a4d75cf4e19e6cb6d4a0ccbbb6cd80 127.0.0.1:7004
slots: (0 slots) slave
replicates 67c520e08194014965474ffb6ac8e30c8f39fb63
M: 67c520e08194014965474ffb6ac8e30c8f39fb63 127.0.0.1:7001
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.
集群的客户端
Redis 集群现阶段的一个问题是客户端实现很少。 以下是一些网上一些资料:
- redis-rb-cluster 是antirez编写的 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@test src]# redis-cli -c -p 7000
127.0.0.1:7000> set name ray
-> Redirected to slot [5798] located at 127.0.0.1:7001
OK
127.0.0.1:7001>
127.0.0.1:7001> set name2 Tim
-> Redirected to slot [742] located at 127.0.0.1:7000
OK
127.0.0.1:7000> set name3 Andy
OK
关于redis 支持的数据类型和详细的命令解释,可以参考http://redisdoc.com
[root@test /]# redis-cli -p 7000 cluster nodes
d94fc8802b29df1c8601b337df927420492e7179 127.0.0.1:7002@17002 master - 0 1506867732433 3 connected 10923-16383
045c473cb8e416f39444e455151eeff8168bff69 127.0.0.1:7003@17003 slave 2909642e736c8510a6b284139de164e5192749f4 0 1506867731000 4 connected
6bdaac36d8ea69968b061b44bb47657000340e4c 127.0.0.1:7005@17005 slave d94fc8802b29df1c8601b337df927420492e7179 0 1506867730528 6 connected
079ea3c2c8a4d75cf4e19e6cb6d4a0ccbbb6cd80 127.0.0.1:7004@17004 slave 67c520e08194014965474ffb6ac8e30c8f39fb63 0 1506867730427 5 connected
2909642e736c8510a6b284139de164e5192749f4 127.0.0.1:7000@17000 myself,master - 0 1506867732000 1 connected 0-5460
67c520e08194014965474ffb6ac8e30c8f39fb63 127.0.0.1:7001@17001 master - 0 1506867731530 2 connected 5461-10922
从上面的输出可以看出,集群由3主3备组成,而且可以看出谁是谁的主和备,其中第一列是Redis 节点的ID,集群通信基于这个唯一的ID 而不是IP 和端口
另外info 命令可以看出集群的其他很多配置信息
127.0.0.1:7002> info
# Server
redis_version:4.0.2
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:5d2f34688764aa69
redis_mode:cluster
os:Linux 2.6.32-696.3.2.el6.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:sync-builtin
gcc_version:4.4.7
process_id:24905
run_id:cf77b38c7a0d209d5e86f0c58243b1c81b2b261b
tcp_port:7002
uptime_in_seconds:5094
uptime_in_days:0
hz:10
lru_clock:13695774
executable:/data/redis/cluster/7002/redis-server
config_file:/data/redis/cluster/7002/redis.conf # Clients
connected_clients:1
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0 # Memory
used_memory:2623728
used_memory_human:2.50M
used_memory_rss:12177408
used_memory_rss_human:11.61M
used_memory_peak:2623728
used_memory_peak_human:2.50M
used_memory_peak_perc:100.04%
used_memory_overhead:2538992
used_memory_startup:1423928
used_memory_dataset:84736
used_memory_dataset_perc:7.06%
total_system_memory:4018335744
total_system_memory_human:3.74G
used_memory_lua:37888
used_memory_lua_human:37.00K
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
mem_fragmentation_ratio:4.64
mem_allocator:jemalloc-4.0.3
active_defrag_running:0
lazyfree_pending_objects:0 # Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1506867313
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:0
rdb_current_bgsave_time_sec:-1
rdb_last_cow_size:8572928
aof_enabled:1
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_last_cow_size:0
aof_current_size:0
aof_base_size:0
aof_pending_rewrite:0
aof_buffer_length:0
aof_rewrite_buffer_length:0
aof_pending_bio_fsync:0
aof_delayed_fsync:0 # Stats
total_connections_received:4
total_commands_processed:702
instantaneous_ops_per_sec:0
total_net_input_bytes:85105
total_net_output_bytes:11244
instantaneous_input_kbps:0.02
instantaneous_output_kbps:0.01
rejected_connections:0
sync_full:1
sync_partial_ok:0
sync_partial_err:1
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:1
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:4435
migrate_cached_sockets:0
slave_expires_tracked_keys:0
active_defrag_hits:0
active_defrag_misses:0
active_defrag_key_hits:0
active_defrag_key_misses:0 # Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7005,state=online,offset=952,lag=1
master_replid:6e3d41dcdd4c3c4767affccdd2b2150cb7d3fcd9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:966
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:966 # CPU
used_cpu_sys:4.53
used_cpu_user:2.50
used_cpu_sys_children:0.01
used_cpu_user_children:0.00 # Cluster
cluster_enabled:1 # Keyspace
8. Redis 故障测试
尝试kill 掉7000实例,集群自动识别并提升对应的slave 为主
[root@test src]# redis-cli -c -p 7001 cluster nodes
079ea3c2c8a4d75cf4e19e6cb6d4a0ccbbb6cd80 127.0.0.1:7004@17004 slave 67c520e08194014965474ffb6ac8e30c8f39fb63 0 1506868482542 5 connected
2909642e736c8510a6b284139de164e5192749f4 127.0.0.1:7000@17000 master,fail - 1506868460710 1506868460595 1 disconnected
045c473cb8e416f39444e455151eeff8168bff69 127.0.0.1:7003@17003 master - 0 1506868482000 7 connected 0-5460
67c520e08194014965474ffb6ac8e30c8f39fb63 127.0.0.1:7001@17001 myself,master - 0 1506868480000 2 connected 5461-10922
6bdaac36d8ea69968b061b44bb47657000340e4c 127.0.0.1:7005@17005 slave d94fc8802b29df1c8601b337df927420492e7179 0 1506868481000 6 connected
d94fc8802b29df1c8601b337df927420492e7179 127.0.0.1:7002@17002 master - 0 1506868481096 3 connected 10923-16383
尝试把7000重新启动,7000并不会去抢占,而是变成新的slave 加入集群
[root@test src]# redis-cli -c -p 7001 cluster nodes
079ea3c2c8a4d75cf4e19e6cb6d4a0ccbbb6cd80 127.0.0.1:7004@17004 slave 67c520e08194014965474ffb6ac8e30c8f39fb63 0 1506868670539 5 connected
2909642e736c8510a6b284139de164e5192749f4 127.0.0.1:7000@17000 slave 045c473cb8e416f39444e455151eeff8168bff69 0 1506868672044 7 connected
045c473cb8e416f39444e455151eeff8168bff69 127.0.0.1:7003@17003 master - 0 1506868671542 7 connected 0-5460
67c520e08194014965474ffb6ac8e30c8f39fb63 127.0.0.1:7001@17001 myself,master - 0 1506868671000 2 connected 5461-10922
6bdaac36d8ea69968b061b44bb47657000340e4c 127.0.0.1:7005@17005 slave d94fc8802b29df1c8601b337df927420492e7179 0 1506868670000 6 connected
d94fc8802b29df1c8601b337df927420492e7179 127.0.0.1:7002@17002 master - 0 1506868671040 3 connected 10923-16383
Redis 除了RDB 之外还有另一种持久化的方式叫AOF,有点像MySQL 的binlog
[root@test 7000]# ll
total 72
-rw-r--r-- 1 root root 124 Oct 1 22:28 appendonly.aof
-rw-r--r-- 1 root root 205 Oct 1 22:30 dump.rdb
-rw-r--r-- 1 root root 781 Oct 1 22:15 nodes.conf
-rw-r--r-- 1 root root 57758 Oct 1 20:52 redis.conf
[root@test 7000]# more appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$5
name2
$3
Tim
*3
$3
set
$5
name3
$4
Andy
*3
$3
set
$5
name2
$4
Tony
最后,想说说Redis 的集群方案,目前有3大方案:
1. Redis cluster
2. Twemproxy
3. Codis
网上有很多这三种方案的比较,目前最成熟的应该是Codis 由豌豆荚开源,后面会补充Codis 的搭建手册 https://github.com/CodisLabs/codis/
本文主要参考资料:
http://www.cnblogs.com/gomysql/p/4395504.html
http://redisdoc.com/topic/cluster-tutorial.html