面试官:热Key问题怎么解决
高并发场景下,大家都可能遇到的经典难题——Redis的热点Key问题。

我们都知道,Redis作为我们首选的内存数据库,性能非常出色。在分布式架构下,我们通常会搭建Redis集群,通过一致性哈希等算法,把不同的Key均匀地分散到不同的节点上,来分摊访问压力,就像这张图展示的一样 。

理想很美好,但现实往往很骨感。大家可以想象一个场景:双十一零点秒杀,或者微博上突然出现一个爆点新闻。成千上万的用户在同一瞬间涌入,抢购同一个商品,访问同一个热搜。这时会发生什么呢?"

"没错,所有请求访问的都是同一个Key,比如stock:product:888。无论我们的集群有多少个节点,这个Key只会固定落在某一个节点上。
大家看,海量的请求就像洪水一样,瞬间涌向了节点2。而旁边的节点1和节点3,却几乎无事可做,处于空闲状态。结果就是,节点2因为不堪重负,CPU和网络带宽被瞬间打满,直接超载、甚至宕机!"

"那么,节点宕机了,问题就结束了吗?不,这只是灾难的开始。
Redis节点宕机后,缓存就失效了。但用户的请求不会停止,这部分巨大的流量会怎么办?它们会‘穿透’缓存层,像一把尖刀,直接刺向我们后端的数据库!
我们都知道,数据库的IO性能和Redis完全不是一个数量级。面对如此恐怖的并发量,数据库瞬间就会被压垮,导致整个系统雪崩。这就是典型的‘缓存击穿’,也是热点Key问题最致命的后果。"
解决方案一:热点分散
"那么,问题来了,怎么解决?
一个很自然的想法是:既然压力都集中在一个点上,那我们能不能把它‘打散’?

这就是我们的第一种策略:热点分散。它的核心思想,就是把一个热点Key,复制成很多份,给它们加上不同的后缀,比如用户ID或者随机数。比如,原来的stock:product:888,现在变成了stock:product:888_1, _2, _3... 直到 _N。
通过哈希计算,这些加了后缀的Key就会被分散到不同的Redis节点上。这样一来,对同一个商品库存的访问压力,就被成功地分摊到了整个集群。
这种方法的优点是简单、有效,直接分摊了访问压力。但缺点也很明显,如果热点是突发的,我们很难提前预知并进行复制和分散,会显得有些措手不及。"
解决方案二:应用层本地缓存
"我们再想一下,秒杀场景下,读多写少是常态。99%的请求都是在查询商品信息和库存,真正进入下单扣库存环节的只是少数。那我们能不能在请求到达Redis之前,就把它拦下来呢?

答案是肯定的。这就是我们的第二种大杀器:应用层本地缓存。
我们可以在我们的Java应用(比如Tomcat服务器)内部,直接使用内存开辟一小块空间作为缓存。在Java生态中,有很多成熟的本地缓存框架,比如Google的Guava Cache,或者性能更强劲的Caffeine。
当读请求过来时,我们先查本地缓存。由于本地缓存是纯内存操作,没有任何网络开销,性能是极致的。绝大部分(比如99%)的读请求在这里就直接被返回了。只有当本地缓存没有命中时,才需要穿透到Redis。这样一来,抵达Redis的流量就只剩下了1%!大大减轻了Redis的压力。"
方案总结
"好了,现在我们有了两个强大的武器。在真实的秒杀系统中,我们通常会将它们结合起来,构建一个固若金汤的防御体系。

- 对于读请求:我们用‘本地缓存’来拦截。利用服务器内存的极致性能,响应速度最快,并且拦截掉99%的查询流量。
- 对于写请求(比如扣减库存):我们用‘热点分散’策略。将写操作的压力均匀地分散到后端的各个Redis节点,避免单点过载。
通过这样‘读写分离’、‘层层过滤’的组合策略,我们就能从容应对高并发下的热点访问问题,保证我们Java系统的稳定和高可用。