目录
SpringBoot使用Redis的核心逻辑
SpringBoot的Redis配置类
序列化与反序列化
JDK Serialization (Java 默认序列化)
String Serialization (字符串序列化)
JSON Serialization (JSON 序列化)
封装Reids操作接口
定义通用接口
接口实现
模板对象线程安全问题
自定义序列化器线程安全
自定义键值转换器线程安全
关联@Cacheable注解使用Redis
@Cacheable基础使用
@Cacheable
@CachePut
@CacheEvict
@Caching
@Cache适配Redis作为缓存存储器
关于Redis相关的组件主要由Reids连接池(RedisConnectionFactory),对象模板(RedisTemplate)、缓存管理器(RedisManager),根据不同的缓存需求场景构造对象模板、缓存管理器对象时指定不同的序列化器,并将其注册到连接池中。
其中可以根据不同项目、场景对于序列化、CacheManger的需求,定义自定义的序列化器和缓存管理器进行扩展。
引入Redis的依赖:
org.springframework.boot
spring-boot-starter-data-redis
进行yml文件相关配置
spring:
redis:
host: 127.0.0.1 # Redis 服务器的主机地址
port: 6379 # Redis 服务器的端口号
password: XXX # Redis 服务器的密码(如果存在)
database: 0 # 使用的数据库索引,默认为 0
timeout: 5000 # 连接超时时间(毫秒),默认为 2000 毫秒
lettuce:
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1 # 连接池最大阻塞等待时间(负值表示不限制)
max-idle: ¾ # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
进行configuration类编写,进行对象模板的构造,Spring Boot 提供了原生的Start来适配Redistribution,原则上不需要额外构建RedisConnectionFactory 连接池的Bean, RedisTemplate 和 StringRedisTemplate 两种对象模板是Spring框架自带的操作 Redis的对象模板。原则上是不需要进行额外的操作,这里主要是对对象模板的序列化方式进行指定。
部分SpringBoot版本直接引用spring-boot-starter-data-redis可能不会自动化配置生成RedisConnectionFactory ,此种情况需要进行手动创建连接池的bean
Redis连接池Bean配置
@Configuration
public class RedisConfigure {
@Autowired
RedisProperty reidis;
@Bean
public RedisConnectionFactory getRedisConnectionFactory(){
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(reidis.getHost()); //通过实体获取yml配置的内容
configuration.setPort(redis.getPort());
configuration.setDatabase(redis.getDataBase());
//如果还有其他参数同样注册上
return new LettuceConnectionFactory(configuration);
}
}
对象模板注册:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 设置键的序列化器
template.setKeySerializer(new StringRedisSerializer());
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
Jackson2JsonRedisSerializer
针对 RedisTemplate的配置,主要是是序列化,包括键/值序列化、默认序列化、针对Hash类型的数据的序列化等
Redis 支持多种序列化和反序列化机制,序列化主要是将复杂的数据结构转换为可以在 Redis 中存储的字节流,以及将这些字节流转回原始的数据结构。根据业务、项目中的数据结构特点、Redis的使用,要选择适合的序列化机制,具体的序列化机制有以下几种:
优点: JDK 序列化是一种通用的序列化方法,它可以序列化任何实现了 Serializable 接口的对象。
缺点: 序列化后的数据较大,效率较低,且不兼容非 Java 环境。
示例:
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
优点: 简单直观,适用于简单的字符串数据。
缺点: 不适合复杂对象。如果需要缓存Java中的对象,则不建议使用该序列化方法
示例:
import org.springframework.data.redis.serializer.StringRedisSerializer;
StringRedisSerializer stringSerializer = new StringRedisSerializer();
优点: 产生的数据较小,易于阅读和调试,跨语言支持良好。
缺点: 序列化和反序列化的性能可能不如原生二进制格式。
示例:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
ObjectMapper objectMapper = new ObjectMapper();
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
创建基本的Redis服务类接口,便于使用:
/**
* Redis 缓存数据操作
*/
public interface RedisCacheServer {
/**
* 缓存 String 内容
* @param key 键
* @param value 值
*/
void setString(final String key, final String value);
/**
* 删除 String 缓存
* @param key 键
*/
void removeString(final String key);
/**
* 读取 String 缓存
* @param key 键
* @return 值
*/
String getString(final String key);
/**
* 读取 String 缓存,如果键不存在则返回缺省值
* @param key 键
* @param defaultStr 缺省值
* @return 值或缺省值
*/
String getString(final String key, final String defaultStr);
/**
* 缓存哈希表
* @param key 键
* @param map 哈希表
*/
void setHash(final String key, final Map map);
/**
* 删除哈希表缓存
* @param key 键
*/
void removeHash(final String key);
/**
* 读取哈希表
* @param key 键
* @return 哈希表
*/
Map getHash(final String key);
/**
* 读取哈希表,如果键不存在则返回缺省值
* @param key 键
* @param defaultMap 缺省哈希表
* @return 哈希表或缺省值
*/
Map getHash(final String key, final Map defaultMap);
/**
* 向列表中添加元素
* @param key 键
* @param values 值
*/
void addList(final String key, final List values);
/**
* 删除列表缓存
* @param key 键
*/
void removeList(final String key);
/**
* 获取全部数据
* @param key key
* @return List
* @param
*/
List getList(final String key);
/**
* 从列表中读取指定范围的元素
* @param key 键
* @param start 开始索引
* @param end 结束索引
* @return 元素列表
*/
List getList(final String key, final long start, final long end);
/**
* 向集合中添加成员
* @param key 键
* @param members 成员
*/
void addSet(final String key, final Set members);
/**
* 删除集合缓存
* @param key 键
*/
void removeSet(final String key);
/**
* 读取集合中的所有成员
* @param key 键
* @return 成员集合
*/
Set getSet(final String key);
/**
* 向有序集合中添加成员
* @param key 键
* @param scoreMembers 分数-成员映射
*/
void addZSet(final String key, final Map scoreMembers);
/**
* 删除有序集合缓存
* @param key 键
*/
void removeZSet(final String key);
/**
* 从有序集合中读取指定范围的成员
* @param key 键
* @param start 开始索引
* @param end 结束索引
* @return 成员集合
*/
Set getZSet(final String key, final long start, final long end);
/**
* 获取有序集合中成员的分数
* @param key 键
* @param member 成员
* @return 分数
*/
Double getZSetScore(final String key, final T member);
}
对应实现类
@Component
public class RedisCacheServerImpl implements RedisCacheServer {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate redisTemplate;
// 字符串操作
@Override
public void setString(final String key, final String value) {
stringRedisTemplate.opsForValue().set(key, value);
}
@Override
public void removeString(final String key) {
stringRedisTemplate.delete(key);
}
@Override
public String getString(final String key) {
return stringRedisTemplate.opsForValue().get(key);
}
@Override
public String getString(final String key, final String defaultStr) {
String result = stringRedisTemplate.opsForValue().get(key);
return result != null ? result : defaultStr;
}
// 哈希表操作
@Override
public void setHash(final String key, final Map map) {
redisTemplate.opsForHash().putAll(key, map);
}
@Override
public void removeHash(final String key) {
redisTemplate.delete(key);
}
@Override
public Map getHash(final String key) {
Map
总的来说,Redis 服务器是单线程的,因此它是线程安全的。但是在客户端应用程序中,特别是多线程环境下,需要确保正确地管理和使用 RedisTemplate 和相关资源的线程问题。
RedisTemplate 和 StringRedisTemplate 是 Spring Data Redis 提供的线程安全的模板类。它们内部使用连接池来管理 Redis 连接,连接池本身也是线程安全的。
只需要确保在配置 RedisTemplate 和 StringRedisTemplate 时没有引入任何非线程安全的组件。 也就是需要保证模板对象时所使用的序列化器、键和值的转换器以及其他相关组件必须是线程安全的。
常见的序列化器包括 JdkSerializationRedisSerializer, StringRedisSerializer, GenericJackson2JsonRedisSerializer 等,这些序列化器通常是线程安全的。需要注意的是自定义的序列化器,需要确保它是线程安全的。
public class CustomSerializer implements RedisSerializer {
@Override
public byte[] serialize(MyObject t) throws SerializationException {
// 确保这里的实现是线程安全的
// 例如,不要在这里使用静态变量或共享状态
// 可以使用线程局部变量(ThreadLocal)来存储临时状态
return ...;
}
@Override
public MyObject deserialize(byte[] bytes) throws SerializationException {
// 确保这里的实现是线程安全的
return ...;
}
}
RedisTemplate 和 StringRedisTemplate 也可以使用键和值的转换器。这些转换器也应该设计成线程安全的。
public class CustomKeyConverter implements Converter {
@Override
public String convert(String source) {
// 确保这里的实现是线程安全的
// 例如,不要在这里使用静态变量或共享状态
return "prefix:" + source;
}
}
接下来基于SpringBoot的@Cache相关注解,适配到Redis作为主缓存,基于注解进行数据操作。
@Cache 注解家族是 Spring Framework 提供的一种声明式缓存机制,可以非常方便地在方法级别启用缓存。使用该家族注解必须在启动类或者配置类中通过@EnableCaching开启缓存使用:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
关于@Cache家族主要有以下几个常用注解及其使用方法:
作用:@Cacheable 注解用于标记一个方法,表示该方法的结果是可以被缓存的。当方法被调用时,如果缓存中存在结果,则直接返回缓存中的结果,否则执行方法并将结果存入缓存。
常用参数:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
//缓存结果不为null的数据,key为参数的id
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User getUserById(Long id) {
// ……读取数据库,组装数据
return new User(id, "John Doe");
}
}
作用:@CachePut 注解用于标记一个方法,表示该方法的结果应该总是被放入缓存中,无论缓存中是否已经存在该结果。该方法总是会被执行。
常用参数:
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@CachePut(value = "users", key = "#user.id", unless = "#result == null")
public User updateUser(User user) {
// 模拟更新用户信息
return user;
}
}
作用:@CacheEvict 注解用于标记一个方法,表示该方法在执行完毕后应该清除缓存中的某些条目。
常用参数:
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
// 模拟删除用户
}
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsers() {
// 清空所有用户的缓存
}
}
作用:@Caching 注解用于组合多个 @Cacheable、@CachePut 和 @CacheEvict 注解,可以在一个方法上同时应用多个缓存操作。
常用参数:
import org.springframework.cache.annotation.Caching;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Caching(
put = {
@CachePut(value = "users", key = "#user.id", unless = "#result == null")
},
evict = {
@CacheEvict(value = "oldUsers", allEntries = true)
}
)
public User updateUserAndClearOldUsers(User user) {
// 更新用户信息并清空 oldUsers 缓存
return user;
}
}
相关Spring的缓存注解适配器的原理可以参考设计模式:从Spring的缓存实现来理解适配器模式-CSDN博客
使用@Cache家族注解以Redis进行数据交互,主要需要向Redis的连接池注册一个RedisManager组件。
以下我们自定义一个支持指定缓存时长的缓存管理器
public class CustomRedisCacheManager extends RedisCacheManager {
private final ReentrantLock lock = new ReentrantLock();
public CustomRedisCacheManager(RedisCacheWriter connectionFactory, RedisCacheConfiguration defaultCacheConfig) {
super(connectionFactory, defaultCacheConfig);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
//创建一个可以在@Cache中通过#符指定缓存时长的对象
String[] arrays = StringUtils.delimitedListToStringArray(name,"#");
name = arrays[0];
//如果存在#则代表用户使用了缓存时长,此数据超时就要清除
if(arrays.length > 1){
long timeOut = Long.parseLong(arrays[1]);
cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(timeOut));
}
return super.createRedisCache(name, cacheConfig);
}
public void clearCache() {
try {
lock.lock(); // 确保线程安全
for (String cacheName : getCacheNames()) {
getCache(cacheName).clear();
}
} finally {
lock.unlock();
}
}
}
在之前的Redis配置类中多注入一个CacheManger的Bean:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 设置键的序列化器
template.setKeySerializer(new StringRedisSerializer());
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
Jackson2JsonRedisSerializer
至此,则可以通过@Cache家族注解向Redis中进行数据交互了:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
//缓存结果不为null的数据,key为参数的id, 数据会缓存 10 S,10S后自动消除
@Cacheable(value = "users#600", key = "#id", unless = "#result == null")
public User getUserById(Long id) {
// ……读取数据库,组装数据
return new User(id, "John Doe");
}
}