Redis3.0 及其之后的版本提供了redis-cluster 集群支持,用于在多个redis节点间共享数据,以提高服务的可用性。

构建 redis-cluster 集群可以通过 redis-trib.rb 工具来完成。redis-trib.rb 是redis官方提供的一个集群管理工具,集成在redis安装包的 src 目录下。redis-trib.rb 封装了redis提供的集群命令,使用简单、便捷。

因为 redis-trib.rb 是由ruby语言编写的,所以使用该工具需要ruby语言环境的支持。

1
2
$ ruby -v
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]

redis-cluster集群

1、配置

要启用redis-cluster集群,需要先修改redis配置文件集群配置部分的内容

redis.conf

1
2
3
4
5
6
7
8
9
10
11
################################ REDIS CLUSTER ###############################
# 启用redis-cluster集群
cluster-enabled yes
# 集群节点配置文件
# 该文件无需手工修改,由redis自动维护(创建和更新)
# 需要注意,单机运行多实例时,确保该文件没有被其他实例覆盖(不允许重名)
cluster-config-file nodes-6377.conf
# 节点超时时长(毫秒)
cluster-node-timeout 150001234567891011

为了方便进行演示,这里分别以端口 637763786379 各启用一个实例来代表不同的redis服务器

ps aux | grep redis
ps aux | grep redis
2、创建集群

创建集群使用 redis-tribcreate 命令完成,create 命令的格式为:

1
create host1:port1 ... hostN:portN1

默认情况下,ruby 是无法识别redis的,直接执行 redis-trib.rb create IP:PORT 将会报错

