缓存


#软件架构与思考


有哪些缓存?

  • CPU 多级缓存
  • 浏览器缓存
  • 数据库缓存
  • CDN
  • 业务数据缓存
  • 等等

按照进程内外科分为:

  • 进程内缓存(如 guava 库提供的 Java 缓存类)
  • 进程外缓存(如 redis 缓存,是位于业务代码运行进程实例之外的)

缓存的目的

加速。

业务缓存的选型

  • redis
  • memcache
  • ehcache
  • guava
  • ...

容量评估

  1. 业务高峰期,内存使用占比会达到多少?
  2. 当缓存可用区间为0时,会发生什么?
  3. 缓存占用空间达到一定阈值时,如何告警?

静态缓存和动态缓存

静态缓存,是指内容不会变的缓存。例如一段文字的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。

没拿到锁的数据怎么办?

  1. 直接报错系统繁忙
  2. 间隔一段时间,获取缓存内容,无则重试加,重试 n 次后无缓存或者拿不到锁,则报错。

缓存击穿

热点 key 突然过期失效,导致大量请求走到 DB 层.

影响

DB压力陡增陡降.

解决方案1: DB请求串行化

见上面的讨论。

缓存雪崩

同一时间大量缓存时效,导致大量请求走到DB,DB 性能急剧下降。

解决方案1:限流

对相关接口限流。

解决方案2:失效时间随机化

失效时间不要做成固定值,而是用一定范围内的随机值。

(待完善)



( 本文完 )