使用 Redis 和 Spring 的首要目标之一是通过 loC 容器连接到存储。因此需要一个 Java 连接驱动。无论选择哪个库,只需要使用一套 Spring Data Redis API,统一 AIP 将会使所有连接器表现一致。

org.springframework.data.redis.connection 包及其中的 RedisConnectionRedisConnectionFactory 接口用于与 Redis 进行通信和检索活动连接。

一、RedisConnection 和 RedisConnectionFactory

RedisConnection 提供了 Redis 通信的核心构建模块,它处理与 Redis 后端的通信。它还自动将底层连接库异常转换为 Spring 一致的 DAO 层次结构异常。因为操作语义保持不变,开发人员可以在不进行任何代码更改的情况下切换连接器。

对于需要原生库 API 的特殊情况,RedisConnection 提供了一个专用方法 getNativeConnection,它返回用于通信的原始底层对象。

RedisConnection 对象是通过 RedisConnectionFactory 创建的。此外,工厂继承了 PersistenceExceptionTranslator 的功能,这意味着一旦声明,它们允许进行透明的异常转换。例如,您可以通过使用 @Repository 注解和 AOP 进行异常转换。

RedisConnection 类不是线程安全的。虽然底层原生连接,比如 LettuceStatefulRedisConnection,可能是线程安全的,但 Spring Data Redis 的 LettuceConnection 类本身不是。因此,不应在多个线程之间共享 RedisConnection 的实例。这对于事务性或阻塞性 Redis 操作和命令(例如 BLPop)尤其如此。在事务和管道操作中,例如,RedisConnection 持有未受保护的可变状态以正确完成操作,因此多线程场景中是线程不安全的。设计如此。

如果需要在多个线程之间共享(有状态的)Redis 资源,比如连接,出于性能原因或其他原因,应该获取原生连接并直接使用 Redis 客户端库(驱动程序)API。或者,您可以使用 RedisTemplate,它以线程安全的方式获取和管理连接操作(和Redis命令)。

根据底层配置,工厂可以返回一个新连接或一个现有连接(当使用池或共享原生连接时)。

使用 RedisConnectionFactory 的最简单方法是通过 loC 容器配置适当的连接器并将其注入到使用类中。不幸的是,目前并非所有连接器都支持所有 Redis 功能。当在 Connection API 上调用底层库不支持的方法时,会抛出 UnsupportedOperationException

以下表格列出了各个Redis连接器支持的功能:

功能 Lettuce Jedis
单节点连接 支持 支持
主从节点连接 支持 支持
Redis Sentinel 主节点操作,哨兵认证,从节点读取 主节点查找
Redis Cluster 集群连接,集群节点连接,从节点读取 集群连接,集群节点连接,从节点读取
传输通道 TCP,OS 原生 TCP(epoll,kqueue),Unix 域套接字 TCP
连接池 支持(使用commons-pool2) 支持(使用commons-pool2)
其他连接功能 非阻塞命令的单例连接共享 管道和交易相互排斥。无法在管道/事务中使用服务器/连接命令。
SSL 支持 支持 支持
PUB 和 SUB 操作 支持 支持
事务 支持 支持(事务和管道操作互斥)
支持的数据类型 Key, String, List, Set, Sorted Set, Hash, Server, Stream, Scripting, Geo, HyperLogLog Key, String, List, Set, Sorted Set, Hash, Server, Stream, Scripting, Geo, HyperLogLog
响应式 (非阻塞式) API 支持 不支持

二、配置 Lettuce 连接器

Lettuce 是一个基于 Netty 的开源连接器,Spring Data Redis 通过 org.springframework.data.redis.connection.lettuce 包支持。

Maven 依赖如下:

1
2
3
4
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>

创建新的 Lettuce 工厂:

1
2
3
4
5
6
7
8
9
@Configuration
class AppConfig {

@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379));
}

}

还有一些 Lettuce 特定的连接参数可以调整。默认情况下,由 LettuceConnectionFactory 创建的所有 LettuceConnection 实例共享相同的线程安全的原生连接,并用于所有非阻塞和非事务性操作。要每次使用专用的连接,请将 shareNativeConnection 属性设置为 false。LettuceConnectionFactory 还可以配置为使用 LettucePool 来池化阻塞和事务性连接,或者如果 shareNativeConnection 设置为 false 的情况下池化所有连接。

以下示例展示了一个更复杂的配置,包括 SSL 和超时,使用 LettuceclientConfigurationBuilder

1
2
3
4
5
6
7
8
9
10
@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {

LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.useSsl().and()
.commandTimeout(Duration.ofSeconds(2))
.shutdownTimeout(Duration.ZERO)
.build();
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379), clientConfig);
}

