多级缓存
1.依赖和配置
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.7.4</version>
</dependency>
jetcache:
# 统计间隔,默认0:表示不统计
statIntervalMinutes: 1
metrics: true # 必须显式开启
# areaName是否作为缓存key前缀,默认True
areaInCacheName: false
local:
default:
# 已支持可选:linkedhashmap、caffeine
type: linkedhashmap
# key转换器的全局配置,当前只有:fastjson, @see com.alicp.jetcache.support.FastjsonKeyConvertor
keyConvertor: fastjson
# 每个缓存实例的最大元素的全局配置,仅local类型的缓存需要指定
limit: 100
# jetcache2.2以上,以毫秒为单位,指定多长时间没有访问,就让缓存失效,当前只有本地缓存支持。0表示不使用这个功能
expireAfterAccessInMillis: 30000
remote:
default:
# 已支持可选:redis、tair
type: redis
# 连接格式@see:https://github.com/lettuce-io/lettuce-core/wiki/Redis-URI-and-connection-details
host: 127.0.0.1
port: 6379
password: JOTBOP1Qc4y1n1jn
keyConvertor: fastjson2
# 序列化器的全局配置。仅remote类型的缓存需要指定,可选java和kryo
broadcastChannel: projectA
valueEncoder: java
valueDecoder: java
poolConfig:
minIdle: 5
maxIdle: 20
maxTotal: 50
# 以毫秒为单位指定超时时间的全局配置
expireAfterWriteInMillis: 5000
2.启动类
@SpringBootApplication
@MapperScan("com.mapper")
@EnableMethodCache(basePackages = "com")
public class Jetcache
{
public static void main( String[] args )
{
SpringApplication.run(Jetcache.class,args);
}
}
3.核心类
@Service
public class TBaseOrgunitDistService {
@Autowired
private TBaseOrgunitDistMapper dao;
/**
* area: 配置文件中关联的缓存域,比如上面的`default`,`longTime`,`shortTime`。默认`default`,继承缓存域的中的相关配置
* cacheType: 枚举值。`BOTH`:本地和远程都缓存,`LOCAL`:本地缓存,`REMOTE`远程缓存
* localLimit: 本地缓存条目限制,仅对本地缓存有效
* key: 缓存的key,支持SpEL表达式
* condition: 前置条件(方法执行前判断),结果为false则不添加缓存,支持SpEL表达式
* postCondition: 后置条件(方法执行后判断),结果为false则不添加缓存,支持SpEL表达式(可以通过#result获取返回结果)。
* @param id
* @return
*/
@Cached(name = "orgUnit:", key = "#id", cacheType = CacheType.BOTH)
public TBaseOrgunitDist get(String id) {
// 实际数据库查询逻辑
return dao.selectById(id);
}
// 更新方法:保持一致性
@CacheUpdate(name = "orgUnit:", key = "#entity.id", value = "#entity")
public void update(TBaseOrgunitDist entity) {
// dao.updateById(entity);
}
// 删除方法:双级失效
@CacheInvalidate(name = "orgUnit:", key = "#id")
@Transactional
public void delete(String id) {
dao.deleteById(id);
}
}
- 实体类
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_base_orgunit_dist")
public class TBaseOrgunitDist extends Model<TBaseOrgunitDist> implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
@TableField("name")
private String name;
@TableField("age")
private Integer age;
}
4.测试类
@RestController
@RequestMapping("/test")
public class TestController{
@Autowired
private TBaseOrgunitDistService service;
@GetMapping("/get")
public TBaseOrgunitDist get() {
return service.get("1");
}
@GetMapping("/update")
public void update() {
// 序列化匿名内部类 Jetcache 的 Redis 序列化配置使用 Java 原生序列化(配置中valueEncoder: java),
// 而匿名内部类的类名会包含外部类信息(如TestController$1),导致反序列化时找不到对应类。
TBaseOrgunitDist dist = new TBaseOrgunitDist();
dist.setId("1");
dist.setName("name");
dist.setAge(18);
service.update(dist);
}
@GetMapping("/delete")
public void delete() {
service.delete("1");
}
}
5.说明
BOTH适用场景:只有qps很高且命中率很高的Cache实例,才需要设置为两级缓存(BOTH),在本地local cache内先查找就是为了给redis挡一下,降低一点redis的负担。如果qps很低,没必要设置BOTH(不要过早、过度优化);如果QPS很高而命中率很低,说明有很多不同key,这种情况下也不适合BOTH,因为local jvm根本就装不下那么多,localLimit多了会降低JVM的gc性能,少了local命中率上不去。在qps和命中率双高的情况下,cacheType设置为BOTH,这时有一个问题,一个JVM对缓存进行了更新操作(这个更新包括PUT和REMOVE),它自己的local JVM和redis里面的数据更新了,可是其它JVM的local cache没更新,所以就需要一个机制来让其它JVM中local cache失效。
原有的解决方案:只提供了一个localExpire的属性,让localExpire的超时时间短一点,超时后local get miss,然后去redis里面get,再刷新local。但这个办法有时候确实难以让人满意,才新增解决方案syncLocal。
所以正确的做法是:
1.一切走默认,就用cacheType=remote
2.觉得redis负担大了,根据80%/20%的原则,找到qps和命中率双高的几个地方,改成BOTH,别的地方都不用管
3.改成BOTH以后如果不能接受local cache没更新,设置localExpire短一点,让local cache尽快失效,保证最终一致
4.不能接受localExpire方案的,再开syncLocal

浙公网安备 33010602011771号