随着大型网站数据量和对系统可用性要求的提升,单机版的Redis越来越难以满足需要,因此我们需要使用Redis集群来提供服务。
目前主流的Redis集群解决方案有三类,它们都是通过将key分散到不同的redis实例上来提高整体能力, 这种方法称为分片(sharding):
- 服务端分片: 客户端与集群中任意的节点通信,服务端计算key在哪一个节点上,若不再当前节点上则通知客户端应访问的节点。 典型代表为官方推出的Redis Cluster
- 客户端分片: 客户端计算key应在集群中的哪一个节点上,并与该节点通信。典型代表为ShardedJedis
- 代理分片: 客户端与集群中的代理(proxy)通信,代理与节点通信进行操作。典型代表为Codis
单机版的Redis中单条指令的执行总是原子性的,在集群中则难以保证这一性质,某些指令可能无法在集群中使用或者受到限制。
若需要使用这些指令或需要它们保持原子性,可以采用单机版Redis和集群搭配使用的方法。将主要业务部署在集群上,将需要较多支持的服务部署在单机版Redis上。
三种集群实现方式各有有缺,本文分别进行简单介绍它们的特性。本文不会详细介绍三种方式的使用方式,只对其架构和特性进行对比,帮助读者选择合适的解决方案。
基本概念
哈希槽
哈希槽(hash slot)是来自Redis Cluster的概念, 但在各种集群方案都有使用。
哈希槽是一个key的集合,Redis集群共有16384个哈希槽,每个key通过CRC16散列然后对16384进行取模来决定该key应当被放到哪个槽中,集群中的每个节点负责一部分哈希槽。
以有三个节点的集群为例:
- 节点A包含0到5500号哈希槽
- 节点B包含5501到11000号哈希槽
- 节点C包含11001到16384号哈希槽
这样的设计有利于对集群进行横向伸缩,若要添加或移除节点只需要将该节点上的槽转移到其它节点即可。
在某些集群方案中,涉及多个key的操作会被限制在一个slot中,如Redis Cluster中的mget/mset操作。
HashTag
HashTag机制可以影响key被分配到的slot,从而可以使用那些被限制在slot中操作。
HashTag即是用{}
包裹key的一个子串,如{user:}1
, {user:}2
。
在设置了HashTag的情况下,集群会根据HashTag决定key分配到的slot, 两个key拥有相同的HashTag:{user:}
, 它们会被分配到同一个slot,允许我们使用MGET命令。
通常情况下,HashTag不支持嵌套,即将第一个{
和第一个}
中间的内容作为HashTag。若花括号中不包含任何内容则会对整个key进行散列,如{}user:
。
HashTag可能会使过多的key分配到同一个slot中,造成数据倾斜影响系统的吞吐量,务必谨慎使用。
主从模型
几种流行的Redis集群解决方案都没有将一个key写到多个节点中,若某个节点故障则无法访问访问其上的key这显然是不满足集群的分区容错性的。
Redis集群使用主从模型(master-slave)来提高可靠性。每个master节点上绑定若干个slave节点,当master节点故障时集群会推举它的某个slave节点代替master节点。
体验Redis集群
Redis Cluster
为了便于部署本文使用docker部署了一个简单集群:
docker run -i -t -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 -p 7006:7006 -p 7007:7007 grokzen/redis-cluster
打开另一个终端窗口, 启动redis客户端:
redis-cli -c -p 7000
在redis客户端中尝试进行操作:
127.0.0.1:7000> set a 1
-> Redirected to slot [15495] located at 127.0.0.1:7002
OK
127.0.0.1:7002> get a
"1"
127.0.0.1:7002> mset ab 2 ac 3
(error) CROSSSLOT Keys in request don‘t hash to the same slot
127.0.0.1:7002> mset {a}b 2 {a}c 3
OK
上述示例中, 执行set a
命令时客户端被重定向到了其它节点。
mset ab 2 ac 3
命令因为key被分配到不同的slot中导致CROSSSLOT错误,而使用HashTag机制mset {a}b 2 {a}c 3
就可以解决这个问题。
更多的内容可以参考Redis Cluster中文文档。
ShardedJedis
Jedis是一个流行的Java语言Redis客户端,在Redis官方提供Redis Cluster之前便实现了客户端集群功能。
ShardedJedis使用一致性哈希算法进行数据分片,不支持涉及多个key的命令, 其不支持的命令可以参考MultiKeyCommands。
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(2);
poolConfig.setMaxIdle(1);
poolConfig.setMaxWaitMillis(2000);
final String HOST = "127.0.0.1";
JedisShardInfo shardInfo1 = new JedisShardInfo(HOST, 6379);
JedisShardInfo shardInfo2 = new JedisShardInfo(HOST, 6380);
JedisShardInfo shardInfo3 = new JedisShardInfo(HOST, 6381);
ShardedJedisPool jedisPool = new ShardedJedisPool(poolConfig, Arrays.asList(shardInfo1, shardInfo2, shardInfo3));
try(ShardedJedis jedis = jedisPool.getResource()) {
jedis.set("a", "1");
jedis.set("b", "2");
System.out.println(jedis.get("a"));
}
在初始化ShardedJedisPool时设置keyTagPattern,匹配keyTagPattern的key会被分配到同一个实例中。
Codis
Codis是豌豆荚开源的代理式Redis集群解决方案,因为Twemproxy缺乏对弹性伸缩的支持,很多企业选择了经过生产环境检验的Codis。
Codis的安装和使用方法可以参考官方文档, 为了方便起见我们使用ReleaseBinary文件安装Codis-Server和Codis-Proxy。
或者使用第三方开发者制作的Docker镜像:
docker run -d --name="codis" -h "codis" -p 18087:18087 -p 11000:11000 -p 19000:19000 ruo91/codis
docker exec codis /bin/bash codis-start all start
使用redis客户端连接19000端口,尝试进行操作:
127.0.0.1:19000> set a 1
OK
127.0.0.1:19000> get a
"1"
127.0.0.1:19000> mset ab 2 ac 3
OK
127.0.0.1:19000> mset {a}b 2 {a}c 3
OK
Codis也支持HashTag, 不过Codis已经解决了大多数命令的slot限制。
集群方案对比
协议支持
Redis Cluster | ShardedJedis | Codis | |
---|---|---|---|
mget/mset | 仅限同一个slot | 不支持 | 失去原子性 |
keys | 仅限同一个slot | 不支持 | 不支持 |
scan | 仅限同一个slot | 不支持 | 仅限同一个slot(SLOTSSCAN命令) |
rename | 仅限同一个slot | 不支持 | 不支持 |
pipeline | 不支持 | 不支持 | 支持 |
事务 | 支持相同slot | 不支持 | 不支持 |
发布/订阅 | 支持 | 不支持 | 不支持 |
eval | 仅限同一slot | 不支持 | 支持 |