本地缓存
方法1: 堆上缓存
在进程中,使用堆内/堆外缓存。
优点:
- 实现简单
- 读取速度高
缺点:
- 进程挂掉,缓存丢失
- 进程重启,大流量可能冲垮应用
- 堆上缓存可能造成语言 GC 效率降低
- 无法持久化,无法分布式共享
方法2: local redis cache
使用本地 redis 代替堆上缓存。本地代表着没有网络 IO 消耗,读取速度高。并且解决了第一部分缺点的前 3 部分。
缺点如下:
- 无法持久化,无法分布式共享
- 单机数存储数据量小
多级缓存
为什么需要多级缓存?
在云开发网关中,缓存层的设计至关重要。
最初,缓存是放在堆上的本机缓存。基本策略是 LRU,过期支持异步续期。
假设集群中有 60 个 pod,采取普通轮询的方式分配流量,缓存有效期是 60s。用户每分钟请求少于 60 个,就会落到不同的 pod 上,不会命中缓存。对于用户,每次请求都很慢。随着集群上 workload 中 pod 的增多,缓存命中率只会越来越低,平均耗时越来越高。
除此之外,重启 pod 会造成缓存丢失,当然这个不如命中率低影响大。
多级缓存逻辑设计
缓存层是一个逻辑上的概念,由多个部分的缓存共同组成:
- 进程(堆上)缓存
- 分布式 redis 缓存中心
在读取缓存的时候,对于没有命中缓存的情况,会自动降级读取。具体如下:
- 出于速度和一致性的考虑,首先读取进程缓存
- 进程缓存没命中,则读取 redis 缓存中心的缓存数据
- redis 缓存中心不命中,则异步请求数据,并将异步刷新缓存
相对地,缓存刷新策略如下:
- 异步获取的数据先存储到进程缓存
- 再存到 redis 缓存中心
其他还有一些小细节,比如 redis js 库的超时设置有时会“抽风”,更稳健的做法是在代码中使用setTimeout
,超时则 Promise.reject,主动结束请求;比如可以加入默认值的设计等等。
常见缓存策略
- 基于空间:缓存占用的存储空间
- 基于容量:缓存总数
- 基于时间:过期时间、空闲期(多久没访问后,移除)
- 回收算法:
- FIFO:先进先出
- LRU:最近最少使用算法(最常用)
- LFU:最不常用算法,一定时间内使用次数(频率)最少的那个被移除
常见多级存实践模式
- Cache Aside:代码里面直接维护缓存的逻辑,比如缓存不命中,再调用后台接口请求缓存,再调用接口刷新缓存结果到本地和 redis。
- Cache as SoR:抽象一层缓存层,开发者只需要通过缓存层接口读取/写数据即可,缓存层内部封装了默认值判断、缓存过期判断、异步请求等逻辑
- Read Through:业务调用 cache 层,cache 层不命中,内部自动回源 Source
- Write Through:业务调用 cache 层修改数据,cache 层内部去更新缓存和 Source
- Write Aside:Write Through 的异步模式,降低耗时,快速返回结果
注:只是一些术语,本质上就是是否抽出一个缓存层,用来代理开发者操作,降低开发和维护成本。