0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

真实案例解析缓存大热key的致命陷阱

京东云 来源:jf_75140285 2025-01-24 15:39 次阅读

作者:京东零售 曹志飞

引言

在现代软件架构中,缓存是提高系统性能和响应速度的重要手段。然而,如果不正确地使用缓存,可能会导致严重的线上事故,尤其是缓存的大热key问题更是老生常谈。本文将探讨一个常见但容易被忽视的问题:缓存大热key和缓存击穿问题。我们将从一个真实案例入手,分析其原因,并提供解决方案和预防措施。

案例描述

某系统在双十一大促期间,遇到了一个严重的线上事故。业务人员在创建一个大型活动,该大型活动由于活动条件和活动奖励比较多,导致生成的缓存内容非常大。活动上线后,系统就开始出现各种异常告警,核心UMP监控可用率由100%持续下降到20%,系统访问Redis的调用次数和查询性能也断崖式下降,后续更是产生连锁反应影响了其他多个核心接口的可用率,导致整个系统服务不可用。

原因分析

在这个系统中,为了提高查询活动的性能,我们开发团队决定使用Redis作为缓存系统。将每个活动信息作为一个key-value存储在Redis中。由于业务需要,有时候业务运营人员也会创建一个非常庞大的活动,来支撑双十一期间的各种玩法。针对这种庞大的活动,我们开发团队也提前预料到了可能会出现的大key和热key问题,所以在查询活动缓存之前增加了一层本地jvm缓存,本地jvm缓存5分钟,缓存失效后再去回源查询Redis中的活动缓存,本以为会万无一失,没想到最后还是出了问题。

image.png


查询方法伪代码

ActivityCache present = activityLocalCache.getIfPresent(activityDetailCacheKey);
if (present != null) {
    ActivityCache activityCache = incentiveActivityPOConvert.copyActivityCache(present);
    return activityCache
}
ActivityCache remoteCache = getCacheFromRedis(activityDetailCacheKey);
activityLocalCache.put(activityDetailCacheKey, remoteCache);
return remoteCache;

查询活动缓存流程如上图所示,为什么加了本地缓存还是出了问题?
这里其实就存在着第一个缓存陷阱:缓存击穿问题。首先解释一下什么是缓存击穿;缓存击穿(Cache Miss)是指在高并发的系统中,如果某个缓存键对应的值在缓存中不存在(即缓存失效),那么所有请求都会直接访问后端数据库,导致数据库的负载瞬间增加,可能会引发数据库宕机或服务不可用的情况。所以在本次事故里边,运营人员审批活动上线的一瞬间,活动缓存只是写入到了Redis缓存中,但是本地缓存还都是空的,所以此时就会有大量请求来同时访问Redis。
按照以往经验,Redis缓存都是纯内存操作,查询性能可以满足大量请求同时查询活动缓存,就在此时我们却陷入了第二个缓存陷阱:网络带宽瓶颈;Redis的高并发性能毋庸置疑,但是我们却忽略了一个大key和热key对网络带宽的影响,本次引发问题的大热key大小达到了1.5M,经过事后了解京东Redis对单分片的网络带宽也有限流,默认200M,根据换算,该热key最多只能支持133次的并发访问。所以就在活动上线的同一时刻,加上缓存击穿的影响,迅速达到了Redis单分片的带宽限流阈值,导致Redis线程进入阻塞状态,以至于所有的业务服务器都无法查询Redis缓存成功,最终引发了缓存雪崩效应。

解决方案

为了解决这个问题,我们开发团队采取了以下措施:

  1. 大key治理:更换缓存对象序列化方法,由原来的JSON序列化调整为Protostuff序列化方式。治理效果:缓存对象大小由1.5M减少到了0.5M。
  2. 使用压缩算法:在存储缓存对象时,再使用压缩算法(如gzip)对数据进行压缩,注意设置压缩阈值,超过一定阈值后再进行压缩,以减少占用的内存空间和网络传输的数据量。压缩效果:500k压缩到了17k。
  3. 缓存回源优化:本地缓存miss后回源查询Redis增加线程锁,减少回源Redis并发数量。
  4. 监控和优化Redis配置:定期监控Redis网络传输情况,根据实际情况调整Redis的限流配置,以确保Redis的稳定运行。

治理后业务伪代码如下:

ActivityCache present = activityLocalCache.get(activityDetailCacheKey, key -> getCacheFromRedis(key));
if (present != null) {                
    return present;
}
         
