高并发下秒杀系统的设计

文章目录

    • 1 业界通用做法
      • 1.1 压力分摊
      • 1.2 Redis+MySQL
      • 1.3 Inventory Hint
      • 1.4 压力分摊+Redis+MQ
    • 2 Redis + MQ 解决高并发下的秒杀场景
      • 2.1 Redis库存预扣减
        • 2.1.1 lua脚本执行流程:
        • 2.1.2 Lua脚本主要做了几件事:
      • 2.2 MySQL库存扣减
      • 2.3 记录操作流水的原因
    • 3 Inventory Hint 数据库扣减
      • 3.1 使用
      • 3.2 原理介绍
        • 3.2.1 关键优化点:
    • 4 总结

在电商这片红海,秒杀活动无疑是屡试不爽的流量密码、销量利器。然而,在应对其并发请求时,其中的设计门道暗藏玄机。并发量小,数据库单表便能一夫当关,稳保活动无虞;一旦碰上爆款引爆流量,并发呈井喷式增长,单表瞬间独木难支,此时,一套高并发 “抗压” 组合拳必不可少。接下来,咱们就一起深挖业界那些屡建奇功的妙招,直击高并发痛点,重点解析其中两大 “硬核” 方案,助你轻松拿捏秒杀场景的技术难点。

1 业界通用做法

1.1 压力分摊

在应对并发的挑战时,利用分桶策略压力分摊,往往受到很多人的青睐。具体而言,面对单品库存,巧妙地拆分为多组,大大缓解抢购高峰的压力。但这背后藏着诸多棘手难题,如何精准地将库存均匀分配至各桶,杜绝分配不均;怎样有效处理拆分产生的库存碎片,避免少卖;分桶间的调度规则如何制定,确保协同高效;还有,业务量起伏不定,又该如何实现分桶动态扩容以灵活适配。这些问题也需要我们进行考虑设计。

1.2 Redis+MySQL

在秒杀系统的架构设计蓝图中,Redis 与 MQ 的组合堪称一对 “黄金搭档”。Redis 凭借其卓越的高性能读写特性以及原子操作能力可以直面秒杀活动带来的汹涌并发流量,筑起防止超卖的坚固防线。而 MQ 通过流量削峰策略,将瞬间爆发的海量数据请求进行缓冲与分流,有效减缓后端数据存储环节的压力,确保整个系统节奏平稳。

不过,不少人心中会泛起疑惑:既然 Redis 如此强大,为何不索性全程在 Redis 中完成操作,待秒杀尘埃落定,再一次性将数据同步至数据库呢?理论上看似可行,但若置于真实复杂的业务场景之中,便会破绽百出。要知道,用户成功抢购后,绝非万事大吉,后续一系列连贯操作随即而来,诸如急切地查看抢购结果,或是迅速进入支付流程等,这些都需要实时、精准的数据交互与支持。

当然,采用 Redis + MQ 这套方案并非一劳永逸,其中数据一致性问题犹如潜藏暗处的礁石,不容小觑。不过别担心,接下来我们就将针对这一方案进行详细的介绍。

1.3 Inventory Hint

大家普遍知晓高并发场景下需精心构建复杂架构应对挑战。然而,有一些公司却看似 “剑走偏锋”,即便面临着不容小觑的并发压力,依旧选择直接对数据库进行操作,并未引入那些令人眼花缭乱的高并发设计体系,这难免让人满腹狐疑。

实则不然,这些公司背后有着强大的技术支撑 —— 他们选用的是阿里的 RDS 云数据库。这款数据库绝非等闲之辈,它依托阿里的强大技术底蕴,内置了先进的 Inventory Hint 技术。凭借该技术对数据库的精准优化,使得这些公司即便在高并发的枪林弹雨中,也能让数据库稳健运行,轻松应对海量数据的读写请求。下面,咱们也会深入其中,着重揭开这项神奇技术的神秘面纱,探寻它究竟是如何赋能数据库、化解高并发难题的。

