#2 关于缓存

2019-02-12

什么地方需要缓存?

  • DB 查询,加上缓存可以减小数据库压力,同时提升性能。
  • 某些费时费资源的操作,加上缓存可以避免重复计算。

缓存方式

  1. 本地缓存
  2. 进程内缓存(内存)
  3. 磁盘缓存
  4. 缓存服务(redis/memcache)
  5. 数据库:有些任务可以提前进行计算,将结果存在数据库中。

本地缓存的问题是多个节点之间容易出现数据不一致的情况。我听说过 Java 的一些本地缓存组件,应该其中有一些可以做到多个节点之间的数据同步。如果要是自己实现的话,可以在服务中增加一个刷新缓存的接口调用,其中一个节点刷新缓存时,调用其他节点的刷新接口。也可以引入 MQ,避免这个调用造成的耦合和可能的性能损耗。

缓存策略

  1. 提前进行一些计算,将内容缓存起来。如有必要,可以选择合适的时间间隔进行数据刷新。
  2. DB 缓存可以由数据库中间层来做,也可以有客户端库来做,或者就在应用的数据库层中实现。
    查询时,先尝试本地缓存,再尝试缓存服务(两道缓存,避免击穿),最后再进行数据库查询。
    缓存的数据应该是这样的:
  3. 高命中(缓存命中率需要做好监控)
  4. 较少变更
  5. 尽可能保证数据变更之后(不一致问题)不会产生严重影响
    如果一致性要求很高的话,要反复确实是否必须使用缓存,如果确定的话,缓存刷新策略需要考虑清楚。

穿透

数据库没有数据,缓存也没有数据。这样的请求直接穿过缓存读数据库,给数据库照成压力。

  1. 一般是非法请求所致,对于这一部分请求应该有机制可以进行过滤掉
    恶意请求,或者高频请求
  2. 可以对没有数据的请求也进行缓存,可以给一个相对小一点的 TTL
  3. 布隆过滤器 参考:2021/03/07, 布隆过滤器
  4. 将空 Key 单独存到一个 Redis SET 中应该也可以
    比如 system:cache:empty_keys:hhmm, TTL = 123,每次用 SISMEMBER 查询当前分钟和上一分钟

击穿

某个热 Key 失效,导致大量请求打到数据库。
热 Key 应该预热,然后有一个比较大的 TTL,甚至没有过期时间。最后,通过定时刷新任务来更新这些热 Key。

雪崩

大量 Key 同时过期, 导致请求直接打到数据库,然后影响整个系统。
大面积的击穿。

  1. 比如系统刚启动的时候,批量写入大量数据,这些数据有相同的 TTL,就会同时过期。我们应该给过期时间加入一些随机,将过期时间点分散在一个区间内。
  2. 对于热 Key 的处理, 同击穿部分。

其他:

  1. 为了防止 Redis 奔溃,导致系统崩溃,应该在本地进程中也设置一个缓存。
    LocalCache -> RedisCache -> DB
  2. 为了防止数据库奔溃,数据库请求应该由一个队列来处理。
  3. 网关部分对于大量来不及处理的请求应该丢弃。
  4. 缓存应该能够按重要性划分一下级别,如果遇到问题能够快速丢弃不重要的数据
    还应该可以快速丢弃指定服务的所有缓存。
    这应该叫做缓存降级。

预热

根据之前的经验,或者开发者预判,将部分数据事先写入缓存。
如果提供相关工具,让系统维护人员能够方便快捷地管理缓存数据,能够手动介入缓存的生命周期就更好了。至少在后台提供一个 缓存预热 的按钮。

  1. 提供指定热 Key,让定时任务负责刷新。
    智能预热:给访问量大的 Key 延长 TTL, 启动定时刷新。
  2. 需要对缓存的访问有一个简单的监控,方便作为之后预热的依据。

缓存更新

另起一篇:缓存更新策略

#1 常见 Web 缓存服务

2019-01-06

Apache,Nginx 都有缓存功能,再加上 Redis 做动态数据的缓存,再加上 CDN,所以我还没有用过专门的缓存服务。
但是这些服务真是大名鼎鼎,即便不用,我也可以先列出来做个简单的了解。