当前位置: 首页 > news >正文

高并发系统设计 --热点key问题解决

热点Key问题,这是一个老生常谈的问题了,今天我们来仔细的去剖析这个问题。

热点key带来的问题

  1. 流量集中。达到服务器处理上限(CPU,网络IO等)
  2. 会影响在同一个Redis实例上其他key的读写请求操作
  3. 热key请求落到同一个Redis实例上,无法通过扩容解决
  4. 大量Redis请求失败,查询操作可能打到数据库,拖垮数据库,导致整个服务不可用

如何发现热点key

凭借业务经验,预估热点key的出现

这样缺点很明显,就是不准确,无法完美预估。

客户端进行收集

例如我用小根堆,哈希表对热点key进行统计。但是它的缺点是:对代码有一定的入侵,而且无法适应多语言架构,每一种语言的SDK都需要进行开发,后期开发维护成本很高。

在代理层进行收集

在这里插入图片描述

这里我们大概来将一下这个图:

  • SLB层做负载均衡
  • Proxy层做读写分离自动路由
  • Master负责写请求
  • ReadOnly负责读请求
  • Slave节点和Master节点做高可用
  • 实际过程中Client将请求传到SLB,SLB又将其分发至多个Proxy内,通过Proxy对请求的识别,将其进行分类发送

这样做的优点是:

  • 对使用方完全透明,能够解决客户端SDK的语言异构和版本升级问题
  • 只需要对proxy进行横向拓展,就可以任意增强热点数据的访问能力

缺点也很明显:

  • 并不是所有Redis集群架构中都有Proxy代理(使用这种方式必须要部署Proxy)
  • 开发成本太高了

使用Redis自带的命令

Redis4.0.3 版本中添加了 hotkeys 查找特性,可以直接利用 redis-cli --hotkeys 获取当前 keyspace 的热点 key,实现上是通过 scan + object freq 完成的。

  • 优点:无需进行二次开发,能够直接利用现成的工具;
  • 缺点:
    • 由于需要扫描整个 keyspace,实时性上比较差;
    • 扫描时间与 key 的数量正相关,如果 key 的数量比较多,耗时可能会非常长。

Redis节点抓包分析

Redis 客户端使用 TCP 协议与服务端进行交互,通信协议采用的是 RESP 协议。自己写程序监听端口,按照 RESP 协议规则解析数据,进行分析。或者我们可以使用一些抓包工具,比如 tcpdump 工具,抓取一段时间内的流量进行解析。

  • 优点:对 SDK 或者 Proxy 代理层没有入侵;
  • 缺点:
    • 有一定的开发成本;
    • Key 节点的网络流量和系统负载已经比较高了,抓包可能会导致情况进一步恶化。

如何解决热点Key问题

使用二级缓存(本地缓存)

我们使用哈希表做二级缓存就可以,其实不需要那么复杂。

使用本地缓存需要注意两个问题:

  1. 如果对热 Key 进行本地缓存,需要防止本地缓存过大,影响系统性能;
  2. 需要处理本地缓存和 Redis 集群数据的一致性问题。

热key备份

RedisKey 问题首先是请求流量过大造成的,但是更深层次原因还是出现了流量倾斜,单个 Redis 实例承担的流量过大造成的,了解到了本质原因,解决的思路也就简单了,就是要想尽一切办法将单个实例承担的流量打散,让每个机器均衡承担热 Key 的流量,不要出现流量倾斜,保证系统的稳定性。

因此,我们需要进行热key备份

通过前面的分析,我们可以了解到,之所以出现热 Key,是因为有大量的对同一个 Key 的请求落到同一个 Redis 实例上,如果我们可以有办法将这些请求打散到不同的实例上,防止出现流量倾斜的情况,那么热 Key 问题也就不存在了。

那么如何将对某个热 Key 的请求打散到不同实例上呢?我们就可以通过热 Key 备份的方式,基本的思路就是,我们可以给热 Key 加上前缀或者后缀,把一个热 Key 的数量变成 Redis 实例个数 N 的倍数 M,从而由访问一个 Redis Key 变成访问 N * MRedis KeyN * MRedis Key 经过分片分布到不同的实例上,将访问量均摊到所有实例。

我们来看一下热key备份的伪代码:

// N为Redis实例个数,M为N的2倍
const M = N * 2;
// 生成随机数
random = GetRandom(0, M);
// 构造备份新的key
bakHotKey = hotKey + "_" + random;
data = redis.GET(bakHotKey);
if data == NULL {
    data = redis.GET(hotKey);
    // 可以利用原子锁写入数据保证数据的一致性
    redis.SET(hotKey, data, expireTime);
    redis.SET(bakHotKey, data, expireTime + GetRandom(0, 5));
} else {
    redis.SET(bakHotKey, data, expireTime + GetRandom(0, 5));
}

在这段代码中,通过一个大于等于 1 小于 M 的随机数,得到一个 bakHotKey,程序会优先访问 bakHotKey,在得不到数据的情况下,再访问原来的 hotkey,并将 hotkey 的内容写回 bakHotKey。值得注意的是,bakHotKey 的过期时间是 hotkey 的过期时间加上一个较小的随机正整数,这是通过坡度过期的方式,保证在 hotkey 过期时,所有 bakHotKey 不会同时过期而造成缓存雪崩。

相关文章:

  • 凡科做的网站百度收不收录/百度广告联盟怎么赚钱
  • 平面设计网站推荐/免费建站网站网页
  • 电子科技大学网站开发制定合同/成都sem优化
  • 网站分类代码/品牌营销服务
  • 北京大兴网站建设公司哪家好/运营推广是做什么的
  • 空调设备公司网站建设/无锡网站seo
  • 基于乾坤的微前端+SpringBoot2.7整套解决方案的基础通用平台及组件
  • PointNet.pytorch点云三维深度学习论文与代码复现
  • kafka心得记录
  • 1.3 PCIe——硬件实现架构
  • 【贪心】AcWing 906. 区间分组
  • vite+vue3环境变量的配置
  • 7、操作DOM对象(重点)
  • 26.Isaac教程--导航算法
  • DW 2023年1月Free Excel 第四次打卡 逻辑判断函数
  • 2022秋招算法岗面经题:训练模型时loss除以10和学习率除以10真的等价吗(SGD等价,Adam不等价)
  • ramparser
  • Exceeded limit on max bytes to buffer : 262144