/**
* 查询二进制缓存
*
* @param activityDetailCacheBinKey
* @return
*/
private ActivityCache getBinCacheFromJimdb(String activityDetailCacheBinKey) {
    List activityByteList = slaveCluster.hMget(activityDetailCacheBinKey.getBytes(),"stock".getBytes());
    if (activityByteList.get(0) != null && activityByteList.get(0).length > 0) {
        byte[] decompress = ByteCompressionUtil.decompress(activityByteList.get(0));
        ActivityCache activityCache = ProtostuffUtil.deserialize(decompress, ActivityCache.class);
        if (activityCache != null) {
            if (activityByteList.get(1) != null && activityByteList.get(1).length > 0) {
                activityCache.setAvailableStock(Integer.valueOf(new String(activityByteList.get(1))));
            }
            return activityCache;
        }
    }
return null;
[]>

预防措施

为了避免类似的问题再次发生,开发团队采取了以下预防措施:

  1. 设计阶段考虑缓存策略:在系统设计阶段,充分考虑缓存的使用场景和数据特性,避免盲目使用大key缓存。
  2. 进行压力测试和性能评估:在上线前,进行充分的压力测试和性能评估,模拟高并发和大数据量的情况,及时发现和解决潜在问题。
  3. 定期进行系统优化和升级:随着业务的发展和技术的进步,定期对系统进行优化和升级,引入新的技术和工具来提高系统的性能和稳定性。

结论

缓存大key和热key是缓存使用中常见的陷阱,千万不要心存侥幸,否则会引发严重的线上事故。通过本文的案例分析和解决方案,我们希望能够帮助读者更好地理解和应对这个问题。记住,合理使用缓存是提高系统性能的关键,而不是简单地将所有数据都存储在缓存中。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 软件
    +关注

    关注

    69

    文章

    5045

    浏览量

    88282
  • 缓存
    +关注

    关注

    1

    文章

    243

    浏览量

    26793
  • key
    key
    +关注

    关注

    0

    文章

    51

    浏览量

    12880
收藏 人收藏

    相关推荐

    LRU缓存模块最佳实践

    LRU(Least Recently Used)是一种缓存替换算法,它的核心思想是当缓存满时,替换最近最少使用的数据。在实际应用中,LRU算法被广泛应用于缓存、页面置换等领域。Rust语言提供了一个
    的头像 发表于 09-30 16:47 985次阅读

    缓存有大key?你得知道的一些手段

          背景: 最近系统内缓存CPU使用率一直报警,超过设置的70%报警阀值,针对此场景,需要对应解决缓存是否有大key使用问题,扫描缓存集群的大
    的头像 发表于 06-19 09:38 855次阅读
    <b class='flag-5'>缓存</b>有大<b class='flag-5'>key</b>?你得知道的一些手段

    基于javaPoet的缓存key优化实践

    数据库中的数据缓存在redis/本地缓存中,代码如下:   @Cacheable(value = { "per" }, key="#person.getId
    的头像 发表于 01-14 15:18 561次阅读
    基于javaPoet的<b class='flag-5'>缓存</b><b class='flag-5'>key</b>优化实践

    开关电源的设计方法解析

    开关电源的设计方法解析
    发表于 08-06 14:14

    避开无源元件的陷阱

    避开无源元件的陷阱如果选错无源元件,再好的运算放大器或数据转换器也可能会表现不佳本文说明需要注意的一些基本陷阱
    发表于 07-29 17:11

    缓存的作用和设计模式

    查询数据,获取数据后并加载到缓存缓存失效:数据更新写到数据库,操作成功后,让缓存失效,查询时候再重新加载;缓存穿透:查询数据库不存在的对象,也就不存在
    发表于 01-05 17:57

    设计/布局的关键测试陷阱-Design/Layout Pit

    设计/测试重点布局的陷阱-Design/Layout Pitfalls Test Key 正常chip,與test key的die size, PE 建議兩者需一致.Case Study.兩者大小不一,會造成PE部門在 CP
    发表于 11-20 11:19 0次下载

    基于节点中心性度量的缓存机制

    中心性、紧密中心性和介数中心性,并将这3个度量和缓存空间空闲率作为缓存节点选择的重要指标。仿真实验表明,与传统内容中心网络(CCN)缓存机制相比,CMC可以有效地提高
    发表于 01-17 11:00 0次下载
    基于节点中心性度量的<b class='flag-5'>缓存</b>机制

    缓存服务器运作的原理解析

    /O。另一方面,memcached在存储区中对于每一个key都维护一个过期时间,一旦达到这个过期时间,memcached便会自动删除这个key,这使得我们的过期检查非常容易,只需要在保存缓存数据时指定过期时间即可。
    发表于 04-28 12:43 1204次阅读

    如何设计一个缓存系统?

    则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。 解决方案 有很多种方法可以有效地解决
    的头像 发表于 02-08 11:40 3009次阅读

    《C陷阱与缺陷》pdf

    《C陷阱与缺陷》pdf
    发表于 12-13 10:46 0次下载

    《C陷阱与缺陷》pdf

    《C陷阱与缺陷》pdf
    发表于 02-14 11:02 0次下载

    缓存被穿透了如何解决

    首先来了解几个概念: 缓存穿透:大量请求根本不存在的key 缓存雪崩:redis中大量key集体过期 缓存击穿:redis中一个热点
    的头像 发表于 05-23 09:54 759次阅读
    <b class='flag-5'>缓存</b>被穿透了如何解决

    proteus+key+C51源码解析

    proteus+key+C51
    发表于 10-24 09:41 0次下载

    聊聊缓存击穿的解决方法

    缓存击穿,Redis中的某个热点key不存在或者过期,但是此时有大量的用户访问该key。比如xxx直播间优惠券抢购、xxx商品活动,这时候大量用户会在某个时间点一同访问该热点事件。但是可能
    的头像 发表于 10-23 13:54 259次阅读