因为一个Bug,差点损失了100w

这篇具有很好参考价值的文章主要介绍了因为一个Bug,差点损失了100w。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

大家好,我是洋子

最近在做单接口的性能测试比较多,在压测过程发现了一个比较有意思的问题,拿出来和大家分享一下

背景是这样的,最近在搞线上的抽奖活动,压测的对象是一个抽奖接口,主要的逻辑见程序的流程图

因为一个Bug,差点损失了100w

  • 这个抽奖的接口逻辑是先通过检查Redis里面存入的已发放奖品数量

  • 查出已发放奖品数量后,与活动配置当中的奖品库存进行对比

  • 若无库存,此时已发放的奖品数量大于了活动预先配置的奖品库存,那么返回库存为空的信息

  • 若还有库存,在Redis里面新增本次中奖的用户信息,设置Redis过期时间,接着进入后续发奖品的逻辑(写DB,修改发送状态等)

在无并发(同一时间内只有一个用户请求)的场景下,这样处理并没有问题,但是在压测当中,有并发请求的场景下,我发现DB里面写入超出库存数量的记录,换句话说上面通过Redis检查库存,拦截多发奖的逻辑存在Bug,没有正常拦截

通过Review代码,我发现这段检测库存的逻辑Redis 的zCard查询,再zAdd插入,为非原子性操作,zCard与zAdd为串行执行,在并发场景下,zCard可能查出的库存是相同的,如活动配置的库存为6,有10个并发用户同时查询出当前已抽奖品数量为5,因为总库存6>已抽奖品数5,当前还有剩余库存,则正常为这10个并发用户发送奖品

        // 查Redis:当前活动已抽出的奖品数量
        $strKey   = sprintf(PrizeStockKey, PrizeInfo_Id, $strDayTime);
        $intStock = $daoRedis->zCard($strKey);
        // 检查当前库存,是否超过活动预先配置的库存
        if($intStock >= $arrPrizeInfo['stock']) {
            Log::warning("the stock empty");
            return $Output;
        }
        $ret = $daoRedis->zAdd($strKey, $intUserId, $intTime);
        //后续逻辑:写DB发送奖品,此处省略

超发奖品这样的逻辑显然是不符合预期的,那我们该如何修改呢,先zAdd再zCard,行不行呢,答案也是不可以,因为先zAdd可能会导致所有用户均无法进行奖品发送

举个例子,总库存为6,此时并发10个用户进行zAdd,再进行zCard 查询为10,超过总库存,此时程序认为奖品已经发完,无法正常发奖

    // 查Redis:当前活动已抽出的奖品数量
    $strKey   = sprintf(PrizeStockKey, PrizeInfo_Id, $strDayTime);
    //先zAdd,再查询zCard
    $ret = $daoRedis->zAdd($strKey, $intUserId, $intTime);
    $intStock = $daoRedis->zCard($strKey);
    // 检查当前库存,是否超过活动预先配置的库存
    if($intStock > $arrPrizeInfo['stock']) {
        Log::warning("the stock empty");
        return $Output;
    }
    //后续逻辑:写DB发送奖品,此处省略

产生这样的现象,归根结底还是以上逻辑Redis都是非原子操作,我们先了解一下什么是原子操作

原子操作是指在计算机科学中的一种操作方式,它被设计成在执行期间不可中断的单个操作。原子操作要么完全执行,要么完全不执行,不会出现中间或部分执行的情况。原子操作通常用于多线程或并发编程中,用于确保共享资源的一致性和并发访问的正确性

原子操作的特点

  • 原子性:原子操作是不可分割的单个操作,要么全部执行成功,要么全部不执行。没有其他线程能够观察到原子操作的中间状态

  • 独立性:原子操作是独立于其他操作的,不受其他线程的干扰或影响。原子操作的执行不会受到并发环境的影响。

原子操作的作用

可以用于实现对共享数据的互斥访问,以避免竞态条件(race condition)的发生。竞态条件是指多个线程对同一共享资源进行并发访问时可能导致的不确定或不正确的结果

常见的原子操作类型

原子读取(atomic read)、原子写入(atomic write)、原子递增(atomic increment)、原子比较并交换(atomic compare-and-swap)等。这些操作通常由硬件或操作系统提供支持,以确保其执行的原子性。

