🌈 集成 Redis 数据库 分布式工具 Redisson 缓存 Spring Cache
🌈 Redis 客户端
🌮 Jedis
Jedis 是 Java 实现的较轻量级的 Redis 客户端,简洁且基于 Socket 的操作方式,很高的性能。
Jedis 的 API 提供的比较全面 Redis 命令的支持。
使用阻塞的 I/O 操作,方法调用都是同步的,程序流需等到 socket 处理完 I/O 才能执行,不支持异步的操作。
是直接连接 Redis Server 的,在多线程环境是非线程安全的,需要通过连接池来操作 Jedis。
🌮 Lettuce
Lettuce 可伸缩线程安全的 Redis 客户端。多个线程可以共享同一个 RedisConnection,利用 Netty NIO 框架高效地管理多个连接。
支持同步、异步、响应式编程,自动重新连接,主从,集群,哨兵,管道和编码器。
Spring Boot 2.x 开始, Lettuce 已取代 Jedis 成为 Spring Boot 默认的 Redis 客户端。
🌮 Redisson
Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的 Java 常用对象,还提供了许多分布式服务。
Redisson 提供了使用 Redis 的最简单和最便捷的方法。
适用于分布式应用,分布式缓存,分布式回话管理,分布式服务(任务,延迟任务,执行器)。
🌊 当没有分布式场景,优先考虑使用 Lettuce 性能更高;需要用到分布式锁,分布式对象,分布式集合等,可以采用 Lettuce + Redisson 组合使用。
🌈 Spring Boot 集成 Redis
🌮 Spring Data Redis 的依赖
Maven 引入依赖,直接使用 Spring Boot 默认的版本(使用默认 Lettuce 客户端)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
引入 commons-pool2 依赖,作为 Lettuce 的连接池
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
🌮 Spring Data Redis 的配置
添加配置,参考 org.springframework.boot.autoconfigure.data.redis.RedisProperties
## Redis数据库索引(默认为0)
spring.redis.database=0
## Redis服务器地址(默认为localhost)
spring.redis.host=127.0.0.1
## Redis服务器连接端口(默认为6379)
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=
## 连接超时时间(毫秒)
spring.redis.timeout=1000
## 连接池最大连接数(默认为8。使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=8
## 连接池最大阻塞等待时间(默认为-1。使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=-1
## 连接池中的最大空闲连接(默认为8)
spring.redis.lettuce.pool.max-idle=8
## 连接池中的最小空闲连接(默认为0)
spring.redis.lettuce.pool.min-idle=0
🌮 Spring Data Redis 的使用
Spring Data Redis 默认向容器中注入 RedisTemplate<Object, Object> redisTemplate
、StringRedisTemplate stringRedisTemplate
两个模板类,是 Spring 对 Redis 操作的封装,通过封装的 API 来操作 Redis。
🍅 参考 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
使用示例
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@GetMapping("/stringRedis/add")
public String stringRedisAdd(String str) {
stringRedisTemplate.opsForValue().set("stringRedis", str);
return stringRedisTemplate.opsForValue().get("stringRedis");
}
@GetMapping("/redis/add")
public SysAccount redisAdd() {
redisTemplate.opsForValue().set("redis", new SysAccount().setAccount("don"));
return JSONUtil.toBean(stringRedisTemplate.opsForValue().get("redis"), SysAccount.class);
}
opsForValue --> string 操作
opsForHash --> hash 操作
opsForList --> list 操作
opsForSet --> set 操作
opsForZSet --> Zset 操作
测试完成,查看 Redis 数据库的数据
RedisTemplate 默认使用的是 JdkSerializationRedisSerializer 序列化
🌮 Spring Data Redis 自定义序列化
编写 Redis 配置类 RedisConfig 自定义 RedisTemplate 缓存序列化配置
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置 RedisConnection 工厂(实现多种 Java Redis 客户端接入的工厂)
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用 Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
return redisTemplate;
}
测试,查看 Redis 数据库的数据,JSON 格式数据
🍅 StringRedisTemplate 默认使用的 StringRedisSerializer 序列化机制,简单的字符串序列化 ,key、value 都是 String 字符串,不用修改。参考 org.springframework.data.redis.core.StringRedisTemplate
🍅 RedisSerializer 接口是 Redis 序列化接口,用于 Redis KEY 和 VALUE 的序列化
org.springframework.data.redis.serializer.RedisSerializer
public interface RedisSerializer<T> {
@Nullable
byte[] serialize(@Nullable T t) throws SerializationException;
@Nullable
T deserialize(@Nullable byte[] bytes) throws SerializationException;
static RedisSerializer<Object> java() {
return java((ClassLoader)null);
}
static RedisSerializer<Object> java(@Nullable ClassLoader classLoader) {
return new JdkSerializationRedisSerializer(classLoader);
}
static RedisSerializer<Object> json() {
return new GenericJackson2JsonRedisSerializer();
}
static RedisSerializer<String> string() {
return StringRedisSerializer.UTF_8;
}
static RedisSerializer<byte[]> byteArray() {
return ByteArrayRedisSerializer.INSTANCE;
}
default boolean canSerialize(Class<?> type) {
return ClassUtils.isAssignable(this.getTargetType(), type);
}
default Class<?> getTargetType() {
return Object.class;
}
}
实现类有:
JdkSerializationRedisSerializer:序列化对象,对象必须实现 Serializable 接口,被序列化除属性内容还有其他内容,长度长且不易阅读,默认就是采用这种序列化方式。
Jackson2JsonRedisSerializer:序列化对象为 JSON 字符串,被序列化对象不需要实现 Serializable 接口,序列化后结果容易阅读,存储字节少,速度快。
🌈 Spring Boot 集成 Redisson
🌮 Redisson 的依赖
Maven 引入依赖,版本根据 Spring Boot 版本选择
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
参考 https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter
🌮 Redisson 的配置
添加配置文件 redisson-config.yml
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: null
clientName: null
database: 0
idleConnectionTimeout: 10000
pingTimeout: 1000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
reconnectionTimeout: 3000
failedAttempts: 3
subscriptionsPerConnection: 5
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 32
connectionPoolSize: 64
dnsMonitoringInterval: 5000
threads: 0
nettyThreads: 0
codec:
class: "org.redisson.codec.JsonJacksonCodec"
transportMode: "NIO"
在 application.yml 中配置
spring:
redis:
redisson:
config: classpath:redisson-config.yml
配置完后即可使用 RedissonClient 操作 Redis
🍅 详细参考 org.redisson.spring.starter.RedissonAutoConfiguration
🌮 RedissonClient 的使用
@Autowired
private RedissonClient redissonClient;
@GetMapping("/set/{key}")
public String set(@PathVariable String key) {
RBucket<String> keyObj = redissonClient.getBucket(key);
keyObj.set(key + new Random().nextInt(1000));
return key;
}
@GetMapping("/get/{key}")
public String get(@PathVariable String key) {
RBucket<String> keyObj = redissonClient.getBucket(key);
return keyObj.get();
}
锁的使用
@GetMapping("/lock")
public void redissonLock() {
RLock lock = redissonClient.getLock("lock");
try {
// 1.获取 redis 锁
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 2.获取库存
RBucket<Integer> keyObj = redissonClient.getBucket("xiaomi");
Integer stock = keyObj.get();
if (stock != null) {
// 3.比较并且扣减库存
if (stock > 0) {
// 4.设置库存
keyObj.set(--stock);
TimeUnit.SECONDS.sleep(1);
System.out.println("购买成功");
}
}
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
更多锁的使用参考 https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers
🌮 使用 RedissonAutoConfigurationCustomizer 配置
新建 Redisson 配置项类
@Data
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
/**
* Redis 缓存 Key 前缀
*/
private String keyPrefix;
/**
* 线程池数量, 默认值 = 当前处理核数量 * 2
*/
private int threads;
/**
* Netty 线程池数量, 默认值 = 当前处理核数量 * 2
*/
private int nettyThreads;
/**
* 传输模式
*/
private TransportMode transportMode;
/**
* 单机服务配置
*/
private SingleServerConfig singleServerConfig;
/**
* 集群服务配置
*/
private ClusterServersConfig clusterServersConfig;
@Data
@NoArgsConstructor
public static class SingleServerConfig {
/**
* 客户端名称
*/
private String clientName;
/**
* 最小空闲连接数
*/
private int connectionMinimumIdleSize;
/**
* 连接池大小
*/
private int connectionPoolSize;
/**
* 连接空闲超时,单位:毫秒
*/
private int idleConnectionTimeout;
/**
* 命令等待超时,单位:毫秒
*/
private int timeout;
/**
* 命令失败重试次数
*/
private int retryAttempts;
/**
* 命令失败重试次数
*/
private int retryInterval;
/**
* 发布和订阅连接的最小空闲连接数
*/
private int subscriptionConnectionMinimumIdleSize;
/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;
/**
* 单个连接最大订阅数量
*/
private int subscriptionsPerConnection;
/**
* DNS监测时间间隔,单位:毫秒
*/
private int dnsMonitoringInterval;
}
@Data
@NoArgsConstructor
public static class ClusterServersConfig {
/**
* 客户端名称
*/
private String clientName;
/**
* master最小空闲连接数
*/
private int masterConnectionMinimumIdleSize;
/**
* master连接池大小
*/
private int masterConnectionPoolSize;
/**
* slave最小空闲连接数
*/
private int slaveConnectionMinimumIdleSize;
/**
* slave连接池大小
*/
private int slaveConnectionPoolSize;
/**
* 连接空闲超时,单位:毫秒
*/
private int idleConnectionTimeout;
/**
* 命令等待超时,单位:毫秒
*/
private int timeout;
/**
* 命令失败重试次数
*/
private int retryAttempts;
/**
* 命令失败重试次数
*/
private int retryInterval;
/**
* 失败从节点重连间隔时间
*/
private int failedSlaveReconnectionInterval;
/**
* 失败从节点校验间隔时间
*/
private int failedSlaveCheckInterval;
/**
* 单个连接最大订阅数量
*/
private int subscriptionsPerConnection;
/**
* 发布和订阅连接的最小空闲连接数
*/
private int subscriptionConnectionMinimumIdleSize;
/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;
/**
* 读取模式
*/
private ReadMode readMode;
/**
* 订阅模式
*/
private SubscriptionMode subscriptionMode;
}
}
添加 yml 配置 Redisson
--- ##### redis 配置 #####
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
# password:
timeout: 5000
# 是否开启ssl
ssl: false
lettuce:
pool:
# 连接池中的最大空闲连接 默认8
max-idle: 8
# 连接池中的最小空闲连接 默认0
min-idle: 0
# 连接池最大连接数 默认8 ,负数表示没有限制
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1
max-wait: -1
--- # redisson 客户端配置
redisson:
key-prefix: ${spring.application.name}
# 线程池数量
threads: 16
# Netty线程池数量
netty-threads: 32
# 传输模式
transport-mode: "NIO"
# 单节点配置
single-server-config:
# 客户端名称
client-name: ${spring.application.name}
# 最小空闲连接数
connection-minimum-idle-size: 32
# 连接池大小
connection-pool-size: 64
# 连接空闲超时,单位:毫秒
idle-connection-timeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
retry-attempts: 3
# 命令重试发送时间间隔,单位:毫秒
retry-interval: 1500
# 发布和订阅连接的最小空闲连接数
subscription-connection-minimum-idle-size: 1
# 发布和订阅连接池大小
subscription-connection-pool-size: 50
# 单个连接最大订阅数量
subscriptions-per-connection: 5
# dns监测时间间隔,单位:毫秒
dns-monitoring-interval: 5000
配置 Redisson
@EnableConfigurationProperties(RedissonProperties.class)
@Configuration
public class DonRedissonAutoConfiguration {
@Bean
public RedissonAutoConfigurationCustomizer redissonCustomizer(RedissonProperties redissonProperties) {
log.info("DonRedissonAutoConfiguration redissonCustomizer init ...");
return config -> {
config.setThreads(redissonProperties.getThreads())
.setNettyThreads(redissonProperties.getNettyThreads())
.setCodec(JsonJacksonCodec.INSTANCE)
.setTransportMode(redissonProperties.getTransportMode());
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
// 单机模式
if (Objects.nonNull(singleServerConfig)) {
config.useSingleServer()
.setClientName(singleServerConfig.getClientName())
// 设置 Redis key 前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setConnectTimeout(singleServerConfig.getTimeout())
.setTimeout(singleServerConfig.getTimeout())
.setRetryAttempts(singleServerConfig.getRetryAttempts())
.setRetryInterval(singleServerConfig.getRetryInterval())
.setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval());
}
// 集群模式
if (Objects.nonNull(clusterServersConfig)) {
config.useClusterServers()
.setClientName(clusterServersConfig.getClientName())
// 设置 Redis key 前缀
.setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
.setConnectTimeout(clusterServersConfig.getTimeout())
.setTimeout(clusterServersConfig.getTimeout())
.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
.setRetryAttempts(clusterServersConfig.getRetryAttempts())
.setRetryInterval(clusterServersConfig.getRetryInterval())
.setFailedSlaveReconnectionInterval(clusterServersConfig.getFailedSlaveReconnectionInterval())
.setFailedSlaveCheckInterval(clusterServersConfig.getFailedSlaveCheckInterval())
.setSubscriptionsPerConnection(clusterServersConfig.getSubscriptionsPerConnection())
.setSubscriptionConnectionMinimumIdleSize(clusterServersConfig.getSubscriptionConnectionMinimumIdleSize())
.setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
.setReadMode(clusterServersConfig.getReadMode())
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
}
};
}
}
测试分布式锁
@GetMapping("/lock")
public void lock() {
RLock lock = redissonClient.getLock("lock");
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
TimeUnit.SECONDS.sleep(1);
log.info("当前线程名:{}", Thread.currentThread().getName());
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
🌮 集群配置
--- ##### redis 配置 #####
spring:
redis:
cluster:
nodes:
- 127.0.0.1:6380
- 127.0.0.1:6381
- 127.0.0.1:6382
- 127.0.0.1:6383
- 127.0.0.1:6384
- 127.0.0.1:6385
database: 0
# 连接超时时间
timeout: 5000
# 是否开启ssl
ssl: false
lettuce:
pool:
# 连接池中的最大空闲连接 默认8
max-idle: 8
# 连接池中的最小空闲连接 默认0
min-idle: 0
# 连接池最大连接数 默认8 ,负数表示没有限制
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1
max-wait: -1
--- # redisson 客户端配置
redisson:
key-prefix: ${spring.application.name}
# 线程池数量
threads: 16
# Netty线程池数量
netty-threads: 32
# 传输模式
transport-mode: "NIO"
cluster-servers-config:
# 客户端名称
client-name: ${spring.application.name}
# 主节点最小空闲连接数
master-connection-minimum-idle-size: 24
# 主节点连接池大小
master-connection-pool-size: 64
# 从节点最小空闲连接数
slave-connection-minimum-idle-size: 24
# 从节点连接池大小
slave-connection-pool-size: 64
# 连接空闲超时,单位:毫秒
idle-connection-timeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 命令失败重试次数
retry-attempts: 3
# 命令重试发送时间间隔,单位:毫秒
retry-interval: 1500
# 失败从节点重连间隔时间
failed-slave-reconnection-interval: 3000
# 失败从节点校验间隔时间
failed-slave-check-interval: 60000
# 单个连接最大订阅数量
subscriptions-per-connection: 5
# 发布和订阅连接的最小空闲连接数
subscription-connection-minimum-idle-size: 1
# 发布和订阅连接池大小
subscription-connection-pool-size: 50
# 读取模式
read-mode: "slave"
# 订阅模式
subscription-mode: "master"
🍔 更多 API 操作参考 Redisson 官网 https://github.com/redisson/redisson
🌈 基于 Spring Cache 实现 Redis 缓存
🌮 Spring Cache 的依赖
Maven 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
🌮 Spring Cache 的配置
在 yml 文件添加配置
--- ##### spring cache 配置 #####
spring:
cache:
type: redis
cache-names: don
在主启动类开启支持
@EnableCaching
完成配置后,Spring Boot 会自动配置一个 RedisCacheManager 的类
参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
🌮 Spring Cache 缓存使用
🍅 注解 @Cacheable
缓存值
添加在查询方法上,将方法的返回值进行缓存,默认情况下,缓存的 key 就是方法的参数,缓存的 value 就是方法的返回值。key 可以手动指定,使用 SpEL
表达式。
@GetMapping("/cache/get")
@Cacheable(key = "#str")
public String get(String str) {
log.info("key {}", str);
return LocalDateTime.now().toString();
}
请求方法,查询缓存中是否有值,有则直接返回,无则执行方法并将值放入缓存。
🍅 注解 @CachePut
更新缓存值
注解添加在更新方法上,可以将方法的返回值自动更新到已经存在的 key 中。
@CachePut(key = "#str")
@GetMapping("/cache/put")
public String put(String str) {
log.info("key {}", str);
return LocalDateTime.now().toString();
}
🍅 注解 @CacheEvict
删除缓存值
注解添加在删除方法上,会将缓存中的值删除。
@CacheEvict(key = "#str")
@GetMapping("/cache/clear")
public String clear(String str) {
log.info("key {}", str);
return LocalDateTime.now().toString();
}
更多使用 https://docs.spring.io/spring-framework/docs/5.3.27/reference/html/integration.html#cache-annotations
🌮 自定义配置 RedisCacheManager
可以手动配置 RedisCacheManager,指定序列化机制,缓存时长等。
示例
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory, CacheProperties cacheProperties) {
log.info("DonCacheAutoConfiguration redisCacheManager init ...");
Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
// 配置序列化, 默认过期时间10分钟
Duration timeToLive = cacheProperties.getRedis().getTimeToLive();
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Objects.isNull(timeToLive) ? Duration.ofMinutes(10) : timeToLive)
.computePrefixWith(name -> name + "::")
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).transactionAware().build();
}
End…
https://github.com/redisson/redisson
https://spring.io/projects/spring-boot