在秒杀场景中,用户点击“抢购”后,后端需要通过异步处理应对高并发(避免请求阻塞),同时需通过实时回调机制将最终结果(成功/失败)推送给客户端并展示。核心方案是:“前端发起请求→后端生成唯一标识并异步处理→客户端通过轮询/长连接监听结果→后端处理完成后更新状态→客户端获取结果并展示”

一、整体流程设计

秒杀场景的回调交互流程需满足:高并发下的请求不阻塞、结果通知及时、前端体验流畅,具体步骤如下:

  1. 用户触发抢购:前端点击按钮,发送抢购请求(携带用户ID、商品ID)。
  2. 后端接收请求
    • 生成唯一的请求ID(如UUID),作为本次秒杀的标识。
    • 立即返回“请求已受理”给前端(避免用户等待),同时返回请求ID。
    • 将秒杀任务(商品ID、用户ID、请求ID)放入消息队列(如RabbitMQ/Kafka),异步处理(库存检查、下单、扣减等)。
  3. 异步处理秒杀逻辑:后端消费者从消息队列取任务,执行秒杀(判断库存、防重复下单、创建订单等),处理完成后将结果(成功/失败原因)存入缓存(如Redis),关联请求ID。
  4. 客户端监听结果:前端拿到请求ID后,通过轮询/长轮询/WebSocket持续查询该ID的处理结果。
  5. 展示结果:一旦缓存中该请求ID的结果就绪,前端立即更新页面(如“抢购成功!订单号XXX”或“手慢了,商品已抢完”)。

整个流程涉及客户端(浏览器)后端API消息队列业务Worker的协同工作。

用户/浏览器客户端页面后端API网关消息队列业务Worker回调服务/WS连接数据库/缓存点击“立即抢购”发送抢购请求 (req_id, user_id)同步请求预检过滤(风控、是否已抢过)发送异步任务消息 (req_id, user_id, ...)202 Accepted, “请求已接受,处理中”显示“排队中,请稍候...”异步处理拉取任务消息执行核心事务:读库存、校验、扣库存、创建订单更新处理状态(成功/失败)回调通知推送处理结果 (req_id, result)通过WS/LP/Push将结果推送到浏览器页面更新显示最终结果:“成功”或“失败”用户/浏览器客户端页面后端API网关消息队列业务Worker回调服务/WS连接数据库/缓存

二、核心技术方案与代码示例

以下以 Spring Boot(后端)+ Vue(前端) 为例,实现完整流程:

1. 后端实现(异步处理+结果缓存)
(1)定义请求ID与结果存储(Redis)

用Redis存储秒杀请求的状态,key为seckill:result:{请求ID},value为JSON格式的结果(如{"success":true, "orderId":"12345", "msg":""})。

