说起分布式缓存,如今redis大行其道。不过,我们在创建缓存组件时,需要着重考虑如下几点:
1.客户端选型
本组件基于springboot2的默认实现,即lettuce客户端。不同客户端区别如下:
名称 | 描述 | 优缺点分析 |
jedis | 1.springboot1.5.*默认 2.老牌客户端,使用稳定,但基于阻塞IO,其客户端实例本身非线程安全,要借助连接池建立物理连接 | **优点:**支持全面的 Redis 操作特性(可以理解为API比较全面)。缺点:1.使用阻塞的 I/O,且其方法调用都是同步的,程序流需要等到 sockets 处理完 I/O 才能执行,不支持异步;2.Jedis 客户端实例不是线程安全的,所以需要通过连接池来使用 Jedis |
Lettuce | Springboot2.*默认,官方推荐 Lettuce底层基于netty,支持异步API。1个连接实例支持多线程复用。 | **优点:**1.支持同步异步通信模式;2.Lettuce 的 API 是线程安全的,如果不是执行阻塞和事务操作,如BLPOP和MULTI/EXEC,多个线程就可以共享一个连接。**缺点:**学习成本相对jedis来说较高 |
Redisson | Redisson是一个在Redis的基础上实现的 Java驻内存数据网格。基于netty框架通信机制,宗旨为促进使用者对Redis的关注分离 | **优点:**1.提供一些高级用法,可提升开发效率,让开发者有更多的时间来关注业务逻辑2.提供很多分布式相关操作服务。例如分布式锁、分布式集合。可通过Redis支持延迟队列等。**缺点:**Redisson 对字符串的操作支持比较差。 |
2.序列化方式
同样,我们默认采用jackson的序列化方式进行缓存value的序列化和反序列化。
当然,根据大家的习惯,也许有些公司使用fastJson作为首选。或者二者兼而有之。
那如果既要满足jackson的方式,又要满足fastJson的方式应该怎么办呢?
敲重点:使用配置参数
比如我们可以使用如下配置,来控制业务接入使用何种序列化方式。
makefile
frame.cache.serializer:jackson
指定了对应的序列化方式,可以看到配置类核心代码如下:
less
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key的序列化直接使用StringRedisSerializer
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
//根据配置属性判断value使用何种序列化
if(JASKSON.equalsIgnoreCase(serializerType)) {
template.setValueSerializer(jacksonSerializer());
template.setHashValueSerializer(jacksonSerializer());
}
if(FASTJSON.equalsIgnoreCase(serializerType)) {
template.setValueSerializer(fastJsonSerializer());
template.setHashValueSerializer(fastJsonSerializer());
}
template.afterPropertiesSet();
log.info("redisClient2 configuration info:{}",template.getConnectionFactory().toString());
return template;
}
注意,上述的两个if,就是针对不同序列化方式做的兼容处理。
以jackson序列化为例,其配置bean如下:
typescript
/**
* jaskson方式
* @return
*/
private Jackson2JsonRedisSerializer<Object> jacksonSerializer(){
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
// ObjectMapper 将Json反序列化成Java对象
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
3.兼容性
抛个问题:如果有些业务不需要接入cache,有些需要接入,那我们有哪些方式可以处理呢?
a.pom方式
控制是否加入pom依赖,或者排除依赖
b.Conditional语义
读者可以使用springboot关于Conditional的语法控制相关service bean的创建
比如:@ConditionalOnMissingBean、@ConditionalOnBean、@@ConditionalOn****
c.注解开关
比如使用EnableCache注解(其他组件类似,通过Enable的语义控制业务是否激活某个功能组)
4.防腐层设计
我们需要考虑接入redis服务器的方式,就要对公司目前的redis部署架构进行调研。比如主从模式、集群模式,还是哨兵模式?
一般而言,公司的运维服务器架构不会轻易变化的。特别是如果中间件是基于云端的服务,那么架构模式一般是固定下来的。
如果采用的是主备模式,那么我们可以直接基于springboot的redis的starter就可以进行服务端连接。
xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
并配置客户端连接入参即可:
ini
spring.redis.host=****
spring.redis.port=***
spring.redis.password=***
但,如果采取的是其他模式,就需要我们自行创建客户端的连接bean了。
思考题:
如果我们选用了redis作为分布式锁的实现,那么此功能是要跟缓存组件放在一起呢?还是独立出来一个分布式锁组件更合适?虽然两者都是基于redis的实现。