Introducing
Your new presentation assistant.
Refine, enhance, and tailor your content, source relevant images, and edit visuals quicker than ever before.
Trending searches
4.1 批量获取模式
一、从缓存批量获取,分为三部分:
1-获取到模型
2-获取到空标记
3-未获取到
二、将上面的第3部分去DB批量获取,
分为两部分:
1-未获取到——需要去缓存空标记
2-获取到——需要写入缓存
三、将第一步的第3部分,
和第二步的第2部分,
合并并返回给业务逻辑
这里有一种变体,是第二部分改用循环单个获取,并加入防并发(不建议使用)
Rich Client 方式获取缓存:
性能更高,但更不可控
缓存层次:
domain => model => html
=> http response =>browser
SSI / ESI / CSI
更细的粒度
更精确的控制
Squid/Varnish/Apache Traffic Server
@Cacheable与Redis的偏门组合
性能黑洞
3.3.5 Mutex二级缓存主动刷新
与《mutex主动刷新》思想类似
但刷新的是jvm内的二级缓存
mutex变量也是放在jvm本地
让各线程争抢
与防并发结合
抢到mutex的线程负责去获取远程缓存
并刷新本地缓存
这期间其余线程继续从本地缓存获取
与防穿透结合
3.3.6 包装器异步更新
4.2 批量获取模板
模型被包装之后置入缓存
实际缓存过期时间设置为无穷大
真实的过期时间保存在包装器中
Wrapper<Model> wrapper = cache.get(key);
if(wrapper.expire<now()){
expiredQueue.add(key);
}
Model model = wrapper.get();
return model;
实际的刷新缓存操作在一个独立的线程中
这样可以保证缓存命中率为100%,
但缓存失效和更新不是那么及时
操作模板
图中的expiredQueue需要具备:
有序+唯一+跨进程
——可以考虑放在redis的ZSET
3.3.4 Mutex主动刷新
将繁复的批量获取过程封装在模板中
Model model = cache.get(key);
if (model == null) {
if (cache.add(key_mutex, 5*1000) == true) {
model = db.get(key);
cache.set(key, model);
cache.delete(key_mutex);
} else {
//休眠100毫秒并重试
}
} else {
if (model.timeout <= now()) {
if (cache.add(key_mutex, 5*1000) == true) {
//延长内外两个失效时间
model.timeout += 60*1000;
cache.set(key, model, KEY_TIMEOUT);
//从db获取并用新模型刷缓存
//注意内部失效时间早于外部失效时间
model = db.get(key);
model.timeout = KEY_TIMEOUT - 60*1000;
cache.set(key, model, KEY_TIMEOUT);
cache.delete(key_mutex);
} else {
//休眠100毫秒并重试
}
}
} else {
return model;
}
3.3.3 Mutex方式
Model model = cache.get(key);
if (model != null){
return model;
}
if (cache.add(key_mutex, 5*1000) == true) {
model = db.get(key);
cache.set(key, model);
cache.delete(key_mutex);
return model;
} else {
//休眠100毫秒再重试add
}
例子中采用memcache的add操作
3.3.7 不能滥用防并发
3.3 缓存并发
防并发是有代价的,不能滥用
需要评估防并发的代价与带来的好处:
3.3.2 Future方式
场景1:并发会导致若干次DB主键查询
——没必要防并发
“读缓存(未命中)->读DB->写缓存”
不是原子操作
场景2:并发会导致ES扫描十亿数据
——需要精心设计防并发
Future<Model> future = new FutureTask<Model(){
new Callable<Model>(){
Model call(){
cache.get(key);
}
}
}
Future<Model> future =
concurrentHashMap.putIfAbsent(key, future);
if(future != null){
return future.get();
}
future.run(); //在本线程内运行
Model model = future.get();
concurrentHashMap.remove();
return model;
缓存失效的一刻,如果有海量读请求,
会有大量请求同时进入DB
利用了Future对象“执行完成就会停留在最终态,可以被get”的特性
这种写法看起来怪怪的……
解决思路:
DCL/Future/Mutex/异步刷新
3.3.1DCL方式
String key = "1";
String businessPrefix = "getModel";
Model model = cache.get(key);
if(model != null){
return model;
}
synchronized((businessPrefix + key).intern()){
model = cache.get(key);
if(model != null){
return model;
}
//从DB获取
}
此方法仅可以在jvm范围内防并发(一般够用了)
建议用concurrentHashMap的原子操作代替String.intern(),以防止perm区溢出
3.2.2 空标记使用实例
3.2.1 标记空对象
3.1 雪崩
原因:大量记录集中失效
3.2 缓存穿透
结果:短时间内DB负载极高
“不存在(DB无数据)”未被缓存
解决思路:交错失效,随机缓存时间
反复查询一个DB不存在的key
3.1.2 防雪崩工具
穿透
DB的性能损耗:锁/事务/树索引
解决思路:短期缓存一个空对象
雪崩
并发
3.1.1 防雪崩实战
(商品缓存)
1,“未上线”或者“已下线”或者“即将下线(距离下线时间不足12小时)”,则返回“12”到“24小时”之间的秒数。
2,如果“在线”且“长期在线(距离下线时间大于29天)”,则返回“29天12小时”到“30天”之间的秒数。
3,“在线”且“在线时间介于‘12小时’和‘29天’之间“的,返回“在线时间+12小时”到“在线时间+24小时”之间的秒数。
2.4 副本
目标:
高性能/高可用/防并发
客户端双写与读切换
代理模式的性能损失
2.3 分片
一致性哈希
client routing
multiGet性能黑洞
(一般3~5片)
2.2 多级缓存
更热的数据
更珍贵的空间(进程内)
更频繁的获取
更接近调用方的模型
代价:
更难控制和监测
2 概念
缓存粒度
2.1 粒度
多级缓存
列表拆分
对list<model>整体的缓存
拆分为list<id>和model缓存
业务拆分
分片
商品基本:列表页
商品扩展:下单/后台编辑
商品详情:富文本
商品销量:频繁变化
复制
1.2 失效方式
被动
失效周期(变更及时性)
根据被缓存模型本身的业务特点
(例如活动结束/商品下线)
主动
刷新时机:增删改
可行性
(列表缓存的数筛选项组合)
刷新频率
(被人疯狂编辑和刷新)
1.3 覆盖程度
部分覆盖
热点数据较明显
全部覆盖
总量小(例如城市列表)
必要时配合主动刷新
高速
有限
不保证可靠性(争议)
按位置划分
按失效方式
按覆盖程度
1.1 按位置划分
堆内缓存
oscache / ehcache /guava cache
堆外缓存(进程内)
big memory / direct memory
本地缓存(进程外)
本host上的redis进程
远程缓存
memcache / redis