首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >高并发秒杀系统(Redis分布式锁优化与库存防超卖实战)

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

作者头像
大熊计算机
发布2025-07-15 09:19:28
发布2025-07-15 09:19:28
34900
代码可运行
举报
文章被收录于专栏:C博文C博文
运行总次数:0
代码可运行

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

一、秒杀系统的破局思路

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

1.1 架构瓶颈分析

核心痛点诊断

  1. 数据库瓶颈:单MySQL实例TPS仅5000,连接池最大1500
  2. 超卖问题:压测中100并发时超卖率15.2%
  3. 热点竞争:95%请求集中在10%的热门商品
  4. 扩容失效:单纯增加服务节点无法提升数据库处理能力
代码语言:javascript
代码运行次数:0
运行
复制
// 初始架构下单点更新库存(问题代码)
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 哈希分片算法实现
代码语言:javascript
代码运行次数:0
运行
复制
/**
 * 用户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 分片元数据管理架构

配置热更新流程

  1. 运维修改分片规则
  2. ConfigServer生成新版本配置
  3. Zookeeper通知所有网关节点
  4. 网关异步加载新配置(不影响在线流量)
三、Redis分布式锁深度优化
3.1 基础锁的致命缺陷
代码语言:javascript
代码运行次数:0
运行
复制
// 典型错误实现 - 锁续期失败风险
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 生产级分布式锁实现
代码语言:javascript
代码运行次数:0
运行
复制
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 锁性能优化对比
四、库存防超卖全链路设计
4.1 三级库存防护体系

防护要点

  • 网关层:令牌桶限流 + 库存状态缓存
  • 服务层:Redis原子操作预减
  • DB层:数据库乐观锁保证最终一致
4.2 Redis库存管理核心模块
代码语言:javascript
代码运行次数:0
运行
复制
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 秒杀完整业务流程
5.2 分库分表事务处理
代码语言:javascript
代码运行次数:0
运行
复制
@Service
public class SeckillServiceImpl implements SeckillService {
    @Autowired
    private DynamicDataSource dataSource;
    @Autowired
    private StockManager stockManager;
    @Autowired
    private 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,200

420ms

1.2s

15.2%

1x

分库分表基础版

68,000

85ms

230ms

0.3%

1.8x

优化版(本文)

182,000

32ms

68ms

0%

2.1x

6.2 资源消耗对比

6.3 扩容能力线性测试
七、深度优化技巧
7.1 热点商品探测与隔离
代码语言:javascript
代码运行次数:0
运行
复制
// 基于滑动窗口的热点检测
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 动态扩容方案

八、总结与避坑指南

核心经验总结

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

生产环境踩坑实录

  1. 分片键选择不当
    • 场景:使用手机尾号做分片键
    • 问题:数据倾斜严重(尾号6/8占比40%)
    • 解决:改用用户ID哈希+虚拟节点
  2. 锁续期故障
    • 场景:续期线程池被OOM杀死
    • 现象:锁提前释放导致数据不一致
    • 解决:增加续期线程心跳监控
  3. 缓存与DB不一致
    • 场景:Redis预减成功但DB事务失败
    • 解决:引入库存回补机制+对账任务

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

架构演进方向

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、秒杀系统的破局思路
    • 1.1 架构瓶颈分析
    • 1.2 破局方案设计
  • 二、分库分表路由算法核心设计
    • 2.1 分片策略深度对比
    • 2.2 哈希分片算法实现
    • 2.3 分片元数据管理架构
  • 三、Redis分布式锁深度优化
    • 3.1 基础锁的致命缺陷
    • 3.2 生产级分布式锁实现
    • 3.3 锁性能优化对比
  • 四、库存防超卖全链路设计
    • 4.1 三级库存防护体系
    • 4.2 Redis库存管理核心模块
  • 五、分库分表+分布式锁联调
    • 5.1 秒杀完整业务流程
    • 5.2 分库分表事务处理
  • 六、压测结果与性能分析
    • 6.1 性能指标对比(集群模式)
    • 6.2 资源消耗对比
    • 6.3 扩容能力线性测试
  • 七、深度优化技巧
    • 7.1 热点商品探测与隔离
    • 7.2 动态扩容方案
  • 八、总结与避坑指南
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档