Go实战:高并发秒杀系统设计
Go实战:高并发秒杀系统设计摘要: 本篇讲解Go秒杀系统设计实现Redis库存预扣减配合Lua脚本保证原子操作本地缓存挡读流量加异步下单削峰令牌桶限流防刷分享超卖问题排查与修复的踩坑经验对比Redis预扣减、数据库乐观锁、Redis Cluster三种库存方案。开篇故事去年618我们做了一次手机秒杀2000台现货预约人数8万。开抢前压测模拟8万并发结果库存还没扣完订单服务就挂了。数据库连接池打满Redis缓存击穿整个购物流程瘫痪40分钟才恢复。事后排查发现核心问题出在所有请求直接打到数据库做库存扣减8万并发把MySQL的行锁排队拉长到几十秒。更严重的是并发扣减出现超卖明明2000台库存最后下了2053单多卖53台要真金白银赔给用户。这次我把秒杀系统的核心设计写清楚重点讲Redis库存预扣减怎么用Lua脚本保证原子性以及超卖问题怎么排查和修复。一、Redis库存预扣减秒杀的核心矛盾是库存有限并发无限。每个请求都去数据库扣库存数据库必挂。思路是Redis先挡住绝大部分请求只有真正抢到库存的请求才落到数据库。Redis预扣减的关键是检查库存和扣减库存必须原子完成。用普通的GET再SET两个操作之间会有间隙高并发下依然超卖。Lua脚本把多个Redis命令打包成原子执行中间不会被其他命令插入。packageseckillimport(contexterrorsfmttimegithub.com/redis/go-redis/v9)// StockService 库存预扣减服务// Redis存储库存Lua脚本保证扣减原子性typeStockServicestruct{client*redis.Client// 扣减库存的Lua脚本检查扣减原子执行deductScript*redis.Script}// NewStockService 创建库存服务// 预加载Lua脚本到Redis减少每次传输开销funcNewStockService(client*redis.Client)*StockService{returnStockService{client:client,// Lua脚本: 先检查库存是否足够足够则扣减// KEYS[1] 库存key// ARGV[1] 扣减数量(默认1)deductScript:redis.NewScript( local key KEYS[1] local num tonumber(ARGV[1]) -- 获取当前库存 local stock tonumber(redis.call(GET, key) or 0) if stock nil then return -1 -- 商品不存在 end if stock num then return 0 -- 库存不足 end -- 扣减库存返回剩余数量 redis.call(DECRBY, key, num) return 1 -- 扣减成功 ),}}// Deduct 预扣减库存// 返回值: 1成功, 0库存不足, -1商品不存在func(s*StockService)Deduct(ctx context.Context,itemIDstring)(int64,error){key:fmt.Sprintf(seckill:stock:%s,itemID)// 执行Lua脚本原子操作result,err:s.deductScript.Run(ctx,s.client,[]string{key},1).Int64()iferr!nil{return0,fmt.Errorf(库存扣减失败: %w,err)}returnresult,nil}// InitStock 初始化库存到Redis// 活动开始前调用把数据库库存同步到Redisfunc(s*StockService)InitStock(ctx context.Context,itemIDstring,countint)error{key:fmt.Sprintf(seckill:stock:%s,itemID)// 设置库存加过期时间防止脏数据残留returns.client.Set(ctx,key,count,24*time.Hour).Err()}Lua脚本是整个秒杀系统的命脉。脚本里先GET库存判断够不够够了再DECRBY扣减这整个过程Redis保证不会被打断。8万并发进来Redis单线程串行执行这些脚本库存精确扣到0为止。二、本地缓存加异步下单Redis预扣减成功只是第一步真正的订单创建要写数据库这一步慢。直接同步写库Redis扣减成功但数据库写超时用户看到抢到了实际没下单。解决思路是异步下单预扣减成功后把订单消息丢到消息队列异步消费写库。packageseckillimport(contextencoding/jsonlogtimegithub.com/redis/go-redis/v9)// Order 订单信息typeOrderstruct{UserIDstringjson:user_idItemIDstringjson:item_idTime time.Timejson:time}// OrderService 异步下单服务// 预扣减成功后订单信息写入Redis队列异步消费typeOrderServicestruct{client*redis.Client stockSrv*StockService queueKeystring// 订单队列key}// NewOrderService 创建异步下单服务funcNewOrderService(client*redis.Client,stockSrv*StockService)*OrderService{returnOrderService{client:client,stockSrv:stockSrv,queueKey:seckill:order:queue,}}// TrySeckill 秒杀入口// 返回: 是否抢到(预扣减成功)func(o*OrderService)TrySeckill(ctx context.Context,userID,itemIDstring)(bool,error){// 第一步: Redis预扣减库存result,err:o.stockSrv.Deduct(ctx,itemID)iferr!nil{returnfalse,err}ifresult!1{// 库存不足或商品不存在returnfalse,nil}// 第二步: 扣减成功订单信息入队列order:Order{UserID:userID,ItemID:itemID,Time:time.Now(),}data,_:json.Marshal(order)// LPUSH写入队列异步消费iferr:o.client.LPush(ctx,o.queueKey,data).Err();err!nil{// 入队失败需要回滚库存// 这里简化处理生产环境需要补偿log.Printf(订单入队失败: %v,err)returnfalse,err}returntrue,nil}// ConsumeOrders 消费订单队列异步写库// 单独goroutine运行从队列取订单写数据库func(o*OrderService)ConsumeOrders(ctx context.Context){for{select{case-ctx.Done():returndefault:// BRPOP阻塞等待避免空转msg,err:o.client.BRPop(ctx,5*time.Second,o.queueKey).Result()iferr!nil{iferrredis.Nil{continue// 队列为空继续等待}log.Printf(消费订单失败: %v,err)continue}// 解析订单并写库varorder Orderiferr:json.Unmarshal([]byte(msg[1]),order);err!nil{log.Printf(解析订单失败: %v,err)continue}// 写入数据库(实际调用DAO层)iferr:o.saveOrder(ctx,order);err!nil{log.Printf(写库失败: %v,err)// 写库失败重试或补偿}}}}// saveOrder 写入数据库func(o*OrderService)saveOrder(ctx context.Context,order*Order)error{// 实际调用数据库DAO层// 这里简化为日志输出log.Printf(订单入库: user%s item%s,order.UserID,order.ItemID)returnnil}异步下单把同步的数据库写入变成异步消费用户端响应时间从几百毫秒降到几十毫秒。代价是用户体验上有一个订单确认中的中间态需要前端轮询或WebSocket推送最终结果。三、踩坑经验:超卖问题排查与修复超卖是秒杀系统最严重的故障。我们的活动上线后2000台库存最后下了2053单多出的53单要赔钱。排查过程很曲折。第一个怀疑对象是Lua脚本。我反复检查脚本的逻辑GET判断再DECRBY逻辑没问题。后来发现真正的问题出在库存初始化环节。活动开始前我们调了InitStock设置Redis库存为2000但运维同事又手动执行了一次SET命令把库存改成了2050因为数据库里有个字段记错了。更隐蔽的问题是Redis集群的主从切换。我们用的Redis Cluster主节点扣减成功后同步到从节点有延迟。主节点故障切换到从节点时从节点可能还没同步到最新的库存值新主节点读到旧库存继续扣减导致超卖。修复方案有两个要点。第一库存初始化加校验Redis里的库存不能超过数据库里的库存。第二最终防线放在数据库用乐观锁做最后一道校验。packageseckillimport(database/sqlerrorsfmt)// DBStockChecker 数据库层库存校验// 作为最后一道防线防止Redis层超卖传导到数据库typeDBStockCheckerstruct{db*sql.DB}// NewDBStockChecker 创建数据库校验器funcNewDBStockChecker(db*sql.DB)*DBStockChecker{returnDBStockChecker{db:db}}// DeductWithOptimisticLock 乐观锁扣减库存// 利用SQL的WHERE条件做并发控制// 只有多个人同时竞争时一个成功其余失败func(d*DBStockChecker)DeductWithOptimisticLock(itemIDstring,userIDstring,)error{// 乐观锁: UPDATE时带上stock 0的条件// 如果库存已经为0affected rows为0说明被别人抢了query: UPDATE seckill_items SET stock stock - 1, sold_count sold_count 1 WHERE item_id ? AND stock 0 result,err:d.db.Exec(query,itemID)iferr!nil{returnfmt.Errorf(数据库扣减失败: %w,err)}rows,_:result.RowsAffected()ifrows0{// 库存已被抢完返回错误returnerrors.New(库存不足)}returnnil}// VerifyStock 库存校验// 初始化时调用确保Redis库存不超过数据库库存func(d*DBStockChecker)VerifyStock(itemIDstring)(int,error){varstockinterr:d.db.QueryRow(SELECT stock FROM seckill_items WHERE item_id ?,itemID,).Scan(stock)iferr!nil{return0,err}returnstock,nil}数据库乐观锁是兜底方案。Redis负责快速过滤99%的请求数据库乐观锁保证最后1%的请求不会超卖。即使Redis层出了问题数据库这一层挡住超卖。四、对比分析库存方案性能精确度复杂度适用规模Redis预扣减极高(10万QPS)高(Lua原子)中中大并发数据库乐观锁低(几千QPS)极高低小并发兜底Redis Cluster极高(分片)中(主从延迟)高超大并发本地缓存扣减极高(无网络)低(多实例不共享)低单机秒杀Redis预扣减是秒杀系统的主力方案性能和精确度都不错。数据库乐观锁适合做兜底性能扛不住大并发。Redis Cluster用分片扛超大流量主从切换的延迟是隐患。本地缓存扣减只在单机场景能用多实例会各自扣减导致超卖。总结秒杀系统的设计核心是把流量挡在数据库之外。Redis预扣减用Lua脚本保证原子性挡住99%的无效请求。异步下单把数据库写入变成队列消费削峰填谷。数据库乐观锁做最后一道防线防止Redis层异常导致超卖。库存初始化必须做校验Redis里的库存不能超过数据库。下一篇我们聊实时聊天系统的架构设计。