为了修复此问题,我们将zCard和zAdd改成incrBy即可解决上面的问题,因为Redis的incrBy是原子操作,在并发场景也不会出现因并发访问而导致的数据不一致或竞态条件问题

		//写redis:当前奖品抽出数量+1
		$intStock = $daoRedis->incrBy($strKey, 1);
        // 检查当前库存,是否超过活动预先配置的库存
  		if($intStock >= $arrPrizeInfo['stock']) {
            Log::warning("the stock empty");
            return $Output;
        }
        //后续逻辑:写DB发送奖品,此处省略

结束语

完事后,我仔细想想,还好通过这次压测,发现了这个问题,要是活动当中本来发1w现金,最后发成了100w,那我不直接被开了文章来源地址https://www.toymoban.com/news/detail-453761.html

到了这里,关于因为一个Bug,差点损失了100w的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 新接手一个业务系统,我是这么熟悉的

    接二连三地背锅让小猫的内心受到了前所未有的打击。这也是他职业生涯中的第一次。感兴趣的伙伴们如果想了解一下小猫怎么了,可以看一下“幂等事件”以及“缓存击穿事件”。 这天组长找小猫来到了一间会议室。 “在这么短的时间内发生了这么多的事故,我想也你心

    2024年01月21日
    浏览(41)
  • leetcode热题100. 二叉树的最近公共祖先

    Problem: 236. 二叉树的最近公共祖先 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先

    2024年01月19日
    浏览(42)
  • jdk11环境 提示“因为 accessExternalDTD 属性设置的限制导致不允许 ‘http‘ 访问“bug

    在运行 mybatis 源码的时候,提示一下错误: 由于我的用的是 jdk11 ,解决方案是在 %JAVA_HOEE%conf 目录下,新建一个文件 jaxp.properties ,内容: 就可以解决了

    2024年02月15日
    浏览(44)
  • 我是如何在react中把一个集成了html,css的内容放到页面中的

    首先把html,css内容进行一个变量化,然后利用useState()去初始化一个变量,最后同通过一个标签属性就好了dangerouslySetInnerHTML={变量} 通过这样我把 typora 导出的 html 就可以直接放到上面展示了。

    2024年02月05日
    浏览(57)
  • 分享一个500页面给大家

    先看效果: 再看代码:

    2024年02月06日
    浏览(54)
  • 分享一个403界面给大家

    先看效果图(说明:小鬼影会飘来飘去,长时间停留会有小惊喜,具体大家跑一下就知道): 代码如下: PS:发现我用文字写太生硬了,干的噎嗓子,干脆在代码里加注释了。

    2024年02月06日
    浏览(46)
  • 记录--一行代码修复100vh bug

    你知道奇怪的移动视口错误(也称为100vh bug)吗?或者如何以正确的方式创建全屏块? 什么是移动视口错误? 你是否曾经在网页上创建过全屏元素?只需添加一行 CSS 并不难: 1vh是视口高度的1% ,正是我们所需要的。但当我们在移动设备上测试时,就会出现问题。移动浏览

    2024年02月04日
    浏览(48)
  • 设计一个LRU(最近最少使用)缓存

    约束和假设 我们正在缓存什么? 我们正在缓存Web Query的结果 我们可以假设输入是有效的,还是需要对其验证? 假设输入是有效的 我们可以假设它适应内存吗? 对 编码实现

    2024年01月24日
    浏览(67)
  • 【ChatGPT】开源软件:ChatALL —— 我是 GitHub 榜一!(PS: 其实,小编本地 build run 了一下,就是一个组装 Chat UI ……)

    给第一次听说 ChatALL 的朋友介绍下它吧。很简单,它就是个 能让你同时和 ChatGPT、Bing Chat、Bard、文心一言、讯飞星火、Claude、HuggingChat、Alpaca, Vincuna、MOSS、ChatGLM 聊天的工具,帮你快速找到最靠谱的答案。 Concurrently chat with ChatGPT, Bing Chat, bard, Alpaca, Vincuna, Claude, ChatGLM, MOSS,

    2024年02月07日
    浏览(60)
  • 2023-05-25 最近的一个客户POC的反思

    最近在遇到一个客户的POC的问题,其中经历诸多有意思的事情, 有必要记录一下,以作为后续创业所要避免的地方。 查询SQL中, 存在给查询到的列属性赋值的情况 给属性的赋值的数据类型,和列属性的数据类型,不匹配,比如给整形的属性赋值字符串 在所做的项目中,数据库

    2024年02月07日
    浏览(64)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包