Introducing 

Prezi AI.

Your new presentation assistant.

Refine, enhance, and tailor your content, source relevant images, and edit visuals quicker than ever before.

Loading…
Transcript

缓存漫谈

目前item项目的缓存结构

4.1 批量获取模式

一、从缓存批量获取,分为三部分:

1-获取到模型

2-获取到空标记

3-未获取到

二、将上面的第3部分去DB批量获取,

分为两部分:

1-未获取到——需要去缓存空标记

2-获取到——需要写入缓存

三、将第一步的第3部分,

和第二步的第2部分,

合并并返回给业务逻辑

这里有一种变体,是第二部分改用循环单个获取,并加入防并发(不建议使用)

5 扩展

Rich Client 方式获取缓存:

性能更高,但更不可控

缓存层次:

domain => model => html

=> http response =>browser

4 批量操作

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 常见问题

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.1 按位置划分

堆内缓存

oscache / ehcache /guava cache

堆外缓存(进程内)

big memory / direct memory

本地缓存(进程外)

本host上的redis进程

远程缓存

memcache / redis

Learn more about creating dynamic, engaging presentations with Prezi