相关新闻

Go微服务通信:gRPC vs REST vs消息队列

Go微服务通信:gRPC vs REST vs消息队列

Go微服务通信:gRPC vs REST vs消息队列摘要: 本篇讲解Go微服务三种通信方式,gRPC用protobuf定义接口获得高性能,REST靠HTTP兼容性好上手快,NATS和Kafka做异步消息解耦,给出性能benchmark对比,用决策树选型,…

2026/8/21 2:05:31 阅读更多 →
从拍照到拆机:Sony相机逆向工程工具 Sony-PMCA-RE 四步上手指南

从拍照到拆机:Sony相机逆向工程工具 Sony-PMCA-RE 四步上手指南

从拍照到拆机:Sony相机逆向工程工具 Sony-PMCA-RE 四步上手指南 【免费下载链接】Sony-PMCA-RE Reverse Engineering Sony Digital Cameras 项目地址: https://gitcode.com/gh_mirrors/so/Sony-PMCA-RE 你的 Sony 相机里,藏着多少没告诉你的能力&…

2026/8/21 4:54:48 阅读更多 →
PhpBoot RPC 分布式实战:像调用本地方法一样调用远程接口

PhpBoot RPC 分布式实战:像调用本地方法一样调用远程接口

PhpBoot RPC 分布式实战:像调用本地方法一样调用远程接口 【免费下载链接】phpboot :coffee: 🚀 tiny & fast PHP framework for building Microservices/RESTful APIs, with useful features: IOC, Hook, ORM, RPC, Swagger, Annotation, Parameter…