Lettuce 与 Netty 的原生传输集成,允许您使用 Unix 域套接字与 Redis 通信。确保包括与您的运行时环境匹配的适当原生传输依赖。以下示例展示了如何为 /var/run/redis.sock 上的 Unix 域套接字创建 Lettuce 连接工厂:

1
2
3
4
5
6
7
8
@Configuration
class AppConfig {

@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisSocketConfiguration("/var/run/redis.sock"));
}
}

注意:Netty 目前支持 epoll(Linux)和 kqueue(BSD/macOS)接口用于 OS 原生传输。

三、配置 Jedis 连接器

Jedis 是一个社区驱动的连接器,Spring Data Redis 模块通过 org.springframework.data.redis.connection.jedis 包支持。

Maven 依赖:

1
2
3
4
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

Jedis 的简单配置:

1
2
3
4
5
6
7
8
@Configuration
class AppConfig {

@Bean
public JedisConnectionFactory redisConnectionFactory() {
return new JedisConnectionFactory();
}
}
1
2
3
4
5
6
7
8
9
@Configuration
class RedisConfiguration {

@Bean
public JedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("localhost", 6379);
return new JedisConnectionFactory(config);
}
}

四、Spring Boot 源码

Spring Boot 中有关于 Redis 自动配置的源码,在 Spring Boot 项目的 org.springframework.boot.autoconfigure.data.redis 包下。

其中有 JedisConnectionConfigurationLettuceConnectionConfiguration 两个连接配置,这两个配置类不同之处在于连接 Redis 一个是用过 Jedis 一个是通过 Lettuce。这两个类有抽象父类 RedisConnectionConfiguration

1、JedisConnectionConfiguration

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
@Configuration(proxyBeanMethods = false)  
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
@ConditionalOnMissingBean(RedisConnectionFactory.class)
@ConditionalOnProperty(name = "spring.data.redis.client-type", havingValue = "jedis", matchIfMissing = true)
class JedisConnectionConfiguration extends RedisConnectionConfiguration {

@Bean
@ConditionalOnThreading(Threading.PLATFORM)
JedisConnectionFactory redisConnectionFactory(
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
return createJedisConnectionFactory(builderCustomizers);
}

@Bean
@ConditionalOnThreading(Threading.VIRTUAL)
JedisConnectionFactory redisConnectionFactoryVirtualThreads(
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
JedisConnectionFactory factory = createJedisConnectionFactory(builderCustomizers);
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-");
executor.setVirtualThreads(true);
factory.setExecutor(executor);
return factory;
}

}

上面的代码中,需要注意的几个点:

  • Configuration 中 Bean 注册条件
  • JedisConnectionFactory 对虚拟线程的支持

Bean 注册条件

通过 @ConditionalOnClass@ConditionalOnMissingBean@ConditionalOnProperty 注解来限制 Bean 何时注入到容器中。

  • @ConditionalOnMissingBean(RedisConnectionFactory.class) 当容器中没有 RedisConnectionFactory 时生效,如果自己自定义了 RedisConnectionFactory 并且注入到了容器中,则该配置类中注册 JedisConnectionFactory 将不生效
  • @ConditionalOnProperty(name = "spring.data.redis.client-type", havingValue = "jedis", matchIfMissing = true),当存在属性并且属性的取值为 Jedis 时,该配置类中的 Bean 会被注册。

虚拟线程

根据应用是否支持虚拟线程,而注册不同配置的 RedisConnectionFactory 类到容器中。

2、LettuceConnectionConfiguration

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
@Configuration(proxyBeanMethods = false)  
@ConditionalOnClass(RedisClient.class)
@ConditionalOnProperty(name = "spring.data.redis.client-type", havingValue = "lettuce", matchIfMissing = true)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {

@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(ClientResources.class)
DefaultClientResources lettuceClientResources(ObjectProvider<ClientResourcesBuilderCustomizer> customizers) {
DefaultClientResources.Builder builder = DefaultClientResources.builder();
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}

@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
@ConditionalOnThreading(Threading.PLATFORM)
LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) {
return createConnectionFactory(builderCustomizers, clientResources);
}

@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
@ConditionalOnThreading(Threading.VIRTUAL)
LettuceConnectionFactory redisConnectionFactoryVirtualThreads(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) {
LettuceConnectionFactory factory = createConnectionFactory(builderCustomizers, clientResources);
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-");
executor.setVirtualThreads(true);
factory.setExecutor(executor);
return factory;
}

}

3、RedisAutoConfiguration

该配置类中主要将 RedisTemplate 注册到容器中。

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
@AutoConfiguration  
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

@Bean
@ConditionalOnMissingBean(RedisConnectionDetails.class)
PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties) {
return new PropertiesRedisConnectionDetails(properties);
}

@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}

}

相关链接

Drivers :: Spring Data Redis

OB tags

#Spring #Redis #SpringBoot