1
2
3
4
$ ./redis-trib.rb create 192.168.206.128:6377 192.168.206.128:6378 192.168.206.128:6379
/usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- redis (LoadError)
from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from ./redis-trib.rb:25:in `<main>'1234

所以需要先为ruby安装redis第三方接口,执行命令 gem install redis 即可

1
2
3
4
5
6
7
8
$ sudo gem install redis
[sudo] password for zhangcs:
Fetching: redis-4.0.1.gem (100%)
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 1 seconds
1 gem installed12345678

此时再使用 create 就可以将637763786379 这3台服务器构建成一个集群了

创建集群
创建集群

有一点需要注意的是,redis-cluster集群至少需要3个可用节点。

3、查看集群

使用 info 命令指定集群上任一节点的地址便可以查看集群状态

集群状态
集群状态

主从复制模型

刚才说到,redis-cluster至少需要保证3个节点可用。那么为了避免节点宕机导致服务不可用,我们就需要添加主从配置,为集群中的节点增加从节点;使其在主节点宕机时,能够将从节点提升为主节点代替原来的主节点进行工作。

在非集群的单节点环境中,配置主从关系的方法大致有 2 种:

1、修改从服务器配置文件 redis.confslaveof <masterip> <masterport> 选项;

2、在从服务器上使用slaveof 命令直接指定主服务器。

然而在redis-cluster集群环境中,启用了cluster配置的情况下slaveof 是不可用的。

假定有 6381 服务器配置 slaveof 指定主服务器 6377 ,同时该服务器启用了redis-cluster配置

1
2
3
4
5
6
7
################################# REPLICATION #################################
slaveof 192.168.206.128 6377
################################ REDIS CLUSTER ###############################
cluster-enabled yes
cluster-config-file nodes-6381.conf
cluster-node-timeout 150001234567

那么此时 6381 端口的服务器将无法成功启动

1
2
3
4
5
$ ./redis-server /usr/local/redis/6381/redis.conf
*** FATAL CONFIG FILE ERROR ***
Reading the configuration file, at line 283
>>> 'slaveof 192.168.206.128 6377'
slaveof directive not allowed in cluster mode12345

显然我们无法使用原有的主从配置方法对集群进行配置。此时我们需要借助于 redis-trib.rb 工具来进行从节点的添加操作,先将节点加入集群中,然后再声明节点为原有节点的从节点。

1、启用服务后先将该节点加入集群,直接使用redis-trib.rb的 add-node 命令即可实现:

1
redis-trib add-node new_host:new_port existing_host:existing_port1
添加节点
添加节点

此时通过 redis-trib info 能够查看到该节点已经成功加入了集群中,并且该节点并没有分配哈希槽

集群状态2
集群状态2

2、声明该节点为集群中某一节点的从节点,需要使用客户端进入该节点(此处即为新增的从节点 6381)进行设置,使用 cluster replicate 命令指定所属主节点ID。

主节点ID可以使用客户端连接到集群后通过命令 cluster nodes 查看 :

节点信息
节点信息

使用客户端连接新增的从节点 6381 ,指定主节点 6377 在集群中的ID ,声明为 6377 节点的从节点

1
2
3
$ ./redis-cli -h 192.168.206.128 -c -p 6381
192.168.206.128:6381> cluster replicate e10dde558fb46fe8ae6fe66e54ef56032fbcce0f
OK

至此就完成了集群中一个节点的主从配置,查看 6377 节点能够看到其包含一个从节点:

集群状态3
集群状态3

查询出 6377 端口服务对应的PID,然后通过 kill 将服务关闭,使该节点在集群上不可用;此时查看集群信息,能够发现从节点 6381 自动提升为主节点,顶替了不可用的 6377 节点。

关闭服务
关闭服务
集群状态4
集群状态4

重新启动已宕机的服务后,该节点将会被当做从节点添加到管理原先的哈希槽分配范围的节点上。这里也就是添加到了 6381 节点上,6381 节点管理的哈希槽就是原先由 6377 节点所管理的

重启宕机的服务器
重启宕机的服务器

客户端连接 redis-cluster

客户端在连接 redis 服务器时带上参数 -c 即为连接到cluster集群

1
2
3
4
5
$ redis-cli -h 192.168.206.128 -c -p 6377
192.168.206.128:6377> set name zhangcs
-> Redirected to slot [5798] located at 192.168.206.128:6378
OK
192.168.206.128:6378>12345

可以看到,在 6377 端口的服务器上存储一个string类型的键值对 name = zhangsan 的时,操作被重定向到了 6378 端口的服务器上,而 name = zhangsan 这个键值对最终也被存储在了 6378 端口的服务器里。

同理,在获取数据时,也会重定向到对应数据实际存储的服务器上,然后在该服务器上进行操作。

1
2
3
4
$ ./redis-cli -h 192.168.206.128 -c -p 6377
192.168.206.128:6378> get name
"zhangcs"
192.168.206.128:6378>1234
单独连接集群上的节点

需要注意的是,节点在加入集群后,如果不声明参数 -c 连接集群而是单独连接集群上的节点,那么在操作时如果需要重定向到其他的服务器,是无法成功重定向然后完成操作的。

例如键值对 name = zhangsan 存储在 6378 端口的服务器上,此时如果我们单独连接到 6377 端口的服务器上进行操作,那么该操作是无法成功的。

1
2
3
4
5
6
$ redis-cli -h 127.0.0.1 -p 6377
127.0.0.1:6377> get name
(error) MOVED 5798 127.0.0.1:6378
127.0.0.1:6377> set name lisi
(error) MOVED 5798 127.0.0.1:6378
127.0.0.1:6377>123456

而如果无需重定向,则能成功完成操作。

1
2
3
4
$ redis-cli -h 127.0.0.1 -p 6378
127.0.0.1:6378> get name
"zhangsan"
127.0.0.1:6378>1234

Spring集成 redis-cluster

1、连接池配置

1
2
3
4
5
6
7
8
@Bean
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(100);
jedisPoolConfig.setMaxIdle(10);
jedisPoolConfig.setMaxWaitMillis(1500);
return jedisPoolConfig;
}

2、JedisCluster对象配置

1
2
3
4
5
6
7
8
9
10
@Bean
public JedisCluster jedisCluster() {
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
nodes.add(new HostAndPort("192.168.206.128", 6377));
nodes.add(new HostAndPort("192.168.206.128", 6378));
nodes.add(new HostAndPort("192.168.206.128", 6379));
nodes.add(new HostAndPort("192.168.206.128", 6381));
JedisCluster jedisCluster = new JedisCluster(nodes, jedisPoolConfig());
return jedisCluster;
}

3、JedisCluster对象的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Autowired
private JedisCluster jedisCluster;
@Test
public void testCluster() {
Assert.assertNotNull(jedisCluster);
String result = jedisCluster.set("name", "zhangsan");
Assert.assertNotNull(result);
Assert.assertEquals("OK", result);
String name = jedisCluster.get("name");
Assert.assertNotNull(name);
Assert.assertEquals("zhangsan", name);
}
spring-cache集成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@Configuration
@ComponentScan(basePackages = {"org.pro.service"})
// 启用缓存
@EnableCaching
public class RootConfig {
/**
* jedis连接池配置
* */
@Bean
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(1000);
jedisPoolConfig.setMaxIdle(10);
jedisPoolConfig.setMaxWaitMillis(1500);
return jedisPoolConfig;
}
/**
* redis-cluster配置
* */
@Bean
public RedisClusterConfiguration redisClusterConfiguration() {
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
Set<RedisNode> nodes = new HashSet<>();
nodes.add( new RedisNode("127.0.0.1", 6377));
nodes.add( new RedisNode("127.0.0.1", 6378));
nodes.add( new RedisNode("127.0.0.1", 6379));
redisClusterConfiguration.setClusterNodes(nodes);
redisClusterConfiguration.setMaxRedirects(4);
return redisClusterConfiguration;
}
/**
* jedis连接工厂
* */
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisClusterConfiguration(), jedisPoolConfig());
jedisConnectionFactory.setTimeout(15000);
return jedisConnectionFactory;
}
/**
* redis模板
* */
@Bean
public RedisTemplate redisTemplate() {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(stringRedisSerializer);
return redisTemplate;
}
/**
* redis缓存管理器
* */
@Bean
public RedisCacheManager redisCacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate());
return redisCacheManager;
}
}