前言

Lettuce客户端是一个线程安全的客户端,通信方式上基于netty,保证多个线程共享一个连接对象也不会存在线程安全问题,并且连接实例数量上支持可伸缩设计,一个连接达到上限后会按需增加连接实例。另外Lettuce客户端还支持异步请求方式、全局命令超时设置等特性。

注意: 使用Lettuce客户端,Redis的版本至少2.6

1.依赖

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.3</version>
</dependency>

Lettuce客户端因为不存在单个连接对象的线程安全问题,因此在连接池使用方面没有强制要求。如果请求频率不是很高,完全可以创建单个连接对象,毕竟Redis服务端处理命令是单线程执行,创建多个物理连接也是无意义的消耗资源。

2.单机版配置

2.1 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# IP地址
spring.lettuce.host=127.0.0.1

# 端口
spring.lettuce.port=6379

# 连接密码
spring.lettuce.password=123456

# 连接超时时间
spring.lettuce.timeout=2000

# 连接池最大连接数(使用负值表示没有限制)
spring.lettuce.pool.max-total=200

# 连接池最大空闲连接
spring.lettuce.pool.max-idle=10

# 连接池最小空闲连接
spring.lettuce.max-idle=5

2.2 Java配置类

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
72
@Configuration
public class LettuceConfig{

@Value("${spring.lettuce.host}")
private String host;

@Value("${spring.lettuce.port}")
private int port;

@Value("${spring.lettuce.password}")
private String password;

@Value("${spring.lettuce.timeout}")
private int timeout;

@Value("${spring.lettuce.pool.min-idle}")
private int minIdle;

@Value("${spring.lettuce.pool.max-idle}")
private int maxIdle;

@Value("${spring.lettuce.pool.max-total}")
private int maxTotal;

/**
* 普通连接
*/
@Bean
public StatefulRedisConnection<String, String> statefulRedisConnection(){

// 需要连接的服务器信息
RedisURI redisUri = RedisURI.builder()
.withHost(host)
.withPort(port)
.withPassword(password)
.withTimeout(Duration.of(timeout, ChronoUnit.SECONDS))
.build();

// 创建客户端对象
RedisClient redisClient = RedisClient.create(redisUri);

// 创建连接池对象
return client.connect();
}

/**
* 连接池
*/
@Bean
public GenericObjectPool<StatefulRedisConnection<String, String>> genericObjectPool(){

// 需要连接的服务器信息
RedisURI redisUri = RedisURI.builder()
.withHost(host)
.withPort(port)
.withPassword(password)
.withTimeout(Duration.of(timeout, ChronoUnit.SECONDS))
.build();

// 创建连接池配置信息
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
genericObjectPoolConfig.setMinIdle(minIdle);
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMaxTotal(maxTotal);

// 创建客户端
RedisClient redisClient = RedisClient.create(redisUri);

// 创建连接对象pool
return ConnectionPoolSupport.createGenericObjectPool(() -> client.connect(), poolConfig);
}
}

3.主从版配置

3.1 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# IP地址
spring.lettuce.host=127.0.0.1

# 端口
spring.lettuce.port=6379

# 连接密码
spring.lettuce.password=123456

# 连接超时时间
spring.lettuce.timeout=2000

# 连接池最大连接数(使用负值表示没有限制)
spring.lettuce.pool.max-total=200

# 连接池最大空闲连接
spring.lettuce.pool.max-idle=10

# 连接池最小空闲连接
spring.lettuce.max-idle=5