(2)接收抢购请求,生成任务并返回请求ID
// 秒杀Controller
@RestController
@RequestMapping("/seckill")
public class SeckillController {@Autowiredprivate RabbitTemplate rabbitTemplate;@Autowiredprivate StringRedisTemplate redisTemplate;// 用户点击抢购时调用@PostMapping("/submit")public Result submitSeckill(@RequestBody SeckillRequest request) {// 1. 生成唯一请求IDString requestId = UUID.randomUUID().toString();// 2. 初始状态:处理中redisTemplate.opsForValue().set("seckill:result:" + requestId,"{\"success\":false, \"status\":\"processing\"}",30, TimeUnit.MINUTES // 过期时间:30分钟);// 3. 发送任务到消息队列(异步处理)SeckillTask task = new SeckillTask();task.setRequestId(requestId);task.setUserId(request.getUserId());task.setGoodsId(request.getGoodsId());rabbitTemplate.convertAndSend("seckill-exchange", "seckill.key", task);// 4. 立即返回,告知客户端请求IDreturn Result.success("请求已受理,请等待结果", requestId);}// 客户端查询结果的接口(供轮询/长轮询调用)@GetMapping("/result/{requestId}")public Result getResult(@PathVariable String requestId) {String resultJson = redisTemplate.opsForValue().get("seckill:result:" + requestId);if (resultJson == null) {return Result.fail("请求ID不存在");}return Result.success(JSON.parseObject(resultJson));}
}// 秒杀请求参数
@Data
class SeckillRequest {private Long userId;private Long goodsId;
}// 消息队列任务
@Data
class SeckillTask implements Serializable {private String requestId;private Long userId;private Long goodsId;
}
(3)异步处理秒杀逻辑(消息队列消费者)
// 秒杀任务消费者
@Component
public class SeckillConsumer {@Autowiredprivate SeckillService seckillService;@Autowiredprivate StringRedisTemplate redisTemplate;@RabbitListener(queues = "seckill-queue")public void handleSeckill(SeckillTask task) {String requestId = task.getRequestId();try {// 执行秒杀逻辑(检查库存、创建订单等)SeckillResult result = seckillService.doSeckill(task.getUserId(), task.getGoodsId());// 3. 更新Redis结果String resultJson = JSON.toJSONString(result);redisTemplate.opsForValue().set("seckill:result:" + requestId,resultJson,30, TimeUnit.MINUTES);} catch (Exception e) {// 处理异常(如库存不足、重复下单)String errorJson = JSON.toJSONString(new SeckillResult(false, null, e.getMessage()));redisTemplate.opsForValue().set("seckill:result:" + requestId,errorJson,30, TimeUnit.MINUTES);}}
}// 秒杀结果封装
@Data
class SeckillResult {private boolean success; // 是否成功private String orderId; // 订单号(成功时返回)private String msg; // 提示信息(失败时返回)public SeckillResult(boolean success, String orderId, String msg) {this.success = success;this.orderId = orderId;this.msg = msg;}
}
2. 前端实现(轮询监听+结果展示)

前端拿到请求ID后,通过短轮询(适合中小流量)或WebSocket(适合高并发实时性要求高的场景)监听结果,这里以短轮询为例:

<template><div class="seckill-page"><button @click="handleSeckill" v-if="!isProcessing">立即抢购</button><div v-if="isProcessing"><div>正在抢购中,请稍候...</div><div class="loading"></div></div><div v-if="showResult" class="result"><div v-if="result.success">✅ 抢购成功!订单号:{{ result.orderId }}</div><div v-else>❌ 抢购失败:{{ result.msg }}</div></div></div>
</template><script>
import axios from 'axios';export default {data() {return {isProcessing: false, // 是否正在处理showResult: false, // 是否显示结果result: {}, // 秒杀结果pollTimer: null // 轮询定时器};},methods: {// 点击抢购按钮async handleSeckill() {this.isProcessing = true;this.showResult = false;try {// 1. 发送抢购请求,获取requestIdconst res = await axios.post('/seckill/submit', {userId: this.$store.state.userId,goodsId: 1001 // 商品ID(示例)});const requestId = res.data.data;// 2. 启动轮询,查询结果this.startPolling(requestId);} catch (e) {this.isProcessing = false;alert('请求失败,请重试');}},// 轮询查询结果(每1-2秒一次,避免频繁请求)startPolling(requestId) {this.pollTimer = setInterval(async () => {try {const res = await axios.get(`/seckill/result/${requestId}`);const result = res.data.data;// 3. 判断是否处理完成(状态不是processing)if (result.status !== 'processing') {clearInterval(this.pollTimer);this.isProcessing = false;this.showResult = true;this.result = result;}} catch (e) {console.error('查询结果失败', e);}}, 1500); // 1.5秒轮询一次}},beforeDestroy() {// 组件销毁时清除定时器if (this.pollTimer) {clearInterval(this.pollTimer);}}
};
</script>

三、优化方案(应对高并发)

  1. 用长轮询替代短轮询
    短轮询会频繁发送请求,增加服务器压力。长轮询的逻辑是:客户端发起请求后,服务器hold住连接(30秒内),若期间结果就绪则立即返回;若超时未就绪,客户端再发起新请求。减少无效请求次数。

    // 长轮询版本的getResult接口
    @GetMapping("/result/{requestId}")
    public DeferredResult<Result> getResultLongPolling(@PathVariable String requestId) {DeferredResult<Result> deferredResult = new DeferredResult<>(30000L); // 30秒超时// 1. 检查当前结果是否就绪String resultJson = redisTemplate.opsForValue().get("seckill:result:" + requestId);if (resultJson != null && !resultJson.contains("processing")) {deferredResult.setResult(Result.success(JSON.parseObject(resultJson)));return deferredResult;}// 2. 未就绪,注册监听器(当结果更新时触发)RedisMessageListenerContainer container = new RedisMessageListenerContainer();// 监听Redis的key变化(需配置Redis监听)container.addMessageListener((message, pattern) -> {String updatedJson = message.toString();deferredResult.setResult(Result.success(JSON.parseObject(updatedJson)));}, new PatternTopic("seckill:result:" + requestId));// 3. 超时处理deferredResult.onTimeout(() -> {deferredResult.setResult(Result.success("{\"status\":\"processing\"}"));});return deferredResult;
    }
    
  2. WebSocket实时推送
    对于高并发场景(如百万级用户),可使用WebSocket建立长连接,后端处理完成后主动推送结果给客户端(无需客户端轮询)。
    示例技术栈:后端用Spring WebSocket,前端用WebSocket API,通过请求ID关联用户连接。

  3. 限流与降级
    秒杀请求入口需限流(如用Redis+Lua脚本控制每秒请求量),避免后端被冲垮;同时,轮询接口也需限流(如限制单用户每秒最多2次请求)。

四、核心要点总结

  1. 异步处理:通过消息队列 decouple 秒杀请求的接收与处理,避免阻塞。
  2. 结果缓存:用Redis存储请求状态,支持高并发查询。
  3. 回调机制:根据流量规模选择轮询/长轮询/WebSocket,确保结果及时通知。
  4. 用户体验:前端需显示“处理中”状态,避免用户重复点击;结果页清晰反馈成功/失败原因。

通过这套方案,既能应对秒杀的高并发压力,又能通过回调机制实时将结果反馈给用户,平衡了性能与体验。

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

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

相关文章

php apache无法接收到Authorization header

Apache 默认不传递 Authorization头到后端环境&#xff08;如 PHP&#xff09;。其表现是&#xff1a;print_r($_SERVER)时&#xff0c; 没有 [Authorization] &#xff1a;Array ([Accept-Language] > zh,en;q0.9,zh-CN;q0.8,en-US;q0.7[Accept-Encoding] > gzip, defla…

当我们想用GPU(nlp模型篇)

在个人设备上“把 GPU 真正用起来”做 NLP&#xff0c;分五步&#xff1a;准备 → 安装 → 验证 → 训练/推理 → 踩坑排查。下面每一步都给出可复制命令和常见错误。 ────────────────── 1. 硬件准备 • 一张 NVIDIA GPU&#xff0c;算力 ≥ 6.1&#xff08…

CryptSIPVerifyIndirectData函数分析

可以使用此函数从SIP接口对应的文件中提取签名信息 CryptSIPVerifyIndirectData&#xff1a;将当前文件的哈希结果做为“指纹”&#xff0c;并与从CryptSIPGetSignedDataMsg中提取的签名信息进行比较。 如果哈希结果相同&#xff0c;则意味着当前文件与之前签名的文件相同&…

20250823解决荣品RD-RK3588-MID开发板在充电的时候大概每10s屏幕会像水波纹闪烁一下

20250823解决荣品RD-RK3588-MID开发板在充电的时候大概每10s屏幕会像水波纹闪烁一下 2025/8/23 17:58【结论】&#xff1a;使用直流电源供电&#xff0c;给电池【快速】充电&#xff0c;但是直流电源的电压稳定&#xff0c;电流抖动导致的&#xff01;那个是2.4G 已经知道了我司…

CANN安装

前提条件 请参考本文档正确安装和使用CANN软件,不建议将CANN安装在共享磁盘后,通过挂载的方式使用CANN,因为CANN对文件系统有文件锁的依赖,部分共享存储不支持文件锁,可能导致任务拉起失败。 root用户和非root用户安装CANN软件包的步骤一致,当前示例步骤以非root用户为例…

docker的基础配置

目录 数据卷 数据卷容器 端口映射与容器互联 互联机制实现便捷互访(基于容器搭建论坛) 数据卷 1.创建数据卷 [rootopenEuler-1 /]# docker volume create test test [rootopenEuler-1 /]# docker volume ls DRIVER VOLUME NAME local test [ro…

VSCode Import Cost:5 分钟学会依赖瘦身

一句话作用&#xff1a;在代码里 import 时&#xff0c;实时显示包大小&#xff0c;帮你一眼揪出体积炸弹。1️⃣ 30 秒安装 & 启动 打开 VSCode → 扩展商店搜索 Import Cost → 安装重启 VSCode&#xff0c;立即生效&#xff0c;零配置。2️⃣ 使用方式&#xff08;开箱即…

TCP/UDP详解(一)

UDP报文源端口16bit 目的端口16bit校验和checksum16bit 总长度16bit--------------------------------------------------------------------------------------------------------------------------源目端口用于标识应用层协议&#xff0c;分为知名端口&#x…

数据库优化提速(一)之进销存库存管理—仙盟创梦IDE

从存储过程到通用 SQL&#xff1a;进销存系统的数据操作优化在进销存系统的开发与维护中&#xff0c;数据库查询语句的编写方式对系统的性能、兼容性和可维护性有着深远影响。本文将围绕给定的三段 SQL 代码展开&#xff0c;深入探讨将存储过程转换为通用 SQL 在进销存场景下的…

Redis面试精讲 Day 28:Redis云原生部署与Kubernetes集成

【Redis面试精讲 Day 28】Redis云原生部署与Kubernetes集成 在当今微服务与容器化浪潮中&#xff0c;Redis作为高性能缓存和消息中间件&#xff0c;已从单机部署逐步演进为云原生环境下的核心组件。Day 28 聚焦“Redis云原生部署与Kubernetes集成”&#xff0c;深入解析如何在…

leetcode刷题记录03——top100题里的6道简单+1道中等题

leetcode刷题记录03——top100题里的6道简单1道中等题上一篇博客&#xff1a; leetcode刷题记录01——top100题里的7道简单题 leetcode刷题记录02——top100题里的7道简单题 有效的括号 看懂需要用栈了&#xff0c;但是不知道怎么去写&#xff0c;看了题解mark下正确答案。 cla…

求单位球内满足边界条件 u = z³ 的调和函数

问题 6&#xff1a;在区域 {x2y2z2≤1}\{x^{2}y^{2}z^{2}\leq 1\}{x2y2z2≤1} 内找到一个调和函数 uuu&#xff0c;使得在边界 x2y2z21x^{2}y^{2}z^{2}1x2y2z21 上&#xff0c;uuu 等于 gz3gz^{3}gz3。 提示&#xff1a;根据第8.1节&#xff0c;解必须是一个三次调和多项式&…

AAA 服务器与 RADIUS 协议笔记

一、AAA 服务器概述1. 核心定义AAA 是认证&#xff08;Authentication&#xff09;、授权&#xff08;Authorization&#xff09;和计费&#xff08;Accounting&#xff09; 的简称&#xff0c;是网络安全领域中实现访问控制的核心安全管理机制&#xff0c;通过整合三种服务确保…

Vue3源码reactivity响应式篇之数组代理的方法

概览 vue3中对于普通的代理包含对象和数组两类&#xff0c;对于数组的方法是重写了许多方法&#xff0c;具体实现参见packages\reactivity\src\arrayInstrumentations.ts arrayInstrumentations实际上就是一个对象&#xff0c;对象的属性就是数组的方法&#xff0c;属性值就是重…

如何玩转K8s:从入门到实战

一、K8S介绍及部署 1 应用的部署方式演变 部署应用程序的方式上&#xff0c;主要经历了三个阶段&#xff1a; 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其它技术的参与 缺点&#xff1a;不能为应用…

综合测验:配置主dns,dhcp,虚拟主机,nfs文件共享等

综合实验(所有设备关闭防火墙和selinux)在appsrv上部署主dns&#xff0c;为example.com提供域名解析 安装bind bind-chroot rootappsrv ~]# yum install bind bind-chroot -y编辑主配置文件&#xff0c;全局配置文件&#xff0c;正向解析文件 [rootappsrv ~]# vim /etc/named.c…

MySQL数据库管理与索引优化全攻略

一、表管理1.建库语法&#xff1a;create database if not exists 数据库名;命名规则&#xff1a;仅可使用数字、字母、下划线、不能纯数字&#xff1b;区分字母大小写&#xff1b;具有唯一性&#xff1b;不可使用MySQL命令或特殊字符。相关命令&#xff1a;show databases; …

基于大模型构建 Java 混淆的方式方法(从入门到精通 · 含开源实践)

1. 目标与威胁模型:你到底想防什么? 把“混淆”当作成本叠加器:让逆向者付出更多时间与技能,而不影响用户体验与可维护性。可用 Collberg 等提出的四指标来权衡:有效性/韧性/隐蔽性/成本(potency/resilience/stealth/cost)。近年的研究也在重审这些评估方法,建议结合可…

RabbitMQ面试精讲 Day 28:Docker与Kubernetes部署实践

【RabbitMQ面试精讲 Day 28】Docker与Kubernetes部署实践 在微服务架构日益普及的今天&#xff0c;消息中间件RabbitMQ已成为解耦系统、异步通信的核心组件。随着云原生技术的成熟&#xff0c;如何在Docker与Kubernetes&#xff08;K8s&#xff09;环境中高效、高可用地部署Ra…

神经网络和深度学习介绍

目录 1.深度学习的介绍 2.神经网络的构造 ①神经元结构 ②神经网络组成 ③权重核心性 3.神经网络的本质 4.感知器 单层感知器的局限性&#xff1a; 5.多层感知器 多层感知器的优势&#xff1a; 6.偏置 7.神经网络的设计 8.损失函数 常用的损失函数&#xff1a; 9…