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

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

3天内不再提示

redis分布式锁造成的事故分析及解决方案

数据分析与开发 来源:浪漫先生 2020-08-27 15:27 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

前言

基于Redis使用分布式锁在当今已经不是什么新鲜事了。本篇文章主要是基于我们实际项目中因为redis分布式锁造成的事故分析及解决方案。

背景:我们项目中的抢购订单采用的是分布式锁来解决的。有一次,运营做了一个飞天茅台的抢购活动,库存100瓶,但是却超卖了!要知道,这个地球上飞天茅台的稀缺性啊!!!事故定为P0级重大事故...只能坦然接受。整个项目组被扣绩效了~~事故发生后,CTO指名点姓让我带头冲锋来处理,好吧,冲~

事故现场

经过一番了解后,得知这个抢购活动接口以前从来没有出现过这种情况,但是这次为什么会超卖呢?

原因在于:之前的抢购商品都不是什么稀缺性商品,而这次活动居然是飞天茅台,通过埋点数据分析,各项数据基本都是成倍增长,活动热烈程度可想而知!话不多说,直接上核心代码,机密部分做了伪代码处理。。。

publicSeckillActivityRequestVOseckillHandle(SeckillActivityRequestVOrequest){ SeckillActivityRequestVOresponse; Stringkey="key:"+request.getSeckillId; try{ BooleanlockFlag=redisTemplate.opsForValue().setIfAbsent(key,"val",10,TimeUnit.SECONDS); if(lockFlag){ //HTTP请求用户服务进行用户相关的校验 //用户活动校验 //库存校验 Objectstock=redisTemplate.opsForHash().get(key+":info","stock"); assertstock!=null; if(Integer.parseInt(stock.toString())<= 0) {                 // 业务异常             } else {                 redisTemplate.opsForHash().increment(key+":info", "stock", -1);                 // 生成订单                 // 发布订单创建成功事件                 // 构建响应VO             }         }     } finally {         // 释放锁         stringRedisTemplate.delete("key");         // 构建响应VO     }     return response; }

以上代码,通过分布式锁过期时间有效期10s来保障业务逻辑有足够的执行时间;采用try-finally语句块保证锁一定会及时释放。业务代码内部也对库存进行了校验。看起来很安全啊~ 别急,继续分析。。。

事故原因

飞天茅台抢购活动吸引了大量新用户下载注册我们的APP,其中,不乏很多羊毛党,采用专业的手段来注册新用户来薅羊毛和刷单。

当然我们的用户系统提前做好了防备,接入阿里云人机验证、三要素认证以及自研的风控系统等各种十八般武艺,挡住了大量的非法用户。此处不禁点个赞~

「但也正因如此,让用户服务一直处于较高的运行负载中。」

抢购活动开始的一瞬间,大量的用户校验请求打到了用户服务。导致用户服务网关出现了短暂的响应延迟,有些请求的响应时长超过了10s,但由于HTTP请求的响应超时我们设置的是30s,这就导致接口一直阻塞在用户校验那里,10s后,分布式锁已经失效了,此时有新的请求进来是可以拿到锁的,也就是说锁被覆盖了。

这些阻塞的接口执行完之后,又会执行释放锁的逻辑,这就把其他线程的锁释放了,导致新的请求也可以竞争到锁~这真是一个极其恶劣的循环。

这个时候只能依赖库存校验,但是偏偏库存校验不是非原子性的,采用的是get and compare的方式,超卖的悲剧就这样发生了~~~

事故分析

仔细分析下来,可以发现,这个抢购接口在高并发场景下,是有严重的安全隐患的,主要集中在三个地方:

「没有其他系统风险容错处理」由于用户服务吃紧,网关响应延迟,但没有任何应对方式,这是超卖的导火索。

「看似安全的分布式锁其实一点都不安全」虽然采用了set key value [EX seconds] [PX milliseconds] [NX|XX]的方式,但是如果线程A执行的时间较长没有来得及释放,锁就过期了,此时线程B是可以获取到锁的。当线程A执行完成之后,释放锁,实际上就把线程B的锁释放掉了。这个时候,线程C又是可以获取到锁的,而此时如果线程B执行完释放锁实际上就是释放的线程C设置的锁。这是超卖的直接原因。

「非原子性的库存校验」非原子性的库存校验导致在并发场景下,库存校验的结果不准确。这是超卖的根本原因。

通过以上分析,问题的根本原因在于库存校验严重依赖了分布式锁。因为在分布式锁正常set、del的情况下,库存校验是没有问题的。但是,当分布式锁不安全可靠的时候,库存校验就没有用了。

解决方案

知道了原因之后,我们就可以对症下药了。

实现相对安全的分布式锁

相对安全的定义:set、del是一一映射的,不会出现把其他现成的锁del的情况。从实际情况的角度来看,即使能做到set、del一一映射,也无法保障业务的绝对安全。

因为锁的过期时间始终是有界的,除非不设置过期时间或者把过期时间设置的很长,但这样做也会带来其他问题。故没有意义。

要想实现相对安全的分布式锁,必须依赖key的value值。在释放锁的时候,通过value值的唯一性来保证不会勿删。我们基于LUA脚本实现原子性的get and compare,如下:

publicvoidsafedUnLock(Stringkey,Stringval){ StringluaScript="localin=ARGV[1]localcurr=redis.call('get',KEYS[1])ifin==currthenredis.call('del',KEYS[1])endreturn'OK'""; RedisScriptredisScript=RedisScript.of(luaScript); redisTemplate.execute(redisScript,Collections.singletonList(key),Collections.singleton(val)); }

我们通过LUA脚本来实现安全地解锁。

实现安全的库存校验

如果我们对于并发有比较深入的了解的话,会发现想 get and compare/ read and save 等操作,都是非原子性的。

如果要实现原子性,我们也可以借助LUA脚本来实现。但就我们这个例子中,由于抢购活动一单只能下1瓶,因此可以不用基于LUA脚本实现而是基于redis本身的原子性。原因在于:

//redis会返回操作之后的结果,这个过程是原子性的 LongcurrStock=redisTemplate.opsForHash().increment("key","stock",-1);

发现没有,代码中的库存校验完全是“画蛇添足”。

改进之后的代码

经过以上的分析之后,我们决定新建一个DistributedLocker类专门用于处理分布式锁。

publicSeckillActivityRequestVOseckillHandle(SeckillActivityRequestVOrequest){ SeckillActivityRequestVOresponse; Stringkey="key:"+request.getSeckillId(); Stringval=UUID.randomUUID().toString(); try{ BooleanlockFlag=distributedLocker.lock(key,val,10,TimeUnit.SECONDS); if(!lockFlag){ //业务异常 } //用户活动校验 //库存校验,基于redis本身的原子性来保证 LongcurrStock=stringRedisTemplate.opsForHash().increment(key+":info","stock",-1); if(currStock < 0) { // 说明库存已经扣减完了。             // 业务异常。             log.error("[抢购下单] 无库存");         } else {             // 生成订单             // 发布订单创建成功事件             // 构建响应         }     } finally {         distributedLocker.safedUnLock(key, val);         // 构建响应     }     return response; }

深度思考

分布式锁有必要么

改进之后,其实可以发现,我们借助于redis本身的原子性扣减库存,也是可以保证不会超卖的。对的。但是如果没有这一层锁的话,那么所有请求进来都会走一遍业务逻辑,由于依赖了其他系统,此时就会造成对其他系统的压力增大。这会增加的性能损耗和服务不稳定性,得不偿失。基于分布式锁可以在一定程度上拦截一些流量。

分布式锁的选型

有人提出用RedLock来实现分布式锁。RedLock的可靠性更高,但其代价是牺牲一定的性能。在本场景,这点可靠性的提升远不如性能的提升带来的性价比高。如果对于可靠性极高要求的场景,则可以采用RedLock来实现。

再次思考分布式锁有必要么

由于bug需要紧急修复上线,因此我们将其优化并在测试环境进行了压测之后,就立马热部署上线了。实际证明,这个优化是成功的,性能方面略微提升了一些,并在分布式锁失效的情况下,没有出现超卖的情况。

然而,还有没有优化空间呢?有的!

由于服务是集群部署,我们可以将库存均摊到集群中的每个服务器上,通过广播通知到集群的各个服务器。网关层基于用户ID做hash算法来决定请求到哪一台服务器。这样就可以基于应用缓存来实现库存的扣减和判断。性能又进一步提升了!

//通过消息提前初始化好,借助ConcurrentHashMap实现高效线程安全 privatestaticConcurrentHashMapSECKILL_FLAG_MAP=newConcurrentHashMap<>(); //通过消息提前设置好。由于AtomicInteger本身具备原子性,因此这里可以直接使用HashMap privatestaticMapSECKILL_STOCK_MAP=newHashMap<>(); ... publicSeckillActivityRequestVOseckillHandle(SeckillActivityRequestVOrequest){ SeckillActivityRequestVOresponse; LongseckillId=request.getSeckillId(); if(!SECKILL_FLAG_MAP.get(requestseckillId)){ //业务异常 } //用户活动校验 //库存校验 if(SECKILL_STOCK_MAP.get(seckillId).decrementAndGet()< 0) {         SECKILL_FLAG_MAP.put(seckillId, false);         // 业务异常     }     // 生成订单     // 发布订单创建成功事件     // 构建响应     return response; }

通过以上的改造,我们就完全不需要依赖redis了。性能和安全性两方面都能进一步得到提升!

当然,此方案没有考虑到机器的动态扩容、缩容等复杂场景,如果还要考虑这些话,则不如直接考虑分布式锁的解决方案。

总结

稀缺商品超卖绝对是重大事故。如果超卖数量多的话,甚至会给平台带来非常严重的经营影响和社会影响。经过本次事故,让我意识到对于项目中的任何一行代码都不能掉以轻心,否则在某些场景下,这些正常工作的代码就会变成致命杀手!对于一个开发者而言,则设计开发方案时,一定要将方案考虑周全。怎样才能将方案考虑周全?唯有持续不断地学习!

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

    关注

    30

    文章

    4941

    浏览量

    73148
  • 线程
    +关注

    关注

    0

    文章

    508

    浏览量

    20759
  • Redis
    +关注

    关注

    0

    文章

    390

    浏览量

    12056

原文标题:一次由 Redis 分布式锁造成的重大事故

文章出处:【微信号:DBDevs,微信公众号:数据分析与开发】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    全新分布式智慧投屏终端开启智慧教学新时代!

    全新分布式智慧教室系统的核心设备是分布式智慧投屏终端,集无线投屏、白板书写、多机联动、信息发布于一体,为老师学生分组投屏、互动分享提供多屏协作解决方案。智能书写功能与方案中多屏互动相辅
    的头像 发表于 09-19 11:35 505次阅读
    全新<b class='flag-5'>分布式</b>智慧投屏终端开启智慧教学新时代!

    【节能学院】Acrel-1000DP分布式光伏监控系统在奉贤平高食品 4.4MW 分布式光伏中应用

    摘要:在“双碳”和新型电力系统建设背景下,分布式光伏接入比例不断提高,对配电网电压、调度运行及调峰等环节造成强烈冲击。本文设计包含平台层、设备层二层架构体系的分布式光伏管控平台,以及小容量工商业
    的头像 发表于 08-23 08:04 3306次阅读
    【节能学院】Acrel-1000DP<b class='flag-5'>分布式</b>光伏监控系统在奉贤平高食品 4.4MW <b class='flag-5'>分布式</b>光伏中应用

    分布式光伏发电监测系统技术方案

    分布式光伏发电监测系统技术方案 柏峰【BF-GFQX】一、系统目标 :分布式光伏发电监测系统旨在通过智能化的监测手段,实现对分布式光伏电站的全方位、高精度、实时化管理。该系统能
    的头像 发表于 08-22 10:51 2884次阅读
    <b class='flag-5'>分布式</b>光伏发电监测系统技术<b class='flag-5'>方案</b>

    Redis集群部署配置详解

    Redis集群是一种分布式Redis解决方案,通过数据分片和主从复制实现高可用性和横向扩展。集群将整个数据集分割成16384个哈希槽(hash slots),每个节点负责一部分槽位。
    的头像 发表于 07-17 11:04 588次阅读

    分布式IO选型指南:2025年分布式无线远程IO品牌及采集控制方案详解

    。2025年,分布式IO市场呈现出技术革新与品牌竞争加剧的态势。本文基于权威数据平台(如Statista、MarketsandMarkets、Grand View Research)的市场分析,全面解读分布式无线远程IO的选型要
    的头像 发表于 06-23 09:48 966次阅读

    分布式IO模组选购指南:2025主流品牌盘点与应用方案解析

    分布式IO模块市场进入快速增长期。本文基于权威数据平台的市场分析,盘点2025年主流分布式IO模块品牌,介绍优势产品,并解析典型应用方案,旨在为企业选购提供权威参考。
    的头像 发表于 06-10 16:57 910次阅读

    安科瑞分布式光伏监控系统:高效、安全、智能的绿色能源解决方案

    ?并网标准如何满足?运维成本如何降低?安科瑞电气股份有限公司凭借多年行业经验,创新推出Acrel-1000DP分布式光伏监控系统,为光伏电站提供全生命周期解决方案。 一、分布式光伏发电系统标准规范 1.并网标准
    的头像 发表于 05-08 16:40 549次阅读

    浅谈分布式光伏系统在工业企业的设计及应用

    主要对工业厂区屋顶分布式光伏发电系统的设计及应用进行研究,为工业厂区能源供应提供一种全新的解决思路和技术支持。介绍了工业厂区屋顶分布式光伏系统及其优势,分析了工业厂区屋顶分布式光伏系统
    的头像 发表于 03-21 14:24 727次阅读
    浅谈<b class='flag-5'>分布式</b>光伏系统在工业企业的设计及应用

    铁塔基站分布式储能揭秘!

    的正常运转。为了解决这些问题,安科瑞推出了基站铁塔分布式储能解决方案,为基站的稳定供电提供了可靠的保障。 一、什么是基站铁塔分布式储能? 基站铁塔分布式储能系统是一种将储能电池
    的头像 发表于 02-12 16:42 1352次阅读
    铁塔基站<b class='flag-5'>分布式</b>储能揭秘!

    可靠稳定,经济灵活,真热插拔的MR30分布式IO工业通讯解决方案介绍

    MR30分布式IO是基于模块化设计、高性能的实时自动化IO系统,体积小巧、结构紧凑组合灵活。采用高速总线技术,所有模块均支持带电热插拔功能,具有丰富的兼容性,是不同场景下的分布式控制应用的完美解决方案
    的头像 发表于 02-07 10:55 777次阅读
    可靠稳定,经济灵活,真热插拔的MR30<b class='flag-5'>分布式</b>IO工业通讯<b class='flag-5'>解决方案</b>介绍

    分布式云化数据库有哪些类型

    分布式云化数据库有哪些类型?分布式云化数据库主要类型包括:关系型分布式数据库、非关系型分布式数据库、新SQL分布式数据库、以列方式存储数据、
    的头像 发表于 01-15 09:43 871次阅读

    基于ptp的分布式系统设计

    在现代分布式系统中,精确的时间同步对于确保数据一致性、系统稳定性和性能至关重要。PTP(Precision Time Protocol)是一种网络协议,用于在分布式系统中实现高精度的时间同步
    的头像 发表于 12-29 10:09 975次阅读

    分布式、域控及SOA架构车身功能测试方案

    北汇信息推出分布式、域控以及SOA架构的车身功能测试解决方案,支持在实验室环境下完成车身单部件、系统级功能自动化测试,可以极大地提升车身功能的可靠性和稳定性。
    的头像 发表于 12-27 09:05 3386次阅读
    <b class='flag-5'>分布式</b>、域控及SOA架构车身功能测试<b class='flag-5'>方案</b>

    HarmonyOS Next 应用元服务开发-分布式数据对象迁移数据权限与基础数据

    使用分布式数据对象迁移数据,当需要迁移的数据较大(100KB以上)或需要迁移文件时,可以使用分布式数据对象。原理与接口说明详见分布式数据对象跨设备数据同步。 说明:自API 12起,由于直接使用跨
    发表于 12-24 09:40

    分布式光伏监控系统在能源领域中的重要性

    在当今能源领域,分布式光伏发电作为一种可持续的能源解决方案正日益普及。而分布式光伏监控系统在其中扮演着至关重要的角色,为分布式光伏发电的高效运行和管理带来了诸多显著好处。 一、提升发电
    的头像 发表于 12-09 14:39 1097次阅读
    <b class='flag-5'>分布式</b>光伏监控系统在能源领域中的重要性