1、需求背景
项目需要提供一个管理界面给内部人员操作用户信息,需要在修改用户信息后删除用户的redis缓存。用户所在的区域不同,其redis服务地址也不相同,因此需要管理多个redis连接,且redis要求以集群方式并支持ssl进行连接。
2、实现方案
为每个区域定义一个regionId,用来关联不同区域的redis连接信息,将redis连接信息存于数据库中。采用工具类的方式,构建RedisTemplate对象存于以Map形式存于工具类中的静态变量当中,key为regionId,value为RedisTemplate。每个RedisTemplate构建时机为第一次需要用到它时。
3、具体实现
1、首先是存储redis信息的实体类
public class RedisInfo {
private String regionId;
private String ip;
private int port;
private String pwd;
// 是否使用ssl
private boolean ssl;
// 超时时间
private long timeout;
}
2、其次是工具类的实现
public class RedisManage {
private static Map<String, RedisTemplate<String, Object>> redisTemplateMap = new HashMap<>();
/**
* 获取RedisTemplate方法
* @param redisInfo 数据库连接信息
* @return
*/
public static RedisTemplate<String, Object> getRedisTemplate(RedisInfo redisInfo) {
// 首先从map中获取
RedisTemplate<String, Object> redisTemplate = redisTemplateMap.get(redisInfo.getRegionId());
// 获取不到就构建一个并存入redis
if (redisTemplate == null) {
// redis连接配置,RedisClusterConfiguration为集群配置对象,集群下无需指定database
RedisClusterConfiguration config = new RedisClusterConfiguration();
config.clusterNode(redisInfo.getIp(), redisInfo.getPort());
config.setPassword(RedisPassword.of(redisInfo.getPwd()));
// 创建 LettuceClientConfiguration,用于构建LettuceConnectionFactory
LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofMillis(redisInfo.getTimeout()))
.shutdownTimeout(Duration.ofMillis(redisInfo.getTimeout()));
// 根据需要启用ssl
if (redisInfo.isSsl()) {
builder.useSsl();
}
// 创建 LettuceConnectionFactory,集群连接的关键
LettuceConnectionFactory factory = new LettuceConnectionFactory(redisStandaloneConfiguration, builder.build());
// 重要,调用afterPropertiesSet才能正常使用
factory.afterPropertiesSet();
redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(factory);
// 将对象属性名序列化成string类型的key,并将string类型的key反序列化成属性名
StringRedisSerializer keySerializer = new StringRedisSerializer();
// 数据绑定工具,操作redis时需要对key和value进行序列化和反序列化,这个对象可以设置规范
ObjectMapper mapper = new ObjectMapper();
// 反序列化时遇到未知属性不抛出异常
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 序列化空bean时不抛出异常
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 反序列化时对象的成员变量与缓存中的对象类型对不上或代码中不存在该类时,不抛出异常
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
// 序列化时间字段时,以文本形式序列序列化而非时间戳
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 支持Java8的日期类型,如LocalDate、LocalTime、LocalDateTime等
mapper.registerModule(new JavaTimeModule());
// 序列化时保留对象类型信息
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
// 序列化和反序列化工具,使用jackson库将对象序列化成json字符串,并将json字符串反序列化成对象,应用了上面ObjectMapper的各项规范
RedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(mapper);
redisTemplate.setKeySerializer(keySerializer);
redisTemplate.setValueSerializer((RedisSerializer)valueSerializer);
redisTemplate.setHashKeySerializer(keySerializer);
redisTemplate.setHashValueSerializer((RedisSerializer)valueSerializer);
// 重要,非托管Spring容器构建的RedisTemplate需要调用afterPropertiesSet()方法
redisTemplate.afterPropertiesSet();
redisTemplateMap.put(redisInfo.getRegionId(), redisTemplate);
}
return redisTemplate;
}
}
3、走过的弯路
1、使用单体配置对象连接集群失败
在一开始,配置redis连接信息的时候,使用的是单体配置对象RedisStandaloneConfiguration配置了连接信息,代码如下:
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(redisInfo.getIp());
redisStandaloneConfiguration.setPort(redisInfo.getPort());
redisStandaloneConfiguration.setPassword(RedisPassword.of(redisInfo.getPwd()));
// 设置database
redisStandaloneConfiguration.setDatabase(0);
结果发生了以下错误:
Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: MOVED 12376 redis.eadfe.cache.amazonaws.com:6379
这是因为使用了单体配置对象配置连接信息,导致连接集群失败,改为RedisClusterConfiguration 后解决了该问题