缓存使用模式分为两大类:Cache-Aside和Cache As SoR 专业名词:
- SoR(system-of-record):记录系统,或者可以叫数据源,实际存储原始数据的系统
- Cache:缓存,是SoR的快照数据,Cache的访问速度比SoR要快,放入Cache的目的是提升系统速度,减少回源到SoR的次数
- 回源:回到数据源头检索数据,Cache没有命中需要回到SoR读取数据,这叫做回源
1)、Cache-Aside
Cache-Aside:即业务代码围绕Cache编写,由业务代码维护缓存; 如:
读场景
data = cache.getIfPresent(key); if(data == null){ data = loadFromSoR(key); cache.put(key,data) } 先从缓存中获取,没有查询SoR,再放入缓存
写场景(主动更新和删除时被动读取)
- 双写模式 //1、先将数据写入SoR writeToSoR(k,v); //2、执行成功后立即同步写入缓存 cache.put(k,v) 或者
- 失效模式 //1、先将数据写入SoR writeToSoR(k,v); //2、失效缓存,下次读取时从缓存中加载(进入读场景), cache.invalidate(k);
并发更新问题与解决(多个缓存实例,同时更新自己里面的同样数据) 1、考虑使用中间件如Canal订阅binlog,进行增量更新分布式缓存,不会存在缓存数据不一致问题。但是会有延迟,我们可以调整合理的过期时间来容忍这个延迟 2、读服务,可以考虑一致性哈希,相同的操作负载均衡到同一个实例,从而减少并发几率。
2)、Cache-As-SoR
即把Cache当做SoR,所有操作都是对Cache进行,然后Cache委托SoR进行数据的真实读写。即业务代码只看到Cache的操作,看不到关于SoR的代码;三种实现模式:read-through、 write-through、write-behind
- read-through 业务代码首先调用Cache,Cache不命中,由Cache回源到SoR(类似回调的方式或者Cache配置的方式),而不是业务代码。【Guava、Ehcache均支持这种模式】 优点:业务代码整洁
- write-through 称为穿透写模式/直写模式。业务代码调用Cache写数据,然后由Cache负责写缓存和写SoR,而不是业务代码。
- write-behind 也叫write-back;回写模式。不同于write-through的是,write-through是同步写,而write-back是异步写,异步成功后可以实现批量写、合并写、延时写等
3)、Copy-Pattern
应用级缓存:(数据一定不能是脏数据)【Map、Ehcache、Guava】 Mybatis:二级缓存Map if(map.get(1)){ return obj; }else{ queryFromDb } obj = cache; 缓存读取来的数据是否是改写安全; obj = cache; obj2 = new Obj(); BeanUtils.copyProperties(obj,obj2) obj2.compute(); //写时复制 obj = ObjHaha(); obj2 = new Obj(); BeanUtils.copyProperties(obj,obj2) map.put(k, obj2); obj.compute(); 分布式缓存:
缓存使用两种复制模式,注意。Copy-On-Read(读时复制)、Copy-On-Write(写时复制)。 有些进程内缓存很多是基于引用的,所以拿到缓存中的数据如果进行修改,可能发生不可预测的问题。 读时复制:读取到的缓存的值,复制内容封装一个新的对象。 写时复制:给缓存中写的值,复制一个新的对象写入