阿里面试:如何解决Redis热点Key问题? (按此作答,拿 60W年薪)
文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :
免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,完成职业升级, 薪酬猛涨!加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取
阿里面试:如何解决Redis热点Key问题? (按此作答,拿 60W年薪)
尼恩特别说明: 尼恩的文章,都会在 《技术自由圈》 公号 发布, 并且维护最新版本。 如果发现图片 不可见, 请去 《技术自由圈》 公号 查找
尼恩说在前面:
在40岁老架构师 尼恩的100+读者交流群 中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:
- 什么是热点key? 热点key会带来哪些问题?
- 如何监测热点key?如何解决热点key?
- 你们生产遇到过 hotkey吗? 怎么解决的?
最近有小伙伴在面试 阿里P7,又遇到了相关的面试题。小伙伴是按照尼恩的方案去答的。
没想到,面试官非常满意,小伙伴阿里P7 offer到手,年薪 60W,还特意来感谢了尼恩。
所以,尼恩给大家做一下系统化、体系化的梳理。
这里,把这个答案共享出来,帮大家内力猛增,让面试官爱到 “不能自已、口水直流”。
然后,帮大家 实现”offer直提”,都拿到阿里P7 offer,逆天改命。
当然,这道面试题 以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V171版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,回复:领电子书
1 宏观总结:热点key的危害和解决方案
什么是HotKey?
在某个Key接收到的访问次数、显著高于其它Key时,我们可以将其称之为HotKey/热key。
从访问量上来说,常见的HotKey如:
- 某Redis实例的每秒总访问量为10000,而其中一个Key的每秒访问量达到了7000(访问次数显著高于其它Key)
- 对一个拥有上千个成员且总大小为1MB的HASH Key每秒发送大量的HGETALL(带宽占用显著高于其它Key)
- 对一个拥有数万个成员的ZSET Key每秒发送大量的ZRANGE(CPU时间占用显著高于其它Key)
从业务上来说, 常见的HotKey如:
1 、MySQL等数据库会被频繁访问的热数据
如爆款商品的skuId。
2 、redis的被密集访问的key
如爆款商品的各维度信息,skuId、shopId等。
3 、机器人、爬虫、刷子用户
如用户的userId、uuid、ip等。
4 、某个接口地址
如/sku/query或者更精细维度的。
注意,我们的HotKey探测框架只关心key,其实就是一个字符串,
2 热点Key的巨大危害
2.1 HotKey对服务层和数据层的风险
在拥有大量并发用户的系统中,HotKey一直以来都是一个不可避免的问题。
- 比如秒杀活动、热点微博、热评,某件商品被数万次点击浏览或购买时,就会造成热点问题
- 比如大量发布、浏览的热点新闻、热点评论等读多写少场景也会产生热点问题
- 比如是 瞬间大量开启的爬虫用户,
- 突发大批机器人以远超正常用户的速度发起极其密集的请求,这些机器人只需要很小的代价,就能发出百倍于普通用户的请求量,从而大幅挤占正常用户的资源。
以京东为例的这些头部互联网公司,动辄某个爆品,会瞬间引入每秒上百万甚至数百万的请求,当然流量多数会在几秒内就消失。
但就是这短短的几秒的HotKey,就会瞬间造成其所在redis分片集群瘫痪。
原因也很简单,redis作为一个单线程的结构,所有的请求到来后都会去排队,当请求量远大于自身处理能力时,后面的请求会陷入等待、超时。
由于该redis分片完全被这个key的请求给打满,导致该分片上所有其他数据操作都无法继续提供服务,也就是HotKey不仅仅影响自己,还会影响和它合租的数据。
这样,redis 缓存没有响应之后,相当于 redis 击穿, 请求直接转向DB
DB的吞吐量,比如会低很多,DB 就会雪崩。
2.2 HotKey带来的常见问题
- HotKey占用大量的Redis CPU时间,使其性能变差并影响其它请求;
- Redis Cluster中各node流量不均衡造成Redis Cluster的分布式优势无法被Client利用,一个分片负载很高而其它分片十分空闲从而产生读/写热点问题;
- 在抢购、秒杀活动中,由于商品对应库存Key的请求量过大,超出Redis处理能力造成超卖;
- HotKey的请求压力数量超出Redis的承受能力造成缓存击穿,此时大量强求将直接指向后端存储,将后端存储打挂并影响到其它业务;
- 流量过于集中,突破物理网卡的极限
- 请求过多,缓存分片服务被打垮
- 穿透DB
2.3 热点Key的巨大危害
当某热点Key请求在某一主机上超过该主机网卡上限时,由于流量过度集中,导致服务器中其它服务无法正常进行
=》
缓存服务崩溃:热点过于集中,热点Key缓存过多,超过目前的缓存容量,就会导致缓存分片服务被打垮
=》
DB雪崩: 此时再有请求产生,会缓存到后台DB,导致缓存穿透,进一步还会导致DB雪崩。
=》
于是 系统 雪崩。
3 HotKey的解决方案
通常的解决方案主要集中在对客户端和Server端进行改造。
3.1 本地缓存方案: 实现就近访问
在应用层引入本地缓存(如Guava Cache、Caffeine、nginx share dict等),将热点数据缓存在本地内存中,减少对Redis的直接访问,从而降低Redis的压力。
优点:减少了对Redis的读请求,降低了热点Key带来的负载压力。
3.2 打撒热点:将热点Key分散到不同的服务器
通过改变Key的结构(如添加随机前缀),将同一个热点Key拆分成多个Key,使其分布在不同的Redis节点上,从而避免所有流量集中在一个节点上。
优点:有效避免了单点瓶颈,提高了Redis集群的整体吞吐量。
3.3 Redis 从节点扩容
如果是用 : redis 主从架构,可以通过增加Redis集群中的从节点,增加 多个读的副本。
通过对读流量进行 负载均衡, 将读流量 分散到更多的从节点 上,减轻单个节点的压力。
优点:通过水平扩展,Redis可以处理更大的负载,特别是针对高并发的读请求。
Redis 从节点扩容 方案非常受限: 如果是用 redis cluster 集群架构,这种方案就没辙了。
关于 redis 主从架构,redis cluster 集群架构 的区别,请看尼恩的下面深度文章:
希音面试:Redis脑裂,如何预防?你能解决吗?(看这篇就够了)
3.4 热点探测
热点探测是 可以帮助我们识别和处理那些突然变得非常热门的键(HotKey),这些键可能会对系统的性能和稳定性造成影响。
京东开源的JD-hotkey框架可以毫秒级探测热点数据,并将这些数据推送至服务器集群内存中,从而降低热数据对数据层的查询压力。
该框架适用于多种场景,如MySQL热数据本地缓存、Redis热数据本地缓存、黑名单用户本地缓存、爬虫用户限流等
3.5 本地缓存的预加载
本地缓存的预加载是一种优化策略,提前加载可能会用到的热点数据,以便在需要时能够快速访问。
4 HotKey的探测方案
监测 热点Key可以通过以下几种方法:
4.1 代理端探测:
如果系统中使用了Redis代理(如Twemproxy、Codis 等),可以在代理层添加统计功能,
对经过代理的Redis请求进行统计,记录每个Key的访问频率。
4.2 服务端探测:
可以实时监控Redis服务器执行一些相关命令(如MONITOR、redis-cli --hotkeys等),通过分析这些命令,可以观察到哪些Key被频繁访问,识别出热点Key。
4.3 客户端探测:
在Redis客户端(如Jedis、Lettuce)中添加统计代码,对每次对Redis的访问进行记录。可以统计每个Key的访问次数,并定期上报到监控系统。
4.4 凭经验判断:
根据经验判断哪些数据是访问频率最高的。
例如,电商系统中的商品详情页、社交平台上的热门帖子等数据通常容易成为热点Key。
5 服务端探测 HotKey:
可以实时监控Redis服务器执行一些相关命令(如MONITOR、redis-cli --hotkeys等),通过分析这些命令,可以观察到哪些Key被频繁访问,识别出热点Key。
第一大命令 MONITOR命令:
可以实时监控Redis服务器执行的所有命令。通过分析这些命令,可以观察到哪些Key被频繁访问,识别出热点Key。
MONITOR命令还可以 结合 第三方监控工具(如RedisInsight)可以实时监控Redis实例的性能数据,并通过设置监控指标和警报,自动检测出访问频率异常高的Key。
第二大命令 redis-cli --hotkeys
命令:
在Redis 4.0及以上版本中,redis-cli
提供了--hotkeys
选项,可以帮助分析哪些Key是热点Key。
5.1 通过 MONITOR命令在服务端探测 HotKey:
MONITOR命令 可以实时监控Redis服务器执行的所有命令。
通过分析这些命令,可以观察到哪些Key被频繁访问,识别出热点Key。
不过,MONITOR
命令的开销较大,所以,MONITOR命令 一般只在调试阶段使用。
要使用MONITOR
命令监控hotkey,首先需要了解MONITOR
命令的基本用途和使用方法。
5.1.1 MONITOR命令语法
到底,什么是MONITOR命令 ?
redis MONITOR命令 基本语法如下:
redis 127.0.0.1:6379> MONITOR
Redis MONITOR命令用于实时打印出 Redis 服务器接收到的命令 。MONITOR 用来帮助我们知道数库正在做什么。 可以通过 redis-cli
和 telnet
调用MONITOR 。
当 Redis 用做数据库或者分布式缓存时,MONITOR 可以帮助我们发现程序中的 bug 。
$ redis-cli monitor
1339518083.107412 [0 127.0.0.1:60866] "keys" "*"
1339518087.877697 [0 127.0.0.1:60866] "dbsize"
1339518090.420270 [0 127.0.0.1:60866] "set" "x" "6"
1339518096.506257 [0 127.0.0.1:60866] "get" "x"
1339518099.363765 [0 127.0.0.1:60866] "del" "x"
1339518100.544926 [0 127.0.0.1:60866] "get" "x"
通过 redis-cli
运行 MONITOR 时,可以发送 SIGINT
(Ctrl+C) 信号来停止退出。
$ telnet localhost 6379
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
MONITOR
+OK
+1339518083.107412 [0 127.0.0.1:60866] "keys" "*"
+1339518087.877697 [0 127.0.0.1:60866] "dbsize"
+1339518090.420270 [0 127.0.0.1:60866] "set" "x" "6"
+1339518096.506257 [0 127.0.0.1:60866] "get" "x"
+1339518099.363765 [0 127.0.0.1:60866] "del" "x"
+1339518100.544926 [0 127.0.0.1:60866] "get" "x"
QUIT
+OK
Connection closed by foreign host.
通过 telnet
运行MONITOR时,可以发送 QUIT
来停止退出。
5.1.2 使用MONITOR
命令监控hotkey
MONITOR
命令主要用于实时打印出Redis服务器接收到的命令,这包括所有对Redis数据库的操作,如读取和写入操作。
通过监控这些操作,我们可以分析出哪些键(key)被频繁访问,从而识别出hotkey。
以下是使用MONITOR
命令监控hotkey的步骤:
-
启动Redis客户端:首先,你需要连接到Redis服务器。这可以通过
redis-cli
命令行工具完成。 -
执行MONITOR命令:在Redis客户端中输入
MONITOR
命令。这将启动监控模式,实时输出所有接收到的命令。例如:
redis 127.0.0.1:6379> MONITOR
OK
1410855382.370791 [0 127.0.0.1:60581] "info"
1410855404.062722 [0 127.0.0.1:60581] "get" "a"
这里,OK
表示命令执行成功,随后的输出显示了时间戳、客户端信息和执行的命令。
-
分析输出:监控模式下,Redis服务器会输出所有接收到的命令。你需要分析这些输出,找出被频繁访问的键。这些频繁访问的键可能就是hotkey。
-
停止监控:当你完成监控任务后,可以通过发送
SIGINT
信号(通常是Ctrl+C)来停止监控,或者在客户端输入QUIT
命令退出监控模式。
需要注意的是,MONITOR
命令虽然非常有用,但它会对Redis服务器的性能产生一定影响。
因为MONITOR需要记录并输出所有命令,这会占用CPU资源和网络带宽。在高负载环境中,使用MONITOR
命令可能会导致吞吐量显著下降。因此,在生产环境中应谨慎使用,并尽量缩短监控时间。
顺便说一下: 和monitor命令一起,Redis 提供了一组内置的命令来获取 服务器 的状态信息,这些命令可以用来进行基本的监控。
-
INFO:提供关于 Redis 服务器的各种信息,包括 客户端 连接数、内存使用情况、持久化状态等。
INFO all
你也可以指定不同的部分,如
INFO clients
或INFO memory
。 -
MONITOR:实时显示所有到达 Redis 服务器的命令。这在调试时非常有用,但在生产环境中应谨慎使用,因为它会显著影响性能。
MONITOR
-
SLOWLOG:记录执行时间超过配置阈值的命令。这对于识别慢查询非常有用。
SLOWLOG GET
使用SLOWLOG 命令可以读取或重置 Redis 慢速查询日志。
通俗讲就是 redis 可以把执行时间超过我们设定值的命令记录下来,slowlog 是记录到内存中的哦,所以非常快。
这里的执行时间不包括 I/O 操作, 比如与客户端, 发送应答等等 , 就是实际执行命令所需的时间(命令唯一执行的阶段,线程被阻塞且不能同时处理其他请求)。
5.1.3 通过 redis-faina 工具实现 MONITOR 的分析与定位
redis-faina 通过解析 Redis 的 MONITOR
命令输出,帮助用户对 Redis 实例进行性能诊断。
Facebook Instagram 开源的 redis-faina(Python),提供了对 Monitor 的一些分析与定位。
redis-faina
是由 Instagram 开发并开源的一个 Redis 查询分析工具。
这个工具使用简单,但功能强大,尤其适合用于定位线上 Redis 性能问题。
安装 redis-faina
你可以通过以下命令来安装 redis-faina
:
git clone https://github.com/facebookarchive/redis-faina.git
然后进入下载的目录中。
redis-faina
的使用也非常简单,你可以通过以下两种方式之一来使用它:
5.1.4 通过管道从 stdin 读取 N 条命令进行分析处理:
redis-cli -p 6379 MONITOR | head -n | redis-faina.py [options]
这里,redis-cli
命令连接到 Redis 服务器,并执行 MONITOR
命令。
head -n
用于限制分析的命令数量,而 redis-faina.py
是分析工具的 Python 脚本。
如果你不指定 head -n 的参数,它将默认输出前 10 行。head -n 1000 显示 Redis 服务器接收到的前 1000 条命令。
[options] 表示选型,比如: --prefix-delimiter 前缀字符串
下面是一个列子
redis-cli -p 6379 monitor | head -n 1000 | redis-faina.py
输出如下,类似如下结果:
Overall Stats
========================================
Lines Processed 7904
Commands/Sec 256.80
Top Prefixes
========================================
n/a
Top Keys
========================================
970125 3952 (50.00%)
aaa850 2 (0.03%)
aaa919 2 (0.03%)
aaa852 2 (0.03%)
aaa853 2 (0.03%)
aaa678 2 (0.03%)
aaa679 2 (0.03%)
aaa856 2 (0.03%)
Top Commands
========================================
AUTH 3952 (50.00%)
set 3950 (49.97%)
Command Time (microsecs)
========================================
Median 1920.0
75% 2016.25
90% 2092.0
99% 2840.0
Heaviest Commands (microsecs)
========================================
AUTH 22542297.5
set 8236623.75
Slowest Calls
========================================
20718202.0 "AUTH" "970125"
8456.0 "set" "aaa82" "aaaaaaaa82"
6624.0 "set" "aaa103" "aaaaaaaa103"
6506.0 "set" "aaa817" "aaaaaaaa817"
6105.0 "set" "aaa2024" "aaaaaaaa2024"
6081.0 "set" "aaa1057" "aaaaaaaa1057"
6074.75 "set" "aaa1948" "aaaaaaaa1948"
6067.0 "set" "aaa576" "aaaaaaaa576"
从这个结果中,我们可以清晰的看到各种指标。
5.1.5 直接从文件中读取 N 条命令进行分析处理:
redis-cli -h xx.xx.xx.xx -p 6379 -a pwd monitor | head -n > /tmp/outfile.txt
redis-faina.py /tmp/outfile.txt
首先,将 MONITOR
命令的输出重定向到一个文件中,然后使用 redis-faina.py
脚本分析该文件。
redis-faina
会输出包括总命令数、每秒命令数(QPS)、最频繁使用的前缀、最频繁使用的键、最频繁执行的命令、命令执行时间的统计分布、最耗时的命令以及最慢的调用等统计信息。这些信息对于分析 Redis 性能和识别热点键(hotkey)非常有用。
需要注意的是,由于 redis MONITOR
输出的只有请求开始的时间,所以在一个非常繁忙的 Redis 实例中,根据该请求的开始时间以及下一个请求的开始时间,可以大概估算出一个请求的执行时间。
由此可以看出,redis-faina
统计的时间并不是十分精确的,尤其在分析一个非常闲的 Redis 实例时,分析的结果可能差异较大
5.1.6 MONITOR 的消耗
因为MONITOR流返回所有命令,所以用起来会有一定的消耗。
下面是一个基准测试对比:
不带MONITOR命令:
$ src/redis-benchmark -c 10 -n 100000 -q
PING_INLINE: 101936.80 requests per second
PING_BULK: 102880.66 requests per second
SET: 95419.85 requests per second
GET: 104275.29 requests per second
INCR: 93283.58 requests per second
带 MONITOR 命令 (redis-cli monitor > /dev/null
):
$ src/redis-benchmark -c 10 -n 100000 -q
PING_INLINE: 58479.53 requests per second
PING_BULK: 59136.61 requests per second
SET: 41823.50 requests per second
GET: 45330.91 requests per second
INCR: 41771.09 requests per second
通过上面的例子可以看到运行一个MONITOR命令降低了超过 50% 的吞吐量。
运行多个MONITOR会进一步降低性能。
MONITOR 不记录的命令
处于安全方面的考虑,所有的管理相关的命令不会记录到MONITOR的输出者。
下面几个命令也不会记录:
5.2 通过 Redis的 hotkey 分析工具在服务端探测 HotKey:
Redis自带的分析工具:在Redis 4.0及以上版本中,redis-cli
提供了--hotkeys
选项,可以帮助分析哪些Key是热点Key。
5.2.1 redis-cli提供了
--hotkeys的用法
redis-cli --hotkeys
是一个Redis命令行工具的选项, 可以监视和查看当前活动的热点键(hot keys)。
下面是使用 redis-cli --hotkeys
的示例:
- 打开终端或命令提示符。
- 输入以下命令以启动
redis-cli
并使用--hotkeys
选项:
redis-cli --hotkeys
执行上述命令后,redis-cli
将连接到本地Redis服务器(默认端口为 6379)。
在连接成功后,它将显示当前活动的热点键及其相关信息,例如键的名称、执行命令的类型和执行的次数。
$./redis-cli --hotkeys
# Scanning the entire keyspace to find hot keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
[00.00%] Hot key 'counter:000000000002' found so far with counter 87
[00.00%] Hot key 'key:000000000001' found so far with counter 254
[00.00%] Hot key 'mylist' found so far with counter 107
[00.00%] Hot key 'key:000000000000' found so far with counter 254
[45.45%] Hot key 'counter:000000000001' found so far with counter 87
[45.45%] Hot key 'key:000000000002' found so far with counter 254
[45.45%] Hot key 'myset' found so far with counter 64
[45.45%] Hot key 'counter:000000000000' found so far with counter 93
-------- summary -------
Sampled 22 keys in the keyspace!
hot key found with counter: 254 keyname: key:000000000001
hot key found with counter: 254 keyname: key:000000000000
hot key found with counter: 254 keyname: key:000000000002
hot key found with counter: 107 keyname: mylist
hot key found with counter: 93 keyname: counter:000000000000
hot key found with counter: 87 keyname: counter:000000000002
hot key found with counter: 87 keyname: counter:000000000001
hot key found with counter: 64 keyname: myset
可以看到,排在前几位的即是热点key。
请注意,redis-cli --hotkeys
选项仅适用于Redis 6.2版本或更高版本。
如果使用的是早期版本的Redis,该选项可能不可用。
此外,请确保在执行 redis-cli --hotkeys
命令之前已经安装和正确配置了Redis,并且Redis服务器正在运行。
5.2.2 redis-cli --hotkeys 的缺点
- 性能影响:由于它是一个全量的Hotkey数据,特别是存在大量hotkey的场景下会对性能产生较大影响,因此不推荐在生产环境频繁执行;
- 局限性:该命令返回的结果是基于Redis自身内部的采样与统计算法,根据机器资源的或预期场景的不同,该结果可能并不是100%符合预期的;
- 完整性:该命令只提供了热点键的基本信息,无法知道更详细的统计和分析信息,需要向业务侧确认;
6 代理端探测探测 HotKey:
如果系统中使用了Redis代理(如Twemproxy、Codis 等),可以在代理层添加统计功能,
在代理层,如Twemproxy或Codis,增加key使用情况的收集。对经过 proxy 代理的Redis请求进行统计,记录每个Key的访问频率。
这种方法适用于代理架构,如果使用的redis cluster 集群架构,这种方式可以忽略。
7 在redis client 客户端探测 HotKey
在Redis客户端(如Jedis、Lettuce)中添加统计代码,对每次对Redis的访问进行记录。
在Redis客户端可以统计每个Key的访问次数,并定期上报到监控系统。
7.1 大厂方案1: 透明多级缓存 (TMC) 架构设计
7.1.1 透明多级缓存 (TMC)简介
TMC ,即“透明多级缓存( Transparent Multilevel Cache )”,是有赞 PaaS 团队给公司内应用提供的整体缓存解决方案。
TMC 在通用“分布式缓存解决方案(本地缓存+ 如 Codis Proxy + Redis)”基础上,增加了以下功能:
- 应用层热点探测
- 应用层本地缓存
- 应用层缓存命中统计
以帮助应用层解决缓存使用过程中出现的热点访问问题。
有赞 为什么要做 TMC
使用有赞服务的电商商家数量和类型很多,商家会不定期做一些“商品秒杀”、“商品推广”活动,导致“营销活动”、“商品详情”、“交易下单”等链路应用出现 缓存热点访问 的情况:
- 活动时间、活动类型、活动商品之类的信息不可预期,导致 缓存热点访问 情况不可提前预知;
- 缓存热点访问 出现期间,应用层少数 热点访问 key 产生大量缓存访问请求:冲击分布式缓存系统,大量占据内网带宽,最终影响应用层系统稳定性;
为了应对以上问题,需要一个能够 自动发现热点 并 将热点缓存访问请求前置在应用层本地缓存 的解决方案,这就是 TMC 产生的原因。
多级缓存解决方案的痛点
基于上述描述,我们总结了下列 多级缓存解决方案 需要解决的需求痛点:
- 热点探测:如何快速且准确的发现 热点访问 key ?
- 数据一致性:前置在应用层的本地缓存,如何保障与分布式缓存系统的数据一致性?
- 效果验证:如何让应用层查看本地缓存命中率、热点 key 等数据,验证多级缓存效果?
- 透明接入:整体解决方案如何减少对应用系统的入侵,做到快速平滑接入?
TMC 聚焦上述痛点,设计并实现了整体解决方案。
以支持“热点探测”和“本地缓存”,减少热点访问时对下游分布式缓存服务的冲击,避免影响应用服务的性能及稳定性。
7.1.2 透明多级缓存 (TMC)整体架构
TMC 整体架构如上图,共分为三层:
- 存储层:提供基础的kv数据存储能力,针对不同的业务场景选用不同的存储服务( codis / zankv / aerospike );
- 代理层:为应用层提供统一的缓存使用入口及通信协议,承担分布式数据水平切分后的路由功能转发工作;
- 应用层:提供统一客户端给应用服务使用,内置“热点探测”、“本地缓存”等功能,对业务透明;
7.1.3 透明多级缓存 (TMC)本地SDK缓存
如何实现透明 ?
(TMC)本地SDK 是如何减少对业务应用系统的入侵,做到透明接入的?
对于公司 Java 应用服务,在缓存客户端使用方式上分为两类:
- 基于
spring.data.redis
包,使用RedisTemplate
编写业务代码; - 基于
youzan.framework.redis
包,使用RedisClient
编写业务代码;
不论使用以上那种方式,最终通过JedisPool
创建的Jedis
对象与缓存服务端代理层做请求交互。
TMC 对原生jedis包的JedisPool
和Jedis
类做了改造,
在JedisPool初始化过程中, 集成TMC“热点发现”+“本地缓存”功能 Hermes-SDK
包的初始化逻辑,
使Jedis
客户端与缓存服务端代理层交互时, 先与Hermes-SDK
交互,从而完成 “热点探测”+“本地缓存”功能的透明接入。
对于 Java 应用服务,只需使用特定版本的 jedis-jar 包,无需修改代码,即可接入 TMC 使用“热点发现”+“本地缓存”功能,做到了对应用系统的最小入侵。
7.1.4 透明多级缓存 (TMC)本地SDK缓存 整体结构
7.1.5 透明多级缓存 (TMC)本地SDK 模块划分
TMC 本地缓存整体结构分为如下模块:
- Jedis-Client: Java 应用与缓存服务端交互的直接入口,接口定义与原生 Jedis-Client 无异;
- Hermes-SDK:自研“热点发现+本地缓存”功能的SDK封装, Jedis-Client 通过与它交互来集成相应能力;
- Hermes服务端集群:接收 Hermes-SDK 上报的缓存访问数据,进行热点探测,将热点 key 推送给 Hermes-SDK 做本地缓存;
- 缓存集群:由代理层和存储层组成,为应用客户端提供统一的分布式缓存服务入口;
- 基础组件: etcd 集群、 Apollo 配置中心,为 TMC 提供“集群推送”和“统一配置”能力;
7.1.6 透明多级缓存 (TMC)本地SDK 基本流程
1) key 值获取
- Java 应用调用 Jedis-Client 接口获取key的缓存值时,Jedis-Client 会询问 Hermes-SDK 该 key 当前是否是 热点key;
- 对于 热点key ,直接从 Hermes-SDK 的 热点模块 获取热点 key 在本地缓存的 value 值,不去访问 缓存集群 ,从而将访问请求前置在应用层;
- 对于非 热点key ,Hermes-SDK 会通过
Callable
回调 Jedis-Client 的原生接口,从 缓存集群 拿到 value 值; - 对于 Jedis-Client 的每次 key 值访问请求,Hermes-SDK 都会通过其 通信模块 将 key访问事件 异步上报给 Hermes服务端集群 ,以便其根据上报数据进行“热点探测”;
2)key值过期
- Java 应用调用 Jedis-Client 的
set()
del()
expire()
接口时会导致对应 key 值失效,Jedis-Client 会同步调用 Hermes-SDK 的invalid()
方法告知其“ key 值失效”事件; - 对于 热点key ,Hermes-SDK 的 热点模块 会先将 key 在本地缓存的 value 值失效,以达到本地数据强一致。同时 通信模块 会异步将“ key 值失效”事件通过 etcd集群 推送给 Java 应用集群中其他 Hermes-SDK 节点;
- 其他Hermes-SDK节点的 通信模块 收到 “ key 值失效”事件后,会调用 热点模块 将 key 在本地缓存的 value 值失效,以达到集群数据最终一致;
3)热点发现
- Hermes服务端集群 不断收集 Hermes-SDK上报的 key访问事件,对不同业务应用集群的缓存访问数据进行周期性(3s一次)分析计算,以探测业务应用集群中的热点key列表;
- 本地预热:对于探测到的热点key列表,Hermes服务端集群 将其通过 etcd集群 推送给不同业务应用集群的 Hermes-SDK 通信模块 ,通知其对热点key列表进行本地缓存 预热;
4)配置读取
- Hermes-SDK 在启动及运行过程中,会从 Apollo配置中心 读取其关心的配置信息(如:启动关闭配置、黑白名单配置、etcd地址...);
- Hermes服务端集群 在启动及运行过程中,会从 Apollo配置中心 读取其关心的配置信息(如:业务应用列表、热点阈值配置、 etcd 地址...);
7.1.7 透明多级缓存 (TMC)本地SDK 稳定性
TMC本地缓存稳定性表现在以下方面:
- 数据上报异步化:Hermes-SDK 使用
rsyslog技术
对“ key 访问事件”进行异步化上报,不会阻塞业务; - 通信模块线程隔离:Hermes-SDK 的 通信模块 使用独立线程池+有界队列,保证事件上报&监听的I/O操作与业务执行线程隔离,即使出现非预期性异常也不会影响基本业务功能;
- 缓存管控:Hermes-SDK 的 热点模块 对本地缓存大小上限进行了管控,使其占用内存不超过 64MB(LRU),杜绝 JVM 堆内存溢出的可能;
7.1.8 透明多级缓存 (TMC)本地SDK 一致性
TMC 本地缓存一致性表现在以下方面:
- Hermes-SDK 的 热点模块 仅缓存 热点key 数据,绝大多数非热点 key 数据由 缓存集群 存储;
- 热点key 变更导致 value 失效时,Hermes-SDK 同步失效本地缓存,保证 本地强一致;
- 热点key 变更导致 value 失效时,Hermes-SDK 通过 etcd集群 广播事件,异步失效业务应用集群中其他节点的本地缓存,保证 集群最终一致;
7.1.9 TMC热点探测流程
TMC热点探测 整体流程
TMC 热点发现流程分为四步:
- 数据收集:收集 Hermes-SDK 上报的 key访问事件;
- 热度滑窗:对 App 的每个 Key ,维护一个时间轮,记录基于当前时刻滑窗的访问热度;
- 热度汇聚:对 App 的所有 Key ,以
<key,热度>
的形式进行 热度排序汇总; - 热点探测:对 App ,从 热Key排序汇总 结果中选出 TopN的热点Key ,推送给 Hermes-SDK;
7.1.9.1 数据收集
Hermes-SDK 通过本地rsyslog
将 key访问事件 以协议格式放入 kafka ,Hermes服务端集群 的每个节点消费 kafka 消息,实时获取 key访问事件。
访问事件协议格式如下:
- appName:集群节点所属业务应用
- uniqueKey:业务应用 key访问事件 的 key
- sendTime:业务应用 key访问事件 的发生时间
- weight:业务应用 key访问事件 的访问权值
Hermes服务端集群 节点将收集到的 key访问事件 存储在本地内存中,
内存数据结构为Map<String, Map<String, LongAdder>>
,
对应业务含义映射为Map< appName , Map< uniqueKey , 热度 >>
。
7.1.9.2 热度滑窗
1: 时间滑窗
Hermes服务端集群 节点,对每个App的每个 key ,维护了一个 时间轮:
- 时间轮中共10个 时间片,每个时间片记录当前 key 对应 3 秒时间周期的总访问次数;
- 时间轮10个时间片的记录累加即表示当前 key 从当前时间向前 30 秒时间窗口内的总访问次数;
2. 映射任务
Hermes服务端集群 节点,对每个 App 每3秒 生成一个 映射任务 ,交由节点内 “缓存映射线程池” 执行。
映射任务 内容如下:
- 对当前 App ,从
Map< appName , Map< uniqueKey , 热度 >>
中取出 appName 对应的MapMap< uniqueKey , 热度 >>
; - 遍历
Map< uniqueKey , 热度 >>
中的 key ,对每个 key 取出其热度存入其 时间轮 对应的时间片中;
7.1.9.3 热度汇聚
完成第二步“热度滑窗”后,映射任务 继续对当前 App 进行“热度汇聚”工作:
- 遍历 App 的 key ,将每个 key 的 时间轮 热度进行汇总(即30秒时间窗口内总热度)得到探测时刻 滑窗总热度;
- 将
< key , 滑窗总热度 >
以排序集合的方式存入 Redis存储服务 中,即 热度汇聚结果;
7.1.9.4 热点探测
- 在前几步,每3秒 一次的 映射任务 执行,对每个 App 都会产生一份当前时刻的 热度汇聚结果 ;
- Hermes服务端集群 中的“热点探测”节点,对每个 App ,只需周期性从其最近一份 热度汇聚结果 中取出达到热度阈值的 TopN 的 key 列表,即可得到本次探测的 热点key列表;
TMC 热点发现整体流程如下图:
7.1.10 TMC热点探测 特性总结
1. 实时性
Hermes-SDK基于rsyslog + kafka 实时上报 key访问事件。
映射任务 3秒一个周期完成“热度滑窗” + “热度汇聚”工作,当有 热点访问场景 出现时最长3秒即可探测出对应 热点key。
2. 准确性
key 的 热度汇聚结果 由“基于时间轮实现的滑动窗口”汇聚得到,相对准确地反应当前及最近正在发生访问分布。
3.扩展性
Hermes服务端集群 节点无状态,节点数可基于 kafka 的 partition 数量横向扩展。
“热度滑窗” + “热度汇聚” 过程基于 App 数量,在单节点内多线程扩展。
7.1.11 TMC实战效果
7.1.11.1 快手商家某次商品营销活动
有赞商家通过快手直播平台为某商品搞活动,造成该商品短时间内被集中访问产生访问热点,
活动期间 TMC 记录的实际热点访问效果数据如下:
图. 某核心应用的缓存请求&命中率曲线图
- 上图蓝线为应用集群调用
get()
方法访问缓存次数 - 上图绿线为获取缓存操作命中 TMC 本地缓存的次数
- 上图为本地缓存命中率曲线图
可以看出活动期间缓存请求量及本地缓存命中量均有明显增长,本地缓存命中率达到近 80% (即应用集群中 80% 的缓存查询请求被 TMC 本地缓存拦截)。
7.1.11.2 热点缓存对应用访问的加速效果
- 上图为应用接口QPS曲线
- 上图为应用接口RT曲线
可以看出活动期间应用接口的请求量有明显增长,由于 TMC 本地缓存的效果应用接口的 RT 反而出现下降。
7.1.11.3 双十一期间部分应用 TMC 效果展示
1. 商品域核心应用效果
5-2-2. 活动域核心应用效果
7.1.12 TMC功能的总结
在有赞, TMC 目前已为商品中心、物流中心、库存中心、营销活动、用户中心、网关&消息等多个核心应用模块提供服务,后续应用也在陆续接入中。
TMC 在提供“热点探测” + “本地缓存”的核心能力同时,也为应用服务提供了灵活的配置选择,应用服务可以结合实际业务情况在“热点阈值”、“热点key探测数量”、“热点黑白名单”维度进行自由配置以达到更好的使用效果。
配合三级缓存的使用,需要进行 热key的 探测,有赞平台通过 热key的探测和 支持,
其中:活动期间,本地缓存命中率达到近 80%的命中率, 并且, 响应时间,和平峰时段,没有变化。
Canal使用场景2: 三级缓存的 数据一致性 通知
在100W qps 三级缓存组件 的架构中,也需要通过 Canal 进行 binlog 的 订阅, 进行无入侵的 缓存数据维护
7.2 大厂方案2: 结合开源hotkey,做热点探测和预加载
基于开源hotkey进行 热点探测,有很多小伙伴,在生产系统进行了 缓存系统的重构, 整体的架构图如下:
没有hotkey探测和有 hotkey探测的缓存组件做对比,来说明这套机制的优缺点。
特性 | 没有hotkey探测和预加载的缓存 | 使用hotkey探测和预加载的缓存 |
---|---|---|
机器资源 | 高配物理机/虚拟机 | 普通物理机/虚拟机/容器 |
管控复杂 | 无法控制热点,不易监控 | 热点数据可以监控统计,可以手动刷新 |
资源利用率 | 资源利用率低,无论是否是热点数据都占用资源 | 资源利用率高,大部分热点数据持有资源 |
突发流量 | 无法弹性应对突发流量 | 弹性应对突发流量 |
预发流量 | 预设所有数据 | 只提前预设热点数据 |
数据一致性 | 集群内数据不一致情况时常发生,出现“横跳”现象 | 集群内数据一致性高,极少或不发生不一致性情况 |
以上内容的视频介绍,在尼恩的 《第26章 百万qps 三级缓存 组件实操》视频 中 有介绍,具体参见视频。
这里涉及的内容很复杂,也很重要,具体请参见视频,这里不做赘述。
说在最后:有问题找老架构取经
Redis hotkey 问题,按照尼恩的梳理,进行 深度回答,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
和前文提到的小伙伴一样,回答到这个水平,阿里P7 offer到手,年薪 60W,还特意来感谢了尼恩。
在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。
很多小伙伴刷完后, 吊打面试官, 大厂横着走。
在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。
另外,如果没有面试机会, 可以找尼恩来改简历、做帮扶。前段时间,刚指导一个27岁 被裁小伙,拿到了一个年薪45W的JD +PDD offer,逆天改命。
狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” 。
技术自由的实现路径:
实现你的 架构自由:
《阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了》
《峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?》
… 更多架构文章,正在添加中
实现你的 响应式 自由:
这是老版本 《Flux、Mono、Reactor 实战(史上最全)》
实现你的 spring cloud 自由:
《Spring cloud Alibaba 学习圣经》 PDF
《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》
实现你的 linux 自由:
实现你的 网络 自由:
《网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!》
实现你的 分布式锁 自由:
实现你的 王者组件 自由:
《队列之王: Disruptor 原理、架构、源码 一文穿透》
《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》
《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》