TMC多级缓存

TMC (Transparent Multilevel Cache) 在通用“分布式缓存解决方案(如 CodisProxy + Redis )”基础上,增加了以下功能:

  • 应用层热点探测
  • 应用层本地缓存
  • 应用层缓存命中统计

以帮助应用层解决缓存使用过程中出现的热点访问问题

image.png

使用有赞服务的电商商家数量和类型很多, 商家会不定期做一些“商品秒杀”、“商品推广”活动, 导致“营销活动”、“商品详情”、“交易下单”等链路应用出现 缓存热点访问 的情况:

  • 活动时间、活动类型、活动商品之类的信息不可预期,导致 缓存热点访问 情况不可提前预知;
  • 缓存热点访问 出现期间,应用层少数热点访问 key 产生大量缓存访问请求:冲击分布式缓存系统,大量 占据内网带宽,最终影响应用层系统稳定性;

为了应对以上问题,需要一个能够 自动发现热点 并将 热点缓存访问请求前置在应用层本地缓存 的解决方案, 这就是 TMC 产生的原因。

TMC 架构

业务定位

image.png

整体流程

image.png

老的上报链路

image.png
旧架构强依赖 etcd 和 kafka

  • etcd write 1w/s, 击穿时 tmc 产生的 变更事件远超这个量级;
  • kafka 链路长,延迟较高且潜在风险较高。

新的上报通道

image.png
image.png
主要功能:热点上报、缓存失效广播、热点下发、服务依赖
主要接接口

  • NotifierService#Report 供 sdk 上报新的热 key
  • NotifierService#ExpireHotKeys 供 sdk 推送 key 过期通知
  • NotifierService#Publish, 供 volcano publish 热 key
syntax="proto3";

package=v1;

service NotifierService {
    rpc ExpireHotKeys (ExpireHotKeysRequest) returns (ExpireHotKeysResponse) {
    }

    rpc Report (ReportRequest) returns (ReportResponse) {
    }

    rpc Publish (PublishRequest) returns (PublishResponse) {
    }
}

message ExpireHotKeysRequest {
  repeated string appNames=1;
  repeated bytes hotKeys=2;
  string groupName=3;
}

message ExpireHotKeysResponse {
  int32 code=1;
  string msg=2;
}

message ReportRequest {
    repeated ReportData reportData = 1;
    string groupName = 2;
}

message ReportData {
    string appName = 1;
    string key = 2;
    int32 accessTimes = 3;
    int32 redisHitTimes = 4;
    int32 localCacheHitTimes = 5;
    int32 localCacheLossTimes = 6;
    int32 version = 7;
    int64 sendTime = 8;
}

message ReportResponse {
    int32 code = 1;
    string msg = 2;
}

message PublishRequest {
    string groupName = 1;
    repeated string appNames = 2;
    repeated HotKeyChangeModel changeModels = 3;
    int64 findAt = 4;
    enum EventType {
        NULL = 0;
        DELETE = 1;
        UPDATE = 2;
        EXPIRE = 3;
        FIND = 4;
    }
    EventType eventType = 5;
}

message HotKeyChangeModel {
    string hotKey = 1;
    int64 expireTime = 2;
}

message PublishResponse {
    int32 code = 1;
    string msg = 2;
}

// code 0: 正确
// code 400: 参数不对
  • 按 group+key 维度聚合
  • 跨注册中心的需求,采用 notifier 互调实现
  • 对于 kvproxy 的 hotkey error,触发立即下发热点 key 的逻辑

notifier 可能会存在内存泄露问题
1、双缓冲队列:通过覆盖写的方式,只能解决突发流量洪峰,丢弃 limit 以外的流量;
2、下游 sweep(Tether 广播或者上报 volcano-server)性能受限时,内存中的 message 会产生积压,造成泄漏;
使用 LRU 缓存来替换抖动的数据映射来解决

热点计算

