使用 Redis 和 Spring 的首要目标之一是通过 loC 容器连接到存储。因此需要一个 Java 连接驱动。无论选择哪个库,只需要使用一套 Spring Data Redis API,统一 AIP 将会使所有连接器表现一致。
org.springframework.data.redis.connection
包及其中的 RedisConnection
和 RedisConnectionFactory
接口用于与 Redis 进行通信和检索活动连接。
一、RedisConnection 和 RedisConnectionFactory
RedisConnection
提供了 Redis 通信的核心构建模块,它处理与 Redis 后端的通信。它还自动将底层连接库异常转换为 Spring 一致的 DAO 层次结构异常。因为操作语义保持不变,开发人员可以在不进行任何代码更改的情况下切换连接器。
对于需要原生库 API 的特殊情况,RedisConnection
提供了一个专用方法 getNativeConnection
,它返回用于通信的原始底层对象。
RedisConnection
对象是通过 RedisConnectionFactory
创建的。此外,工厂继承了 PersistenceExceptionTranslator
的功能,这意味着一旦声明,它们允许进行透明的异常转换。例如,您可以通过使用 @Repository
注解和 AOP 进行异常转换。
RedisConnection
类不是线程安全的。虽然底层原生连接,比如 Lettuce
的StatefulRedisConnection
,可能是线程安全的,但 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
包下。
其中有 JedisConnectionConfiguration
和 LettuceConnectionConfiguration
两个连接配置,这两个配置类不同之处在于连接 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 links
#Spring #Redis #SpringBoot