从架构到实现:高并发场景下的skill设计最佳实践

2次阅读
没有评论

共计 1881 个字符,预计需要花费 5 分钟才能阅读完成。

image.webp

秒杀场景的典型痛点

在高并发秒杀场景中,我们最常遇到两个致命问题:

  1. 库存超卖 :当 100 个库存被 1 万个请求同时抢夺时,传统数据库的UPDATE stock SET count=count-1 会在并发下出现负库存
  2. 接口响应慢:瞬时流量导致数据库连接池耗尽,页面卡死在 ” 正在抢购 ” 状态

去年双十一我们系统就因此损失了 300+ 订单,事后用 jmeter 回放请求时发现:

  • 5000QPS 直接打满 MySQL 连接
  • 超卖比例高达 15%

技术方案横评

方案 1:同步锁(不推荐)

// 伪代码示例
public synchronized void deductStock() {if(stock > 0) {stock--;}
}

– 优点:实现简单
– 缺点:
– 单机锁无法跨服务
– 吞吐量 <100QPS

方案 2:异步队列(推荐)

从架构到实现:高并发场景下的 skill 设计最佳实践
1. 请求先进入 Kafka/RocketMQ
2. 消费者集群异步处理

  • 优点:支持 10W+QPS
  • 缺点:存在秒杀结果延迟

方案 3:分布式事务(慎用)

  • 适用场景:需要强一致性的支付系统
  • 秒杀场景 overkill

核心实现细节

Redis+Lua 原子扣减

-- KEYS[1]: 库存 key
-- ARGV[1]: 扣减数量
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock >= tonumber(ARGV[1]) then
    return redis.call('DECRBY', KEYS[1], ARGV[1])
end
return -1

关键点

  1. 使用 SCRIPT LOAD 预加载 Lua 脚本
  2. 通过 EVALSHA 执行减少网络传输

RabbitMQ 削峰配置

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 50 # 每个消费者最大未 ack 数
        concurrency: 20 # 最小消费者数
        max-concurrency: 100 # 动态扩容上限

多级缓存架构

graph TD
    A[客户端] -->|Nginx 本地缓存 | B[边缘节点]
    B -->|Redis 集群 | C[数据库]
  • 第一层:Guava Cache 设置 3 秒过期
  • 第二层:Redis Cluster 分片存储

完整代码示例(Java)

@RestController
public class SeckillController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    // Lua 脚本 SHA1
    private String stockScriptSha;

    @PostConstruct
    public void init() {
        String script = "..."; // 前述 Lua 脚本
        stockScriptSha = redisTemplate
            .execute(connection -> connection.scriptLoad(script.getBytes()));
    }

    @PostMapping("/seckill")
    public Result seckill(@RequestParam Long itemId) {
        try {
            Long result = redisTemplate.execute((RedisCallback<Long>) connection -> 
                    connection.evalSha(stockScriptSha,
                        ReturnType.INTEGER,
                        1,
                        ("stock_" + itemId).getBytes(),
                        "1".getBytes())
            );

            if (result == -1L) {return Result.fail("库存不足");
            }

            // 发送 MQ 消息
            sendToMQ(itemId);
            return Result.success();} catch (Exception e) {log.error("秒杀异常", e);
            // 重试 3 次
            return retry(() -> seckill(itemId), 3);
        }
    }
}

生产环境要点

缓存异常预防

  • 穿透
  • 空值缓存:redis.set("empty", "", 5min)
  • BloomFilter 前置校验

  • 雪崩

  • 随机过期时间:TTL = baseTime + random(0, 300)
  • 永不过期的后台更新策略

监控指标

指标名称 阈值 告警方式
Redis QPS >50000 企业微信
MQ 堆积量 >10000 短信
接口 99 线 >500ms 邮件

开放性问题

在最终一致性方案中,我们可能会遇到:

  1. 用户支付成功后查不到订单(同步延迟)
  2. 超卖后的补偿退款流程

这需要根据业务容忍度权衡:

  • 金融级系统:采用 TCC/SAGA
  • 电商秒杀:消息队列 + 定时对账

实际项目中,我们通过夜间跑批修复了 0.03% 的数据不一致,这个代价远低于强一致性带来的性能损耗。

正文完
 0
评论(没有评论)