image.png

  • 数据收集: 收集 Volcano-SDK 上报的 key 访问事件;
  • 热度滑窗: 对 App 的每个 Key ,维护一个时间轮,记录基于当前时刻滑窗的访问热度;
  • 热度汇聚: 对 App 的所有 Key ,以<key, 热度>的形式进行 热度排序汇总;
  • 热点探测: 对 App ,从 热 Key 排序汇总 结果中选出 TopN 的热点 Key ,推送给 volcano-SDK;

image.png

volcano-sdk
对于 redis 的 Jedis 客户端中封装已有的 get、set、expire、delete 操作,做热点的发现统计,以及本地缓存变更通知。
image.png
volcano-server
对于收集的数据,进行滑动窗口计算,汇总,热点推送到各机器上
image.png

关于缓存击穿的处理

如果没有处理好并发问题, 业务方很容易出现, 在缓存数据过期时, 大量相同的请求回源到底层的 DB 读取数据, 导致大量无效回源请求, 增加 DB 的压力, 在大流量的情况下可能会导致 DB 崩溃, 从而导致雪崩。 对于这种情况, 我们在 redis-zan 框架中封装了一个简易接口来避免这种情况。
使用 redis-zan 的业务方, 可以通过封装的接口完成缓存击穿的简化处理, 缓存失效自动 DB 回源更新缓存的使用示例:

public void testStringGet() throws ExecutionException {
   String key = "youzan:test:db_callback";
   Supplier<String> supplier = () -> {
    // 处理读取db的数据, 返回对应db的值
     return ret;
   };

   // 此方法会从kv读取key的值, 如果不存在, 调用supplier回调函数从db中回源, 然后自动更新到kv, 并设置指定的ttl过期时间.
   // 此方法会自动处理缓存失效时多个并发回源问题, 保证只有第一个线程的调用会回源db并更新kv. 后面的线程会排队等待第一个线程返回.

   String value = redisClient.opsForString().get(key, 5, supplier);
 }

注意, 此方式可以单独使用, 不需要开启 TMC 本地缓存功能也可以使用。
类似的, 对于读取业务本身就不存在的数据, 出现的缓存穿透情况, 业务可以设置一个业务认为不存在的标记, 来避免不存在的数据一直请求 DB。

KV 增强本地缓存 LRUs

默认的热点缓存一般只用于少量的热点数据缓存(几百以内的 key), tmc-kv 新功能新增支持本地 LRU 能力, 可以近实时的拦截热点(理论上可以立即缓存热点读请求), 也可以缓存更多的数据到本地(几十万 key), 并且通过淘汰算法自动驱逐非热点数据, 不过由于缓存的 key 更多了, 因此有可能会扩大数据不一致的影响范围(开启更新通知广播可以通过更新广播减少不一致窗口), 适合数据修改不频繁的业务场景, 需要的业务方可以联系开发手动开启本地 LRU 增强功能。

RPC 缓存

最近新增了一项 RPC 缓存功能, 来提供更加快捷的本地缓存使用方式, 可以直接缓存 RPC 的响应结果, 业务方不需要关注缓存细节。 通过 RPC 缓存,业务可以:

  • 提升请求的处理速度,提升业务的吞吐量。从本地缓存中获取响应结果,性能远高于向后端发起 RPC 请求。
  • 降低后端应用负载,节省成本。本地缓存可以有效的降低后端应用处理的 RPC 请求量(RPS,requests per second),进而降低后端应用的负载,节省成本。
  • 流量削峰,提升应用稳定性。缓存命中率在秒杀等瞬间洪水流量场景下会显著提升,从而避免洪水流量瞬间冲击后端服务。

通过开启缓存配置,即可零开发零成本的享受到 RPC 缓存带来的收益。对于不需要针对 kv 具体缓存数据做读取操作的应用,或者需要 kv 之外数据缓存的业务, 可以使用 RPC 缓存更加简单的提升性能。

https://alicharles.oss-cn-hangzhou.aliyuncs.com/static/images/mp_qrcode.jpg
文章目录
  1. TMC 架构
    1. 业务定位
    2. 整体流程
      1. 老的上报链路
      2. 新的上报通道
  2. 热点计算
  3. 关于缓存击穿的处理
  4. KV 增强本地缓存 LRUs
  5. RPC 缓存