3.2 Java配置类

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
@Configuration
public class LettuceConfig{

@Value("${spring.lettuce.host}")
private String host;

@Value("${spring.lettuce.port}")
private int port;

@Value("${spring.lettuce.password}")
private String password;

@Value("${spring.lettuce.timeout}")
private int timeout;

@Value("${spring.lettuce.pool.min-idle}")
private int minIdle;

@Value("${spring.lettuce.pool.max-idle}")
private int maxIdle;

@Value("${spring.lettuce.pool.max-total}")
private int maxTotal;

/**
* 普通连接
*/
@Bean
public StatefulRedisMasterSlaveConnection<String, String> statefulRedisConnection(){

// 需要连接的服务器信息, 主节点、从节点都可以
RedisURI redisUri = RedisURI.builder()
.withHost(host)
.withPort(port)
.withPassword(password)
.withTimeout(Duration.of(timeout, ChronoUnit.SECONDS))
.build();

// 创建客户端
RedisClient redisClient = RedisClient.create(redisUri);

// 任意节点最终都能获取到,涉及的所有相关节点信息
StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), redisUri);

// 只从主节点读取数据(读写分离设置成ReadFrom.SLAVE即可)
connection.setReadFrom(ReadFrom.MASTER);

return connection;
}

/**
* 连接池
*/
@Bean
public GenericObjectPool<StatefulRedisMasterSlaveConnection<String, String>> genericObjectPool(){
// 参考单机模式连接池配置...
}
}

如果不想通过任意节点获取所有主从节点信息,可以通过代码固定设置要连接的节点集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public StatefulRedisMasterSlaveConnection<String, String> statefulRedisConnection(){

// 固定节点集合
List<RedisURI> uris = new ArrayList<>();
uris.add(RedisURI.builder().withHost(host1).withPort(port1).build());
uris.add(RedisURI.builder().withHost(host2).withPort(port2).build());
uris.add(RedisURI.builder().withHost(host3).withPort(port3).build());

// 创建客户端
RedisClient redisClient = RedisClient.create();

// 创建连接对象
StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(redisClient,
new Utf8StringCodec(), uris);

// 设置读写分离
connection.setReadFrom(ReadFrom.MASTER);

return connection;
}

4.哨兵版配置

4.1 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 哨兵IP地址
spring.lettuce.sentinel.host=127.0.0.1

# 哨兵端口
spring.lettuce.sentinel.port=26379

# masterId
spring.lettuce.sentinel.master.id=master

# 连接密码
spring.lettuce.password=123456

# 连接超时时间
spring.lettuce.timeout=2000

# 连接池最大连接数(使用负值表示没有限制)
spring.lettuce.pool.max-total=200

# 连接池最大空闲连接
spring.lettuce.pool.max-idle=10

# 连接池最小空闲连接
spring.lettuce.max-idle=5

4.2 Java配置类

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
@Configuration
public class LettuceConfig{

@Value("${spring.lettuce.sentinel.host}")
private String sentinelHost;

@Value("${spring.lettuce.sentinel.port}")
private int sentinelPort;

@Value("${spring.lettuce.password}")
private String password;

@Value("${spring.lettuce.timeout}")
private int timeout;

@Value("${spring.lettuce.pool.min-idle}")
private int minIdle;

@Value("${spring.lettuce.pool.max-idle}")
private int maxIdle;

@Value("${spring.lettuce.pool.max-total}")
private int maxTotal;

/**
* 普通连接
*/
@Bean
public StatefulRedisMasterSlaveConnection<String, String> statefulRedisConnection(){

// Lettuce提供了哨兵的拓扑发现机制,仅需要配置一个哨兵节点即可得到所有相关节点信息
RedisURI redisUri = RedisURI.builder()
.withPassword(password)
.withSentinel(sentinelHost, sentinelPort);
.withSentinelMasterId(masterId);
.build();

RedisClient redisClient = RedisClient.create();
StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), redisUri);

// 读写分离设置
connection.setReadFrom(ReadFrom.SLAVE);
return connection;
}

/**
* 连接池
*/
@Bean
public GenericObjectPool<StatefulRedisMasterSlaveConnection<String, String>> genericObjectPool(){
// 参考单机模式连接池配置...
}
}

5.集群版配置

5.1 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 集群节点
spring.lettuce.cluster.nodes=127.0.0.1:7001,127.0.0.2:7001,127.0.0.3:7001