2026/8/21 2:48:37 阅读更多 →

最新新闻

MiniMax H3 本地部署实战:从环境搭建到工作流调优的完整指南

MiniMax H3 本地部署实战:从环境搭建到工作流调优的完整指南

最近在本地部署和测试各种视频生成模型时,我遇到了一个非常典型的问题:很多模型要么对硬件要求高得离谱,要么生成效果不稳定,要么就是流程复杂到让人望而却步。就在我一边整理测试笔记,一边思考有没有一个“既能跑起来…

2026/8/21 5:07:49 阅读更多 →
大厂Java电商系统面试全解析与实战技巧

大厂Java电商系统面试全解析与实战技巧

1. 项目概述:一场典型的大厂Java技术面试剖析"谢飞机的电商系统闯关记"这个标题生动描绘了一位Java工程师在互联网大厂技术面试中的完整历程。作为从业多年的面试官,我见过太多候选人在电商系统设计这类高频考题上折戟沉沙。这场模拟面试聚焦电…

2026/8/21 5:07:49 阅读更多 →
MECA机制中心AI:如何让机器自动生成有价值的数学猜想

MECA机制中心AI:如何让机器自动生成有价值的数学猜想

1. 项目概述:当AI开始“猜”数学定理最近在AI for Science的圈子里,有个词儿讨论得挺热乎,叫“数学猜想生成”。听起来是不是有点玄乎?让机器去“猜”数学定理,这事儿靠谱吗?我最初看到“MECA: A Mechanism…