1.4 压力分摊+Redis+MQ

面对数百万 QPS 的高压冲击,多种技术方案强强联手,才能站稳脚跟。SQL 合并执行批量处理请求,削减数据库负荷。缓存阵营更是齐发力,分布式缓存广纳热点,本地缓存就近响应,近端缓存将分布式缓存与服务器 “联姻”,实现超高速读取。分桶策略巧妙分流,开辟流量 “绿色通道”。各方案各司其职,分担并发压力,聚合为强大力量,确保大促活动平稳运行。

2 Redis + MQ 解决高并发下的秒杀场景

2.1 Redis库存预扣减

上面我们方案二已经介绍过该方案的整体技术架构,接下来我们进行详细的剖析。该方案我们主要是通过RedisLua脚本进行库存的扣减,这样可以保证扣减过程中的原子性和高效性。示例代码如下:

 /** KEYS[1] --商品id* KEYS[2] --用户id uid* ARGV[1] --扣减数量* ARGV[2] --用户提交的  token*/
String luaScript = "redis.replicate_commands()\n" +//防止用户是否重复提交,利用 token实现的,每次提交前会生成一个token,token更新前才可继续提交"if redis.call('hexists', KEYS[2], ARGV[2]) == 1 then\n" +//抛出用户重复提交的异常"return redis.error_reply('repeat submit')\n" +"end \n" +//商品id"local product_id = KEYS[1] \n" +//获取商品id对应的库存"local stock = redis.call('get', KEYS[1]) \n" +//判断库存是否充足"if tonumber(stock) < tonumber(ARGV[1]) then \n" +//购买的数量"return redis.error_reply('stock is not enough') \n" +"end \n" +"local remaining_stock = tonumber(stock) - tonumber(ARGV[1]) \n" +//更新库存"redis.call('set', KEYS[1], tostring(remaining_stock)) \n" +//获取但当前系统的时间 返回一个数组,包含2个元素 第一个元素是当前的秒数,第2个是当前这一秒已经流逝过的微秒数"local time = redis.call('time') \n" +//当前时间戳 ms"local currentTimeMillis = (time[1] * 1000) + math.floor(time[2] / 1000) \n" +//存如一条流水 {"change":"1","action":"扣减库存","from":"100","token":"token","timestamp":1735293810009,"to":99,"product":"product_id_01"}"redis.call('hset', KEYS[2], ARGV[2], \n" +"cjson.encode({action = '扣减库存', product=product_id  ,from = stock, to = remaining_stock, change = ARGV[1], token = ARGV[2], timestamp = currentTimeMillis})\n" +") \n" +"return remaining_stock";
2.1.1 lua脚本执行流程:

涉及到的Redis数据结构以及对应存储内容:

Hash 数据结构

外层key内层keyvalue
KEYS[2]ARGV[2]json数据格式
uidtoken流水记录

String 数据结构
keyvalue
KEYS[1]stock
商品id库存

2.1.2 Lua脚本主要做了几件事:

1)防重提交

在秒杀活动中用户为了能够抢到想要的商品,会进行疯狂的点击,为了防止用户重复点击提交,往往需要做一些幂等性的判断。用户在每次点击提交按钮前后端会新生成一个token,提交时携带上,后端针对token判断是否已经存在,避免重复下单。

2)库存扣减

判断购买的数据是否大于库存,如果是的话,直接返回库存。如果不是,进行库存扣减,更新Redis库存。

3)记录交易流水

很多人想不明白为啥要进行交易流水的记录,其实是为了一致性考虑的。可以依据这条流水记录去订单表中去进行查询,如果查询不到,说明订单表中未能成功生成订单,可能需要人工介入进行处理。

2.2 MySQL库存扣减

在进行数据库进行库存扣减的时候,我们是通过RocketMQ的事务消息实现的,这样做的目的是为了保证数据库库存可以扣减成功,如果数据库库存扣减失败的话,也会带来少卖问题。具体分为以下几步:

