目录
一 缓存的收益和成本
1.1 收益
- 加速读写:缓存都是全内存(Redis、Memcache),而存储层通常读写性能不够强悍(Mysql),通过缓存的使用可以有效地加速读写,优化用户体验。
- 降低后端负载:帮助后端减少访问量和复杂计算(很复杂的 SQL 语句),在很大程度降低了后端的负载。
1.2 成本
- 数据不一致:缓存层和存储层的数据存在一定时间窗口的不一致性,时间窗口跟更新策略有关。
- 代码维护成本:加入缓存后,需要同时处理缓存层和存储层的逻辑,增加了开发者维护代码的成本。
- 运维成本:Redis Cluester 为例,增加了运维成本。
1.3 缓存使用的场景
- 开销大的复杂计算:以 MySQL 为例,一些复杂的操作或者计算(大量表关联、一些分组计算),如果不加缓存,无法满足高并发量,同时也会给 MySQL 带来巨大的负担。
- 加速请求响应:Redis 每秒可以完成数万次读写,并且提供的批量操作可以优化整个 IO 链的响应时间。
二 缓存更新策略
缓存中的数据都是有生命周期的,需要在指定时间后被删除或者更新,这样保证缓存空间可控。但是缓存中的数据和数据源中的真实数据有一定时间窗口的不一致。需要利用某些策略进行更新。
2.1 内存淘汰
LRU/LFU/FIFO 算法剔除是内存淘汰的一些方式。
- 使用场景
通常用于缓存使用量超过了预设的最大值,Redis 使用 maxmemory-policy 这个配置作为内存最大值后对于数据剔除策略。
- 一致性
要剔除那些数据是由具体的算法决定的,开发人员只能决定使用哪种算法,所以数据的一致性是最差的。
- 维护成本
算法不需要开发人员自己来实现,通常只需要配置最大 maxmemory 和对应的策略就行。开发人员只需要知道每种算法的含义,选择适合自己的算法即可。
2.2 超时剔除
超时剔除策略是:定期删除 + 惰性删除。
定期删除,指的是 Redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就有了惰性删除了。
惰性删除,在你获取某个 key 的时候,Redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。
但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 Redis 内存块耗尽了,咋整?
走内存淘汰机制。
- 使用场景
超时剔除通过给缓存数据设置过期时间,让其在过期时间后自动删除。
- 一致性
一段时间窗口内(取决于过期时间长短)存在一致性问题,即缓存数据和真实数据源的数据不一致。
- 维护成本
只需要设置 expire 过期时间,当然前提是应用方允许这段时间可能发生的数据不一致。
2.3 主动更新
- 使用场景
应用方对于数据的一致性要求很高,需要在数据更新后,立即更新缓存数据。可以利用消息系统或者其他方式通知更新缓存。
- 一致性
一致性最高,建议和超时剔除一起使用。
- 维护成本:维护成本比较高,开发者需要自己来完成更新,并保证更新操作的正确性。
2.4 最佳实践
- 低一致性业务建议配置最大内存和淘汰策略的方式使用。
- 高一致性业务使用超时剔除和主动更新。
三 缓存粒度控制
缓存粒度说的是缓存全部属性还是部分属性。
- 通用性
缓存全部数据比部分数据更加通用,但从实际经验看,很长时间内应用只需要几个重要的属性。
- 空间占用
缓存全部数据比部分数据占用更多的空间,存在以下问题:
- 全部数据会造成内存的浪费。
- 全部数据可能每次传输产生的网络流量会比较大,耗时相对较大,极端情况会阻塞网络。
- 全部数据的序列化和反序列化的CPU开销更大。
- 代码维护
全部数据的优势更加明显,而部分数据一旦要加新字段需要修改业务代码,而且修改后通常还需要刷新缓存数据。