什么地方需要缓存?
- DB 查询,加上缓存可以减小数据库压力,同时提升性能。
- 某些费时费资源的操作,加上缓存可以避免重复计算。
缓存方式
- 本地缓存
- 进程内缓存(内存)
- 磁盘缓存
- 缓存服务(redis/memcache)
- 数据库:有些任务可以提前进行计算,将结果存在数据库中。
本地缓存的问题是多个节点之间容易出现数据不一致的情况。我听说过 Java 的一些本地缓存组件,应该其中有一些可以做到多个节点之间的数据同步。如果要是自己实现的话,可以在服务中增加一个刷新缓存的接口调用,其中一个节点刷新缓存时,调用其他节点的刷新接口。也可以引入 MQ,避免这个调用造成的耦合和可能的性能损耗。
缓存策略
- 提前进行一些计算,将内容缓存起来。如有必要,可以选择合适的时间间隔进行数据刷新。
- DB 缓存可以由数据库中间层来做,也可以有客户端库来做,或者就在应用的数据库层中实现。
查询时,先尝试本地缓存,再尝试缓存服务(两道缓存,避免击穿),最后再进行数据库查询。
缓存的数据应该是这样的:- 高命中(缓存命中率需要做好监控)
- 较少变更
- 尽可能保证数据变更之后(不一致问题)不会产生严重影响
如果一致性要求很高的话,要反复确实是否必须使用缓存,如果确定的话,缓存刷新策略需要考虑清楚。
穿透
数据库没有数据,缓存也没有数据。这样的请求直接穿过缓存读数据库,给数据库照成压力。
- 一般是非法请求所致,对于这一部分请求应该有机制可以进行过滤掉
恶意请求,或者高频请求 - 可以对没有数据的请求也进行缓存,可以给一个相对小一点的 TTL
- 布隆过滤器 参考:2021/03/07, 布隆过滤器
- 将空 Key 单独存到一个 Redis SET 中应该也可以
比如system:cache:empty_keys:hhmm
, TTL = 123,每次用SISMEMBER
查询当前分钟和上一分钟
击穿
某个热 Key 失效,导致大量请求打到数据库。
热 Key 应该预热,然后有一个比较大的 TTL,甚至没有过期时间。最后,通过定时刷新任务来更新这些热 Key。
雪崩
大量 Key 同时过期, 导致请求直接打到数据库,然后影响整个系统。
大面积的击穿。
- 比如系统刚启动的时候,批量写入大量数据,这些数据有相同的 TTL,就会同时过期。我们应该给过期时间加入一些随机,将过期时间点分散在一个区间内。
- 对于热 Key 的处理, 同击穿部分。
其他:
- 为了防止 Redis 奔溃,导致系统崩溃,应该在本地进程中也设置一个缓存。
LocalCache -> RedisCache -> DB - 为了防止数据库奔溃,数据库请求应该由一个队列来处理。
- 网关部分对于大量来不及处理的请求应该丢弃。
- 缓存应该能够按重要性划分一下级别,如果遇到问题能够快速丢弃不重要的数据
还应该可以快速丢弃指定服务的所有缓存。
这应该叫做缓存降级。
预热
根据之前的经验,或者开发者预判,将部分数据事先写入缓存。
如果提供相关工具,让系统维护人员能够方便快捷地管理缓存数据,能够手动介入缓存的生命周期就更好了。至少在后台提供一个 缓存预热
的按钮。
- 提供指定热 Key,让定时任务负责刷新。
智能预热:给访问量大的 Key 延长 TTL, 启动定时刷新。 - 需要对缓存的访问有一个简单的监控,方便作为之后预热的依据。
缓存更新
另起一篇:缓存更新策略