2026/8/21 5:07:49 阅读更多 →
基于文件通信的量化交易信号桥接方案:连接外部Python与QMT/PTrade

基于文件通信的量化交易信号桥接方案:连接外部Python与QMT/PTrade

在实际量化交易开发中,我们常常面临一个核心矛盾:策略研究需要 Python 的灵活生态和强大库支持,而交易执行则依赖于券商或平台提供的专用终端(如迅投 QMT、恒生 PTrade)。这些终端通常内置了策略编写环境,但…

2026/8/21 5:07:49 阅读更多 →
SpringBoot+Vue智能招聘平台架构设计与实践

SpringBoot+Vue智能招聘平台架构设计与实践

1. 项目背景与核心价值在大连这座东北地区重要的IT产业聚集地,每年有超过3万名计算机相关专业毕业生涌入就业市场。与此同时,本地2000余家IT企业持续面临精准招聘的痛点——传统招聘平台往往存在技术岗位匹配度低、简历筛选效率差、沟通成本高等问题。我…

2026/8/21 5:07:49 阅读更多 →
SpringBoot+Vue高校实习管理系统全栈开发实战

SpringBoot+Vue高校实习管理系统全栈开发实战

1. 高校实习管理系统全栈开发实战去年为某高校开发实习管理系统时,我深刻体会到教育信息化对提升教学管理效率的价值。这套基于SpringBootVue的全栈系统,上线后使实习申请审批时间从平均3天缩短至2小时内,学生提交材料完整率提升65%。下面分享…

2026/8/21 5:06:49 阅读更多 →

日新闻

机场边检旅客定位系统国产化白皮书:算法、硬件、底座平台全程自主

机场边检旅客定位系统国产化白皮书:算法、硬件、底座平台全程自主

前言随着国家数字基础设施信创替代、关键技术自主可控战略持续深化,口岸智慧安防、边检智能管控领域正全面进入国产化、自主化、安全可控升级周期。当前国内机场边检旅客识别与定位体系长期依赖国外商用视觉算法、进口成像硬件、闭源通用计算平台,存在核…

2026/8/21 0:00:42 阅读更多 →
别再把“数字孪生”当空间智能了!镜像视界揭开四维时空的真正面纱

别再把“数字孪生”当空间智能了!镜像视界揭开四维时空的真正面纱

别再把“数字孪生”当空间智能了!镜像视界揭开四维时空的真正面纱当下数字化建设浪潮中,很多项目将三维可视化、视频贴图叠加的数字孪生等同于空间智能。传统数字孪生更多停留在三维场景复刻,擅长把物理世界“画出来、展示出来”,…

2026/8/21 0:00:42 阅读更多 →
105、车载温度范围-40°C到85°C的影像质量一致性——ISP参数温漂补偿与产线标定策略

105、车载温度范围-40°C到85°C的影像质量一致性——ISP参数温漂补偿与产线标定策略

105、车载温度范围-40C到85C的影像质量一致性——ISP参数温漂补偿与产线标定策略 去年冬天在北方某车厂做A样评审,凌晨四点的黑河试验场,零下三十三度。客户拿了一台冷启动的车,中控屏上倒车影像全是雪花噪点,暗部细节直接糊成一片。我第一反应是sensor温度没上来,暗电流…

2026/8/21 0:00:42 阅读更多 →

周新闻

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

如果你是一名开发者,最近可能已经感受到了AI大模型正在从“玩具”变成“生产力工具”的强烈信号。从代码补全到智能Agent,从本地部署到云端API,我们正处在一个技术栈快速重构的节点。然而,面对层出不穷的模型、框架和工具&#xf…

2026/8/21 3:21:33 阅读更多 →
工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/21 0:02:09 阅读更多 →
【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

2026/8/19 11:55:16 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/20 6:11:08 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/20 21:46:49 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/21 0:14:22 阅读更多 →