1)发送RocketMQ半消息,此时消息并不能直接消费,需要检查本地事务的执行结果。

2) 检查本地事务我们是判断Redis的Lua脚本是否执行成功,如果执行成功,则返回COMMIT给RocketMQ,如果失败,则ROLL_BACK消息。

3)RocketMQ为了防止收不到对应的本地事务执行结果会有消息回查机制,我们在消息回查中主要判断是否有对应的流水,如果存在的话,说明可以提交。

4)消费消息,进行数据库库存的扣减,同时记录对应操作流水。消费时为了保证一致性我们借助的是RocketMQ的消息重试机制,所以此处我们给MQ返回消费ACK时一定要保证我们的数据已经成功落库,否则不能随意返回。

2.3 记录操作流水的原因

我们在进行完库存扣减动作之后,对应的是下单操作,为了保证下单和库存的一致性,我们可以用定时对账机制来核对库存流水和订单表中数据是否一致。当然也有其他一致性保证方案,比如SeataTCC等,可以根据具体的业务场景选择。

3 Inventory Hint 数据库扣减

很多公司直接利用阿里云的数据库就完成了秒杀的功能,也就是我们上面介绍的方案三。上文已经提到过其底层是依赖Inventory Hint技术实现的,接下来我们介绍下Inventory Hint技术的使用以及实现原理。

3.1 使用

Inventory Hint的使用比较简单,只需要在对应的语句上加上特殊的hint语句就行了。具体可以参考阿里云文档

3.2 原理介绍

其实高并发下库存的扣减动作最后瓶颈落在了数据库单行的热更新上,Inventory Hint技术就是对热更新做了相应的优化。

当用Inventory Hint技术的hint语句标记一个SQL后,就相当于告诉MySQL内核这可能是一行热更新记录。于是,MySQL内核层就会自动识别带此类标记的更新操作,在一定的时间间隔内,将收集到的更新操作按照主键或者唯一键进行分组,这样更新相同行的操作就会被分到同一组中。

为了进一步提升性能,在实现上,使用两个执行单元。当第一个执行单元收集完毕准备提交时,第二个执行单元立即开始收集更新操作;当第二个执行单元收集完毕准备提交时,第一个执行单元已经提交完毕并开始收集新批的更新操作,两个单元不断切换,并行执行。

3.2.1 关键优化点:

1)减少行级锁的申请等待

同组更新同一记录时依 SQL 提交顺序排队,Leader 率先尝试拿目标行锁,成功即操作,Follower 拿锁前先确认,若 Leader 已得锁,Follower 可直接获取,大幅削减行级锁申请的阻塞时长。

2)减少B+树的索引遍历操作

MySQL 依 B + 索引管数据,查询常需遍历索引寻目标行,表大层级多则耗时。对热点行更新分组后,首条 SQL 定位数据存 Row Cache 并修改,后续操作直取缓存改,速减索引遍历耗时。

3)减少事务提交次数

常规多条 update 语句对应多条事务,各需单独提交。分组、排队结合组提交后,一组并发操作完,一次组提交搞定,大大精简提交次数。

4 总结

在电商秒杀的舞台上,技术方案需因 “量” 制宜。

若并发量轻柔、数据量微小,数据库单表辅以加锁策略即可,确保业务有序运转,成本低且易维护。

当业务进阶,数据量攀升、并发趋高,Redis 与 MQ 携手登场。Redis 防止超卖;MQ 缓冲请求,削峰填谷,平衡系统负载,二者联动保障高效稳定。
一旦数据海量、并发如潮,Redis + MQ 稍显吃力,压力分摊策略必须就位,如同给系统装上多重缓冲,分散高流量冲击。

数据量达巅峰时,Redis + MQ + 压力分摊还需 Inventory Hint 技术助力,深挖数据库潜能。

总之,业务多样,技术无万全之策,唯有贴合场景精挑细选、巧妙组合,才能在秒杀中稳操胜券。

关于作者
赵培龙 采货侠JAVA开发工程师

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/21278.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

