Redis 是一个开源的高性能的 Key-Value 服务器。本篇主要介绍一下缓存的设计与优化。
缓存收益与成本
- | 说明 |
---|---|
缓存的受益 | 1、加速读写,通过缓存加速读写速度,例如 CPU L1/L2/L3 Cache、Linux page Cache 加速硬盘读写、浏览器缓存、Ehcache 缓存数据库结果; 2、降低后端负载,后端服务器通过前端缓存降低负载,业务端使用 Redis 降低后端 MySQL 负载等。 |
缓存的成本 | 1、数据不一致,缓存和数据层有时间窗口不一致,和更新策略有关; 2、代码维护成本增加,多了一层缓存逻辑; 3、运维成本增加。 |
单线程架构
单线程架构要注意什么?
- 一次只运行一条命令;单条命令都具有原子性
- 拒绝长(慢)命令,例如 keys、flushall、flushdb、slow lua scrip、mutil/exec、operate big value(collection);
缓存穿透优化
缓存穿透问题,大量请求不命中?
发生缓存穿透的常见原因:
- 业务代码自身问题;
- 恶意攻击、爬虫等等。
如何发现问题?
- 业务的响应时间;
- 业务本身问题;
- 相关监控指标:总调用数、缓存层命中数、存储层命中数;
解决方案:
方案一:缓存空对象。示例代码:
public String getPassThrough(String key) {
String cacheValue = cache.get(key);
if (StringUtils.isBlank(cacheValue)) {
String storageValue = storage.get(key);
cache.set(key, storageValue);
// 如果存储数据为空, 需要设置过期时间
if (StringUtils.isBlank(storageValue)) {
cache.expire(key, 300); // 300秒
}
return storageValue;
} else {
return cacheValue;
}
}
方案二:布隆过滤器拦截。通过很小的内存来实现对数据的过滤。
缓存雪崩优化
缓存雪崩:由于 cache 服务承载大量请求,当 cache 服务异常/脱机后,流量直接压向后端组件(例如 DB),造成级联故障。
缓存雪崩优化方案:
- 保证缓存高可用性,例如 Redis Cluster、Redis Sentinel、VIP;
- 依赖隔离组件为后端限流;
- 提前演练,例如压力测试。
热点key优化
优化方案:
- 避免 bigkey;
- 热键不要用 hash_tag,因为 hash_tag 会落到一个节点上;
- 如果真有热点 key 而且业务对一致性要求不高时,可以用本地缓存 + MQ 解决。
热点key重建优化
问题:热点 key + 较长的重建时间。
扫描二维码关注公众号,回复:
11548558 查看本文章
获取缓存 -> 查询数据源 -> 重建缓存 -> 输出,这个步骤在高并发的情况下,由于查询数据源需要时间,所以会有很多请求会进入到 查询数据源 -> 重建缓存 这个过程。对数据源会造成很大压力,响应时间也会变慢。
优化目标:
- 减少重建缓存的次数;
- 数据尽可能一致;
两个优化方案:
- 互斥锁(mutex key),查询数据源 -> 重建缓存 这个过程加互斥锁;
- 永不过期,缓存层面不设置过期时间(没有用 expire),功能层面为每个 value 添加逻辑过期时间,但发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
两个优化方案的对比:
策略 | 优点 | 缺点 |
---|---|---|
互斥锁 | 思路简单,保证一致性 | 代码复杂度增加,存在死锁的风险 |
永不过期 | 基本杜绝热点 key 重建问题 | 不保证一致性,逻辑过期时间增加维护成本和内存成本 |