高并发秒杀系统(Redis分布式锁优化与库存防超卖实战)

本文通过日活百万级的电商秒杀案例,深度剖析分库分表路由算法在高并发场景下的落地实践。结合Redis分布式锁的优化方案解决库存超卖问题,包含完整架构设计、代码实现及压测数据对比。全文包含12个核心代码片段和8类技术图表,来自线上生产环境的实战经验总结。

一、秒杀系统的破局思路

业务场景:某电商平台「iPhone 16限时秒杀」活动,峰值QPS 12万+,库存量10万台,活动持续30分钟。

1.1 架构瓶颈分析
客户端
Nginx集群 LVS负载
API网关层
秒杀服务集群
数据库集群
缓存集群

核心痛点诊断

  1. 数据库瓶颈:单MySQL实例TPS仅5000,连接池最大1500
  2. 超卖问题:压测中100并发时超卖率15.2%
  3. 热点竞争:95%请求集中在10%的热门商品
  4. 扩容失效:单纯增加服务节点无法提升数据库处理能力
// 初始架构下单点更新库存(问题代码)
public boolean deductStock(Long itemId) {Item item = itemMapper.selectById(itemId);if (item.getStock() > 0) {item.setStock(item.getStock() - 1);itemMapper.update(item); // 并发场景下产生超卖return true;}return false;
}
1.2 破局方案设计
水平扩展
分库分表
并发控制
分布式锁优化
性能提升
缓存预减库存
容错机制
降级+熔断

技术选型矩阵

组件选型优势
分库分表ShardingSphere生态完善,兼容MySQL协议
分布式锁Redis+Lua高性能,原子操作
缓存层Redis集群+持久化支持高并发读写
监控体系Prometheus+Grafana实时流量观测

二、分库分表路由算法核心设计

2.1 分片策略深度对比

分片键选择黄金法则

  1. 离散度高(如用户ID优于手机号)
  2. 业务查询频次匹配
  3. 避免跨分片事务
  4. 预留扩容空间
2.2 哈希分片算法实现
/*** 用户ID分片路由算法(含虚拟节点)* @param userId 用户ID* @param dbCount 物理分库数* @param tableCount 每库分表数* @param virtualFactor 虚拟节点因子*/
public class UserShardingRouter {// 物理节点到虚拟节点映射private static final SortedMap<Integer, String> virtualNodes = new TreeMap<>();private static final int VIRTUAL_FACTOR = 160; // 每个物理节点虚拟节点数static {// 初始化虚拟节点环for (int i = 0; i < dbCount; i++) {for (int j = 0; j < VIRTUAL_FACTOR; j++) {String node = "db_" + i;String vnode = node + "#vnode_" + j;int hash = MurmurHash.hash32(vnode);virtualNodes.put(hash, node);}}}public static String route(String userId) {// 计算用户哈希值int hash = MurmurHash.hash32(userId);// 获取大于该哈希值的子集SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);if (subMap.isEmpty()) {return virtualNodes.get(virtualNodes.firstKey());}String physicalNode = subMap.get(subMap.firstKey());// 计算表路由int tableIdx = Math.abs(userId.substring(0, 8).hashCode()) % tableCount;return physicalNode + ".tb_" + String.format("%03d", tableIdx);}
}
2.3 分片元数据管理架构
发布配置
配置变更通知
拉取最新配置
ConfigServer
+version: String
+shardRules: Map
+refreshRules() : void
+getRouteInfo() : RouteConfig
Zookeeper
+persist(config) : void
+watchChanges() : void
Gateway
-routeCache: ConcurrentMap
+routeRequest(userId) : String

配置热更新流程

  1. 运维修改分片规则
  2. ConfigServer生成新版本配置
  3. Zookeeper通知所有网关节点
  4. 网关异步加载新配置(不影响在线流量)

三、Redis分布式锁深度优化

3.1 基础锁的致命缺陷
// 典型错误实现 - 锁续期失败风险
public boolean tryLock(String key, String clientId, int expireSec) {if (redis.set(key, clientId, "NX", "EX", expireSec)) {// 启动续期线程new Thread(() -> {while (locked) {Thread.sleep(expireSec * 1000 / 3);redis.expire(key, expireSec);  // 非原子操作!}}).start();return true;}return false;
}

基础锁的三大陷阱

  1. 非原子操作(setnx+expire分离)
  2. 锁误删(未验证客户端标识)
  3. 续期失败(线程异常终止)
3.2 生产级分布式锁实现
public class RedisDistributedLock {private final JedisPool jedisPool;private final String lockKey;private final String lockValue;private final int expireTime;private volatile boolean locked = false;private ScheduledExecutorService renewExecutor;public RedisDistributedLock(JedisPool jedisPool, String lockKey, int expireTime) {this.jedisPool = jedisPool;this.lockKey = lockKey;this.lockValue = UUID.randomUUID().toString() + Thread.currentThread().getId();this.expireTime = expireTime;}public boolean tryLock(long waitMillis) {long start = System.currentTimeMillis();try (Jedis jedis = jedisPool.getResource()) {// Lua脚本保证原子性String script = "if redis.call('exists', KEYS[1]) == 0 then " +"   redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2]) " +"   return 1 " +"end " +"return 0";while (true) {Object result = jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(lockValue, String.valueOf(expireTime)));if ("1".equals(result.toString())) {locked = true;startRenewal(); // 启动续期return true;}if (System.currentTimeMillis() - start >= waitMillis) {return false;}Thread.sleep(50); // 避免CPU空转}}}private void startRenewal() {renewExecutor = Executors.newSingleThreadScheduledExecutor();renewExecutor.scheduleAtFixedRate(() -> {try (Jedis jedis = jedisPool.getResource()) {// 续期前验证锁持有者String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"   return redis.call('pexpire', KEYS[1], ARGV[2]) " +"else " +"   return 0 " +"end";jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(lockValue, String.valueOf(expireTime)));}}, expireTime / 3, expireTime / 3, TimeUnit.MILLISECONDS);}public void unlock() {if (!locked) return;try (Jedis jedis = jedisPool.getResource()) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"   return redis.call('del', KEYS[1]) " +"else " +"   return 0 " +"end";jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(lockValue));}if (renewExecutor != null) {renewExecutor.shutdownNow();}locked = false;}
}
3.3 锁性能优化对比
水平扩容能力测试
2分片 18K
1分片 9K
4分片 36K
8分片 72K
16分片 144K

四、库存防超卖全链路设计

4.1 三级库存防护体系
请求
通过
成功
获取
客户端
网关层
库存预检
秒杀服务
Redis预减
获取分布式锁
DB扣减
创建订单

防护要点

  • 网关层:令牌桶限流 + 库存状态缓存
  • 服务层:Redis原子操作预减
  • DB层:数据库乐观锁保证最终一致
4.2 Redis库存管理核心模块
public class StockManager {private static final String STOCK_PREFIX = "sec_stock:";private static final String STOCK_SOLD = "sec_sold:";private final JedisCluster jedisCluster;// 初始化商品库存public void initStock(String itemId, int total) {String key = STOCK_PREFIX + itemId;jedisCluster.set(key, String.valueOf(total));}// 预减库存(返回剩余库存)public long preDeduct(String itemId) {String script = "local key = KEYS[1] " +"local change = tonumber(ARGV[1]) " +"local stock = tonumber(redis.call('get', key)) " +"if stock < change then " +"   return -1 " +  // 库存不足"end " +"local newStock = stock - change " +"redis.call('set', key, newStock) " +"return newStock";return (Long) jedisCluster.eval(script, Collections.singletonList(STOCK_PREFIX + itemId),Collections.singletonList("1"));}// 真实扣减(数据库操作后)public boolean confirmDeduct(String itemId, int quantity) {String script = "local stockKey = KEYS[1] " +"local soldKey = KEYS[2] " +"local quantity = tonumber(ARGV[1]) " +"redis.call('incrby', soldKey, quantity) " +"return 1";jedisCluster.eval(script, Arrays.asList(STOCK_PREFIX + itemId, STOCK_SOLD + itemId),Collections.singletonList("1"));return true;}// 获取已售数量public long getSoldCount(String itemId) {String val = jedisCluster.get(STOCK_SOLD + itemId);return val == null ? 0 : Long.parseLong(val);}
}

五、分库分表+分布式锁联调

5.1 秒杀完整业务流程
Client Gateway SeckillService Redis Database POST /seckill?itemId=1001 库存预检(stock:1001) 返回错误码 秒杀结束 预减成功 转发请求 获取分布式锁(lock:1001) 获取成功 开启事务 查询用户资格 扣减真实库存 创建订单 提交事务 操作成功 确认扣减库存 释放锁 返回成功 秒杀成功 alt [库存不足] [库存足够] Client Gateway SeckillService Redis Database
5.2 分库分表事务处理
@Service
public class SeckillServiceImpl implements SeckillService {@Autowiredprivate DynamicDataSource dataSource;@Autowiredprivate StockManager stockManager;@Autowiredprivate RedisDistributedLock lock;@Transactional(rollbackFor = Exception.class)public SeckillResponse seckill(SeckillRequest request) {// 1. Redis预减库存long remain = stockManager.preDeduct(request.getItemId());if (remain < 0) {throw new BusinessException("库存不足");}// 2. 获取分布式锁String lockKey = "lock_item:" + request.getItemId();if (!lock.tryLock(lockKey, 2000)) {throw new BusinessException("系统繁忙请重试");}try {// 3. 路由计算String dsKey = UserShardingRouter.route(request.getUserId());dataSource.setCurrent(dsKey);// 4. 数据库操作ItemStock stock = stockMapper.selectForUpdate(request.getItemId());if (stock.getAvailable() < 1) {// 库存补偿stockManager.revertDeduct(request.getItemId());throw new BusinessException("库存不足");}// 扣减库存stockMapper.deduct(request.getItemId());// 创建订单Order order = new Order();order.setItemId(request.getItemId());order.setUserId(request.getUserId());orderMapper.insert(order);// 5. 确认扣减stockManager.confirmDeduct(request.getItemId());return SeckillResponse.success(order.getOrderId());} finally {lock.unlock(lockKey);dataSource.clear();}}
}

六、压测结果与性能分析

6.1 性能指标对比(集群模式)
方案QPS平均响应99分位超卖率资源成本
原始架构9,200420ms1.2s15.2%1x
分库分表基础版68,00085ms230ms0.3%1.8x
优化版(本文)182,00032ms68ms0%2.1x
6.2 资源消耗对比

在这里插入图片描述

6.3 扩容能力线性测试

在这里插入图片描述

七、深度优化技巧

7.1 热点商品探测与隔离
// 基于滑动窗口的热点检测
public class HotItemDetector {private static final Map<String, AtomicLong> counter = new ConcurrentHashMap<>();private static final Map<String, Boolean> hotItems = new ConcurrentHashMap<>();@Scheduled(fixedRate = 1000)public void detect() {counter.forEach((itemId, count) -> {long qps = count.getAndSet(0);if (qps > 5000) { // 热点阈值hotItems.put(itemId, true);// 动态增加该商品的分桶addItemBucket(itemId);}});}// 热点商品特殊路由public String routeHotItem(String itemId, String userId) {if (!hotItems.containsKey(itemId)) {return defaultRoute(userId);}// 对热点商品进行分桶隔离int bucket = userId.hashCode() % hotBucketCount;return "hot_db_" + bucket + ".tb_" + itemId;}
}
7.2 动态扩容方案
监控中
流量突增:
检测到QPS增长300%
流量突增
扩容决策:
持续5分钟
扩容决策
增加虚拟节点:
选择负载最低分片
增加虚拟节点
数据迁移:
仅迁移部分数据
数据迁移
路由更新:
通知配置中心
路由更新
流量回落:
低于阈值50%
流量回落
缩容决策:
持续30分钟
缩容决策
标记虚拟节点:
设为待回收
标记虚拟节点
流量迁移:
迁移至其他节点
流量迁移
移除节点:
物理删除
移除节点

八、总结与避坑指南

核心经验总结

  1. 分片键选择:优先选择离散度高的业务字段(如用户ID),避免使用枚举类字段
  2. 分布式锁三原则:
    • 加锁原子性(SET NX PX 单命令)
    • 锁标识唯一(UUID+线程ID)
    • 续租可靠性(后台守护线程)
  3. 库存分层校验:
    提交
    预减
    扣减
    前端
    网关层库存缓存
    Redis原子操作
    数据库最终确认
  4. 热点处理:建立实时监控+动态分桶机制

生产环境踩坑实录

  1. 分片键选择不当

    • 场景:使用手机尾号做分片键
    • 问题:数据倾斜严重(尾号6/8占比40%)
    • 解决:改用用户ID哈希+虚拟节点
  2. 锁续期故障

    • 场景:续期线程池被OOM杀死
    • 现象:锁提前释放导致数据不一致
    • 解决:增加续期线程心跳监控
  3. 缓存与DB不一致

    • 场景:Redis预减成功但DB事务失败
    • 解决:引入库存回补机制+对账任务

完整实现代码已开源:github.com/seckill-optimization
压测脚本路径:/pressure-test/jmeter_cluster.jmx

架构演进方向
在这里插入图片描述

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

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

相关文章

从loader和plugin开始了解webpack

目录 一、webpack中loader和plugin的区别1. Loader&#xff08;每个 Loader 是一个函数或对象&#xff09;2.plugin&#xff08;每个 Plugin 是一个实例&#xff09;3.自定义loader和plugin 二、Babel的功能三、Plugin中的compiler和compilation对象1. compiler对象2. compilat…

36-Oracle Statistics Gathering(统计信息收集)

小伙伴们&#xff0c;有没有因为统计信息不准&#xff0c;导致了业务卡顿&#xff0c;各种状况频出&#xff0c;这几天在实践和实操的过程中&#xff0c;时不时就需要进行统计信息的收集。同时统计信息收集的动作也是OCM必考内容。 数据库中的数据是地图&#xff0c;统计信息是…

Linux驱动程序(PWM接口)与超声波测距

一、利用阿里云服务器实现树莓派外网访问&#xff08;SSH 反向代理&#xff09; 1. 树莓派端配置 步骤 1&#xff1a;安装 SSH 服务&#xff08;若未安装&#xff09; sudo apt-get install openssh-server 步骤 2&#xff1a;创建反向代理连接 -p 22&#xff1a;指定阿里…

Web攻防-XSS跨站文件类型功能逻辑SVGPDFSWFHTMLXMLPMessageLocalStorage

知识点&#xff1a; 1、Web攻防-XSS跨站-文件类型-html&pdf&swf&svg&xml 2、Web攻防-XSS跨站-功能逻辑-postMessage&localStorage 一、演示案例-WEB攻防-XSS跨站-文件类型触发XSS-SVG&PDF&SWF&HTML&XML等 1、SVG-XSS SVG(Scalable Vect…

强大模型通过自我和解进步——Unsupervised Elicitation of Language Models——论文阅读笔记

本周关注的工作是&#xff1a;Unsupervised Elicitation of Language Models 这篇文章通篇体现了这样一件事——香蕉皮大需要香蕉大&#xff01; 一句话总结 首先注意&#xff1a;这个工作不是面向对齐的&#xff0c;而是写【如何准备】对齐任务的Reward Model需要的数据集的…

Qt—(Qt初识,槽,信号,事件)

一 Qt初识 暂时不写了 我的理解是类似于c#&#xff0c;是一个组件库&#xff0c;不局限是一个组件框架。 二 Qt Core Qt Core 是 Qt 框架的基础模块&#xff0c;提供非 GUI 的核心功能&#xff1a; 核心类&#xff1a;QObject&#xff08;信号槽机制&#xff09;、QEvent&…

深度学习——基于卷积神经网络实现食物图像分类【2】(数据增强)

文章目录 引言一、项目概述二、环境准备三、数据预处理3.1 数据增强与标准化3.2 数据集准备 四、自定义数据集类五、构建CNN模型六、训练与评估6.1 训练函数6.2 评估函数6.3 训练流程 七、关键技术与优化八、常见问题与解决九、完整代码十、总结 引言 本文将详细介绍如何使用P…

详细说说分布式Session的几种实现方式

1. 基于客户端存储&#xff08;Cookie-Based&#xff09; 原理&#xff1a;将会话数据直接存储在客户端 Cookie 中 实现&#xff1a; // Spring Boot 示例 Bean public CookieSerializer cookieSerializer() {DefaultCookieSerializer serializer new DefaultCookieSerializ…

用mac的ollama访问模型,为什么会出现模型胡乱输出,然后过一会儿再访问,就又变成正常的

例子&#xff1a;大模型推理遇到内存不足 1. 场景还原 你在Mac上用Ollama运行如下代码&#xff08;以Python为例&#xff0c;假设Ollama有API接口&#xff09;&#xff1a; import requestsprompt "请写一首关于夏天的诗。" response requests.post("http:…

简说 Linux 用户组

Linux 用户组 的核心概念、用途和管理方法&#xff0c;尽量简明易懂。 &#x1f31f; 什么是 Linux 用户组&#xff1f; 在 Linux 系统中&#xff1a; &#x1f449; 用户组&#xff08;group&#xff09; 是一组用户的集合&#xff0c;用来方便地管理权限。 &#x1f449; 用…

S32DS上进行S32K328的时钟配置,LPUART时钟配置步骤详解

1&#xff1a;S32K328的基础信息 S32K328官网介绍 由下图可知&#xff0c;S32K328的最大主频为 240MHz 2&#xff1a;S32K328时钟树配置 2.1 system clock node 节点说明 根据《S32K3xx Reference Manual》资料说明 Table 143 各个 系统时钟节点 的最大频率如下所示&#…

wordpress小语种网站模板

wordpress朝鲜语模板 紫色风格的韩语wordpress主题&#xff0c;适合做韩国、朝鲜的外贸公司官方网站使用。 https://www.jianzhanpress.com/?p8486 wordpress日文模板 绿色的日语wordpress外贸主题&#xff0c;用来搭建日文外贸网站很实用。 https://www.jianzhanpress.co…

网络:Wireshark解析https协议,firefox

文章目录 问题浏览器访问的解决方法python requests问题 现在大部分的网站已经切到https,很多站点即使开了80的端口,最终还是会返回301消息,让客户端转向到https的一个地址。 所以在使用wireshark进行问题分析的时候,解析tls上层的功能,是必不可少的,但是这个安全交换的…

ollama部署开源大模型

1. 技术概述 Spring AI&#xff1a;Spring 官方推出的 AI 框架&#xff0c;简化大模型集成&#xff08;如文本生成、问答系统&#xff09;&#xff0c;支持多种 LLM 提供商。Olama&#xff1a;开源的本地 LLM 推理引擎&#xff0c;支持量化模型部署&#xff0c;提供 REST API …

Kafka 可靠性保障:消息确认与事务机制(二)

Kafka 事务机制 1. 幂等性与事务的关系 在深入探讨 Kafka 的事务机制之前&#xff0c;先来了解一下幂等性的概念。幂等性&#xff0c;简单来说&#xff0c;就是对接口的多次调用所产生的结果和调用一次是一致的。在 Kafka 中&#xff0c;幂等性主要体现在生产者端&#xff0c…

使用 React.Children.map遍历或修改 children

使用场景&#xff1a; 需要对子组件进行统一处理&#xff08;如添加 key、包裹额外元素、过滤特定类型等&#xff09;。 动态修改 children 的 props 或结构。 示例代码&#xff1a;遍历并修改 children import React from react;// 一个组件&#xff0c;给每个子项添加边框…

智能体三阶:LLM→Function Call→MCP

哈喽&#xff0c;我是老刘 老刘是个客户端开发者&#xff0c;目前主要是用Flutter进行开发&#xff0c;从Flutter 1.0开始到现在已经6年多了。 那为啥最近我对MCP和AI这么感兴趣的呢&#xff1f; 一方面是因为作为一个在客户端领域实战多年的程序员&#xff0c;我觉得客户端开发…

flutter的常规特征

前言 Flutter 是由 Google 开发的开源 UI 软件开发工具包&#xff0c;用于构建跨平台的高性能、美观且一致的应用程序。 一、跨平台开发能力 1.多平台支持&#xff1a;Flutter 支持构建 iOS、Android、Web、Windows、macOS 和 Linux 应用&#xff0c;开发者可以使用一套代码库在…

【Git】代码托管服务

博主&#xff1a;&#x1f44d;不许代码码上红 欢迎&#xff1a;&#x1f40b;点赞、收藏、关注、评论。 格言&#xff1a; 大鹏一日同风起&#xff0c;扶摇直上九万里。 文章目录 Git代码托管服务概述Git核心概念主流Git托管平台Git基础配置仓库创建方式Git文件状态管理常用…

Android 网络请求的选择逻辑(Connectivity Modules)

代码分析 ConnectivityManager packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java 许多APN已经弃用,应用层统一用 requestNetwork() 来请求网络。 [ConnectivityManager] example [ConnectivityManager] requestNetwork() [Connectivi…