双重差分学习笔记

双重差分适用的研究场景&#xff1a; 研究某项政策或者冲击造成的影响 例如&#xff0c;某某小学在2024.12.12日颁布了小红花激励措施&#xff0c;我们要研究这项措施对学生成绩的影响&#xff0c;此时&#xff0c;就可以使用双重差分模型。 双重差分适用的数据类型&#xf…

深入理解 C++17 中的 std::atomic<T>::is_always_lock_free

文章目录 原子操作与锁无关性&#xff08;Lock-Free&#xff09;锁无关性&#xff08;Lock-Free&#xff09;无锁&#xff08;Lock-Free&#xff09;与无阻塞&#xff08;Wait-Free&#xff09; std::atomic<T>::is_always_lock_free 是什么&#xff1f;truefalse与 is_l…

VSCode 中 Git 添加了多个远端,如何设置默认远端

VSCode 中 Git 添加了多个远端&#xff0c;如何设置默认远端 查看分支&#xff1a;设置默认远端手动指定远端 查看分支&#xff1a; * 表示当前默认远端 git branch -vv* master a1b2c3d [origin/main] Fix typo dev d4e5f6g [upstream/dev] Add feature设置默认远端 将本…

一文讲清 AIO BIO NIO的区别

引言 在 Java 编程中&#xff0c;BIO&#xff08;Blocking I/O&#xff09;、NIO&#xff08;Non-blocking I/O&#xff09;和 AIO&#xff08;Asynchronous I/O&#xff09;是三种不同的 I/O 模型&#xff0c;它们在处理输入输出操作时有着不同的机制和特点&#xff0c;但是市…

使用(xshell+xftp)将前端项目部署到服务器

一.以vue项目为例 将项目打包生成dist文件 二.下载载安装xshell和xftp 下载地址&#xff1a;家庭/学校免费 - NetSarang Website 三.连接服务器 在xshell新建会话&#xff08;需要用到服务器、用户名、密码、端口号&#xff09;正确输入后连接到服务器 使用命令连接&#x…

硬件岗位是否适合你?

在当今科技飞速发展的时代,硬件行业作为技术创新的基石,始终扮演着至关重要的角色。无论是智能手机、自动驾驶汽车,还是人工智能服务器,硬件都是这些技术的核心支撑。然而,硬件岗位是否适合你?作为一名硬件专家,我将从多个角度为你分析,帮助你判断自己是否适合从事硬件…

Linux基本指令(二)

文章目录 基本指令echocat&#xff08;输入重定向&#xff09;history日志moretail和headmv&#xff08;重要&#xff09;时间相关的指令查找的命令 知识点Linux下一切皆文件为什么计算机关机了&#xff0c;开机后还能准确地记录时间呢&#xff1f; 基本指令 echo 1. echo&…

【Blender】二、建模篇--05,阵列修改器与晶格形变

阵列修改器是bender里面一个比较常用的修改器,所以我们单独开口来讲,我们会先从几片树叶出发,然后我们用阵列修改器把这几片树叶变成这样的造型和这样的造型。这两个造型分别就代表着阵列修改器最常用的两种偏移方法,我们现在就开始我们先来做几个树叶。 1.树叶建模 首先…

fpga助教面试题

