有哪些缓存?
- CPU 多级缓存
- 浏览器缓存
- 数据库缓存
- CDN
- 业务数据缓存
- 等等
按照进程内外科分为:
- 进程内缓存(如 guava 库提供的 Java 缓存类)
- 进程外缓存(如 redis 缓存,是位于业务代码运行进程实例之外的)
缓存的目的
加速。
业务缓存的选型
- redis
- memcache
- ehcache
- guava
- ...
容量评估
- 业务高峰期,内存使用占比会达到多少?
- 当缓存可用区间为0时,会发生什么?
- 缓存占用空间达到一定阈值时,如何告警?
静态缓存和动态缓存
静态缓存,是指内容不会变的缓存。例如一段文字的MD5是不会变的。
动态缓存,缓存的是动态数据,需要经常更新的。例如某个网站的文章数量。
强一致性与最终一致性
数据会持久化到 DB 中(如 MySQL),引入缓存后,DB 和缓存是两套系统,无法做到【单机事务】的效果。
两个选择:
- 强一致性:引入 2PC、paxos 等方案保证一致性。缺点是复杂性提高,性能变差。
- 最终一致性:允许缓存中数据和 DB 中数据一段时间内的不一致。
如何更新缓存:以用 redis 存储业务缓存为例
对于动态缓存,一定要考虑下面几件事:
- 过期时间是多长比较合适?
- 如果有并发,会不会导致缓存内有旧数据,而无新数据?
- 引入缓存,是否会侵入现有的业务代码?对主流程的性能等影响能接受吗?
通过 Trade-Off(权衡) ,我们选择合适自己的方案即可。
常见的套路:
套路1
- 查询:查无缓存,则去DB查,并更新缓存,设置过期时间
- 更新DB:更新DB 后,清理对应的缓存(为了减少并发导致的缓存脏数据,可以在几秒后再清理一次对应缓存)
套路2
- 查询:查无缓存,则去DB查,并更新缓存,设置过期时间
- 更新DB:监听binlog,异步清理对应缓存
套路3
DB 数据增加版本号字段,缓存中也缓存版本号,更新缓存时使用乐观锁/cas。
下面的文章值得参考:
缓存穿透
加上缓存后,某些请求因为在DB中没有数据,所以在缓存中找不到数据,这就导致请求一致会走到DB层。这就是缓存穿透。
例如对用户信息进行缓存,key 是用户id,value 是用户具体信息。如果请求某一个不存在的用户ID,会一致走到DB。
影响
可能导致DB压力未有效缓解。
解决方案1: 尽早拦截非法请求
- 接入风控,尽早拦截非法请求。
解决方案2: 缓存空值
用户ID 虽然不存在,依然缓存,缓存值是空。
解决方案3: DB请求串行化
利用互斥锁(例如 redis的锁),串行化去DB中查询某一用户ID的信息。注意,这里的锁粒度是用户ID。
没拿到锁的数据怎么办?
- 直接报错
系统繁忙
。 - 间隔一段时间,获取缓存内容,无则重试加,重试 n 次后无缓存或者拿不到锁,则报错。
缓存击穿
热点 key 突然过期失效,导致大量请求走到 DB 层.
影响
DB压力陡增陡降.
解决方案1: DB请求串行化
见上面的讨论。
缓存雪崩
同一时间大量缓存时效,导致大量请求走到DB,DB 性能急剧下降。
解决方案1:限流
对相关接口限流。
解决方案2:失效时间随机化
失效时间不要做成固定值,而是用一定范围内的随机值。
(待完善)