1.内存缓存的使用
1.1 自定义HashMap实现
如题所说,自己实现key-value的map,使用定时器管理缓存的清空之类的, 比较麻烦,不建议使用
1.2 使用现有的内存缓存框架
spring引入缓存框架是非常容易的, 详细使用方式可以参考Spring Boot 缓存配置
在之前做的项目中使用的是 caffenie 框架, 该框架为 google guava 框架内缓存模块的升级版本, 提高了性能, 增加了更多的功能, 使用方式可以参考Caffeine 缓存框架
简单介绍一下使用方式
1.2.1 通用配置方式
在application.yml文件中增加配置
cache:
type: caffeine
caffeine:
spec: maximumSize=%d,expireAfterWrite=%ds
在SpringBoot启动类中增加注解
@EnableCaching
@SpringBootApplication
class Application {
companion object {
@JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
}
}
在需要被缓存的方法上增加注解
/* 对方法增加缓存 */
@Override
@Cacheable
public List<ConfigAllSite> listConfigAllSite() {
return configMapper.listConfigAllSite();
}
/* 对类中的所有方法增加缓存 */
@Component
@CacheConfig(cacheNames = CacheNames.ConfigRefer)
@Cacheable
public class ConfigRepositoryImpl implements ConfigRepository {
//````
}
其中CacheConfig指定了该缓存块的名称,使用其他注解进行更新和清除操作时会用到
1.3 使用redis
内存缓存框架有一定的局限性, 包括分布式缓存以及大数据量缓存的恢复和重建等
redis很好的解决了这些问题, 而且性能也并没有差太多, 比数据库快许多
使用redis的方式
1.3.1 添加依赖
spring-data-redis是spring为redis操作开发的一套框架, 对于redis的简单使用引用这个包就十分方便了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在application.yml中声明
// 主从模式, 配置host,port,password
spring:
redis:
host: xxx
password: xxx
port: xxx
// cluster模式, 配置集群所有节点host, port
spring:
redis:
cluster:
nodes: 127.0.0.1:6379,127.0.0.1:6380...
password: xxx
port: xxx
我们可能引入一些序列化工具, 因为spring 默认使用 jvmserialiser 来序列化对象, 存入redis的对象必须继承 serialer 接口, 非常不方便,
spring 自带 jackson, 我们可以使用jackson, 也可以使用 alibaba 的 fastjson
使用 jackson
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setDefaultSerializer(serializer);
return redisTemplate;
}
}
使用 fastjson (文档), 首先需要引用依赖
<!--https://github.com/alibaba/fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
然后配置, 基本同上
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer();
redisTemplate.setDefaultSerializer(serializer);
return redisTemplate;
}
}
fastjson其他使用方法可以参考文档
2 使用多个缓存时发生冲突的问题
有时可能我们的工程既需要使用redis, 又因为对速度的需求需要使用内存缓存, 这时就需要增加配置来适配多个manager的问题.
一般情况下, 引用了 spring-data-redis 模块后, 在不进行额外配置的情况下spring会默认使用redis的cachemanager作为全局的cachemanager. 也就是@Cacheable相关注解也会使用redis作为管理器, 这是与我们的需求违背的.
首先还是需要增加依赖和相关配置, 如前几点所说, 这里不做叙述
接下来需要增加一个新的 cachemManager
@Configuration
@EnableCaching
public class SimpleCaffeineCacheManager {
// CacheManager bean name
public static final String CACHE_MANAGER_BEAN_NAME = "caffeineCacheManager";
// 最多存储的缓存条数
private static final int DEFAULT_MAX_SIZE = 20000;
// 默认过期时间
private static final int DEFAULT_TTL_SECONDS = 300;
@Bean
@Qualifier(CACHE_MANAGER_BEAN_NAME)
public CacheManager caffeineCacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
String spec = String.format("maximumSize=%d,expireAfterWrite=%ds", DEFAULT_MAX_SIZE, DEFAULT_TTL_SECONDS);
CaffeineSpec caffeineSpec = CaffeineSpec.parse(spec);
manager.setCaffeineSpec(caffeineSpec);
return manager;
}
}
然后在其他地方使用@Cacaheable相关主解时, 需要指定CacheManager
@CacheConfig(cacheNames = CacheNames.ConfigRefer, cacheManager = SimpleCaffeineCacheManager.CACHE_MANAGER_BEAN_NAME)
@Cacheable
public class ConfigRepositoryImpl implements ConfigRepository {
//...
}
这样就解决了多个CacheManager的冲突问题
备注
今天使用时候碰到了个bug, 使用@Cacheable标注类, 在类的方法中使用mybatis的mapper的查询结果作为返回值, 会出现说返回值是单个无法转换为list的bug.
查询了一下发现是由于key的生成策略导致的:
默认的key是通过KeyGenerator生成的,其默认策略如下:
1. 如果方法没有参数,则使用0作为key;
2. 如果只有一个参数的话则使用该参数作为key;
3. 如果参数多于一个则使用所有参数的hashcode作为key;
所以添加了一个keygenerator来避免这个问题
用 Cachemanager 继承 CachingConfigurerSupport 类, 并实现keygenerator方法
public class SimpleCaffeineCacheManager extends CachingConfigurerSupport {
//...
@Bean
public KeyGenerator keyGenerator() {
return (target, method, objects) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName() + " :");
sb.append(method.getName() + "=-=");
for (Object object : objects) {
if (object == null) sb.append("|-|-|");
else sb.append(object.toString());
}
return sb.toString();
};
}
}