第一题 module sfp_pwm( input wire clk, //clk is 200M input wire rst_n, input wire clk_10M_i, input wire PPS_i, output reg pwm ) reg [6:0] cunt ;always (posedge clk ) beginif(!rst_n)cunt<0;else if(cunt19) //200M是10M的20倍cunt<0;elsecunt<cunt1;…

SpringAI系列 - ToolCalling篇(二) - 如何设置应用侧工具参数ToolContext(有坑)

目录 一、引言二、集成ToolContext示例步骤1: 在`@Tool`标注的工具方法中集成`ToolConext`参数步骤2:`ChatClient`运行时动态设置`ToolContext`参数三、填坑一、引言 在使用AI大模型的工具调用机制时,工具参数都是由大模型解析用户输入上下文获取的,由大模型提供参数给本地…

Jest单元测试

由于格式和图片解析问题&#xff0c;可前往 阅读原文 前端自动化测试在提高代码质量、减少错误、提高团队协作和加速交付流程方面发挥着重要作用。它是现代软件开发中不可或缺的一部分&#xff0c;可以帮助开发团队构建可靠、高质量的应用程序 单元测试&#xff08;Unit Testi…

pyside6学习专栏(二):程序图像资源的加载方式

pyside6中的QLabel控件可以加载图像和gif动画&#xff0c;可以直接从外部文件加载&#xff0c;也可以从QRC类型的文件(实际是一脚本文件)经编绎生成对应的资源.PY模块文件(就是将qrc文本中指定的资源文件的16制内容写入.py文件)来使用&#xff0c;本文对两种方式作了一简单的示…

Nginx--日志(介绍、配置、日志轮转)

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、Nginx日志介绍 nginx 有一个非常灵活的日志记录模式&#xff0c;每个级别的配置可以有各自独立的访问日志, 所需日志模块 ngx_http_log_module 的…

cs106x-lecture12(Autumn 2017)-SPL实现

打卡cs106x(Autumn 2017)-lecture12 (以下皆使用SPL实现&#xff0c;非STL库&#xff0c;后续课程结束会使用STL实现) travel Write a recursive function named travel that accepts integers x and y as parameters and uses recursive backtracking to print all solution…

了解随机振动疲劳分析中 Ansys nCode DesignLife 的平均应力校正

概括 在本篇博文中&#xff0c;我们将探讨 Ansys nCode 在分析随机振动引起的疲劳方面的重要性。我们将了解 nCode 如何帮助校正平均应力并预测受随机振动影响的结构的寿命和耐久性。 什么是疲劳寿命以及了解平均应力对疲劳寿命的影响 疲劳寿命是指结构在重复载荷作用下发生…

ubuntu20.04重启后不显示共享文件夹

ubuntu20.04重启后不显示共享文件夹 主要参见这两篇博客 Ubuntu重启后不显示共享文件夹_ubuntu 20.04 共享目录无法使用-CSDN博客 ubuntu22.04 配置共享文件夹 找不到/mnt/hgfs_ubuntu安装tools 后mnt文件夹在哪-CSDN博客 重启Ubuntu20.04后&#xff0c;发现共享文件夹进不去…

Rust编程语言入门教程 (六)变量与可变性

Rust 系列 &#x1f380;Rust编程语言入门教程&#xff08;一&#xff09;安装Rust&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;二&#xff09;hello_world&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;三&#xff09; Hello Cargo&#x1f…

nvm安装、管理node多版本以及配置环境变量【保姆级教程】

引言 不同的项目运行时可能需要不同的node版本才可以运行&#xff0c;由于来回进行卸载不同版本的node比较麻烦&#xff1b;所以需要使用node工程多版本管理。 本人在配置时&#xff0c;通过网络搜索教程&#xff0c;由于文章时间过老&#xff0c;或者文章的互相拷贝导致配置时…

本地部署DeepSeek大模型

环境&#xff1a;nuc工控机器 x86架构 ubuntu20.04 1、浏览器打开Download Ollama on Linux&#xff0c;复制命令。 2.打开终端&#xff0c;输入命令。 curl -fsSL https://ollama.com/install.sh | sh 等待安装&#xff0c;安装完成后&#xff0c;终端输入 ollama&#xff…

深度解析应用层协议-----HTTP与MQTT(涵盖Paho库)

HTTP协议概述 1.1 HTTP的基本概念 HTTP是一种应用层协议&#xff0c;使用TCP作为传输层协议&#xff0c;默认端口是80&#xff0c;基于请求和响应的方式&#xff0c;即客户端发起请求&#xff0c;服务器响应请求并返回数据&#xff08;HTML&#xff0c;JSON&#xff09;。在H…