# 连接密码
spring.lettuce.password=123456

# 连接超时时间
spring.lettuce.timeout=2000

# 连接池最大连接数(使用负值表示没有限制)
spring.lettuce.pool.max-total=200

# 连接池最大空闲连接
spring.lettuce.pool.max-idle=10

# 连接池最小空闲连接
spring.lettuce.max-idle=5

5.2 Java配置类

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
@Configuration
public class LettuceConfig{

@Value("#{'${spring.lettuce.cluster.nodes}'.split(',')}")
private List<String> clusterList;

@Value("${spring.lettuce.password}")
private String password;

@Value("${spring.lettuce.timeout}")
private int timeout;

@Value("${spring.lettuce.pool.min-idle}")
private int minIdle;

@Value("${spring.lettuce.pool.max-idle}")
private int maxIdle;

@Value("${spring.lettuce.pool.max-total}")
private int maxTotal;

/**
* 普通连接
*/
@Bean
public StatefulRedisClusterConnection<String, String> statefulRedisConnection(){

// 指定所有主节点,也可以随便指定一个节点,lettuce会通过拓扑机制发现所有节点
ArrayList<RedisURI> redisUriList = new ArrayList<>();
for(String cluster : clusterList){
String[] hostPort = cluster.split(":");
String host = hostPort[0];
Integer port = Integer.valueOf(hostPort[1]);
redisUriList.add(RedisURI.builder().withHost(host).withPort(port).withPassword(password).build());
}

// 建立客户端
RedisClusterClient redisClusterClient = RedisClusterClient.create(redisUriList);

// 定时更新集群的拓扑信息,这里设置10分钟更新一次
ClusterTopologyRefreshOptions options = ClusterTopologyRefreshOptions
.builder()
.enablePeriodicRefresh(Duration.of(10, ChronoUnit.MINUTES))
.build();

// 将更新规则设置到客户端对象
redisClusterClient.setOptions(ClusterClientOptions.builder().topologyRefreshOptions(options).build());

// 建立连接
StatefulRedisClusterConnection<String, String> connection = redisClusterClient.connect();

// 读写分离设置
connection.setReadFrom(ReadFrom.SLAVE);

// 返回
return connection;
}

/**
* 连接池
*/
@Bean
public GenericObjectPool<StatefulRedisClusterConnection<String, String>> genericObjectPool(){
// 参考单机模式连接池配置...
}
}

6.基本使用

6.1 同步使用

同步的使用方式就和普通的方法调用一样:

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
@Component
public class LettuceCommands{

@Autowired
private StatefulRedisClusterConnection clusterConnection;

private static RedisAdvancedClusterCommands<String, String> commands;

@PostConstruct
public void initRedisCommands(){
commands = clusterConnection.sync();
}

public static void set(String key, String value){
commands.set(key, value);
}

public static void set(String key, String value){
// nx()代表键不存在才会设置,xx()代表键存在才会设置;ex()代表过期时间单位为秒,px代表毫秒
SetArgs setArgs = SetArgs.Builder.nx().ex(5);
commands.set(key, value, setArgs);
}

public static String get(String key){
return commands.get(key);
}
}

6.2 异步使用

异步调用就像使用Java自带的线程FutureTask一样,调用后立即返回RedisFuture对象,并通过调用get()方法阻塞获取返回值:

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
@Component
public class LettuceCommands{

@Autowired
private StatefulRedisClusterConnection clusterConnection;

private static RedisAdvancedClusterAsyncCommands<String, String> commands;

@PostConstruct
public void initRedisCommands(){
commands = clusterConnection.async();
}

public static RedisFuture<String> set(String key, String value){
return commands.set(key, value);
}

public static RedisFuture<String> set(String key, String value){
return commands.set(key, value);
}

public static RedisFuture<String> get(String key){
return commands.get(key);
}
}

6.3 反应式使用

Lettuce的反应式基于编程框架Project Reactor,暂时没看懂。

评论