实用指南:秒杀系统设计与实现

秒杀系统设计与实现

前言

秒杀系统是电商平台中最具挑战性的业务场景之一。每逢618、双11等大促活动,京东、淘宝等平台都会面临数百万甚至上千万的并发请求。本文将从架构设计、核心技术、代码实现等方面,详细讲解如何设计一个高并发、高可用的秒杀系统。

一、秒杀系统的业务特点

1.1 秒杀业务的核心挑战

+------------------+     +------------------+     +------------------+
|    瞬时高并发     |     |    库存超卖风险   |     |   数据一致性     |
|  100万+ QPS     |     |   超卖=亏损      |     |  缓存与DB同步    |
+------------------+     +------------------+     +------------------+
         |                       |                       |
         v                       v                       v
+------------------+     +------------------+     +------------------+
|    服务雪崩       |     |    恶意请求       |     |   用户体验       |
|   系统崩溃风险    |     |   刷单/机器人     |     |   响应时间要求    |
+------------------+     +------------------+     +------------------+

1.2 秒杀系统的核心指标

指标要求说明
QPS100万+峰值每秒请求数
响应时间<100ms用户可接受的等待时间
可用性99.99%系统稳定性要求
库存准确性100%绝对不能超卖

二、系统架构设计

2.1 整体架构图

                              +------------------+
                              |     用户请求      |
                              +--------+---------+
                                       |
                              +--------v---------+
                              |      CDN         |
                              |   静态资源分发    |
                              +--------+---------+
                                       |
                              +--------v---------+
                              |    Nginx集群     |
                              |   负载均衡/限流   |
                              +--------+---------+
                                       |
         +-----------------------------+-----------------------------+
         |                             |                             |
+--------v---------+         +--------v---------+         +--------v---------+
|   Gateway网关    |         |   Gateway网关    |         |   Gateway网关    |
|  认证/限流/路由   |         |  认证/限流/路由   |         |  认证/限流/路由   |
+--------+---------+         +--------+---------+         +--------+---------+
         |                             |                             |
         +-----------------------------+-----------------------------+
                                       |
         +-----------------------------+-----------------------------+
         |                             |                             |
+--------v---------+         +--------v---------+         +--------v---------+
|   秒杀服务集群    |         |   秒杀服务集群    |         |   秒杀服务集群    |
|   业务逻辑处理    |         |   业务逻辑处理    |         |   业务逻辑处理    |
+--------+---------+         +--------+---------+         +--------+---------+
         |                             |                             |
         +-----------------------------+-----------------------------+
                                       |
                    +------------------+------------------+
                    |                  |                  |
           +--------v-------+  +-------v--------+  +------v-------+
           |   Redis集群    |  |  RocketMQ集群  |  |  MySQL集群   |
           |   库存/限流    |  |   异步削峰     |  |   订单存储   |
           +----------------+  +----------------+  +--------------+

2.2 核心组件说明

  1. CDN层:静态页面、图片等资源缓存
  2. Nginx层:负载均衡、流量控制、静态化
  3. Gateway层:统一入口、认证鉴权、限流熔断
  4. 服务层:秒杀核心业务逻辑
  5. 缓存层:Redis集群,库存预热、分布式锁
  6. 消息队列:RocketMQ异步处理,削峰填谷
  7. 数据库层:MySQL主从集群,最终数据持久化

三、数据库设计

3.1 核心表结构

-- 秒杀商品表
CREATE TABLE `seckill_goods` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '秒杀商品ID',
`goods_id` BIGINT(20) NOT NULL COMMENT '商品ID',
`goods_name` VARCHAR(128) NOT NULL COMMENT '商品名称',
`goods_img` VARCHAR(256) DEFAULT NULL COMMENT '商品图片',
`original_price` DECIMAL(10,2) NOT NULL COMMENT '原价',
`seckill_price` DECIMAL(10,2) NOT NULL COMMENT '秒杀价',
`stock_count` INT(11) NOT NULL COMMENT '库存数量',
`start_time` DATETIME NOT NULL COMMENT '秒杀开始时间',
`end_time` DATETIME NOT NULL COMMENT '秒杀结束时间',
`status` TINYINT(1) DEFAULT 0 COMMENT '状态:0-未开始,1-进行中,2-已结束',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_goods_id` (`goods_id`),
KEY `idx_start_time` (`start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀商品表';
-- 秒杀订单表
CREATE TABLE `seckill_order` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`order_no` VARCHAR(32) NOT NULL COMMENT '订单编号',
`user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
`goods_id` BIGINT(20) NOT NULL COMMENT '商品ID',
`seckill_goods_id` BIGINT(20) NOT NULL COMMENT '秒杀商品ID',
`goods_name` VARCHAR(128) NOT NULL COMMENT '商品名称',
`goods_price` DECIMAL(10,2) NOT NULL COMMENT '商品价格',
`order_status` TINYINT(1) DEFAULT 0 COMMENT '订单状态:0-待支付,1-已支付,2-已取消',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`pay_time` DATETIME DEFAULT NULL COMMENT '支付时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_goods` (`user_id`, `seckill_goods_id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀订单表';
-- 库存流水表(用于库存对账)
CREATE TABLE `stock_log` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`seckill_goods_id` BIGINT(20) NOT NULL,
`order_no` VARCHAR(32) NOT NULL,
`stock_change` INT(11) NOT NULL COMMENT '库存变化:负数为扣减',
`type` TINYINT(1) NOT NULL COMMENT '类型:1-扣减,2-回滚',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_seckill_goods_id` (`seckill_goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存流水表';

四、核心技术实现

4.1 项目结构

seckill-system/
├── seckill-common/                 # 公共模块
│   ├── src/main/java/
│   │   └── com/seckill/common/
│   │       ├── constant/           # 常量定义
│   │       ├── enums/              # 枚举类
│   │       ├── exception/          # 异常定义
│   │       ├── result/             # 统一返回
│   │       └── utils/              # 工具类
├── seckill-gateway/                # 网关服务
├── seckill-service/                # 秒杀服务
│   ├── src/main/java/
│   │   └── com/seckill/service/
│   │       ├── controller/         # 控制器
│   │       ├── service/            # 业务逻辑
│   │       ├── mapper/             # 数据访问
│   │       ├── entity/             # 实体类
│   │       ├── dto/                # 数据传输对象
│   │       ├── config/             # 配置类
│   │       └── mq/                 # 消息队列
└── seckill-admin/                  # 管理后台

4.2 实体类定义

package com.seckill.service.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 秒杀商品实体
*/
@Data
@TableName("seckill_goods")
public class SeckillGoods {
@TableId(type = IdType.AUTO)
private Long id;
private Long goodsId;
private String goodsName;
private String goodsImg;
private BigDecimal originalPrice;
private BigDecimal seckillPrice;
private Integer stockCount;
private LocalDateTime startTime;
private LocalDateTime endTime;
private Integer status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
package com.seckill.service.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 秒杀订单实体
*/
@Data
@TableName("seckill_order")
public class SeckillOrder {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long userId;
private Long goodsId;
private Long seckillGoodsId;
private String goodsName;
private BigDecimal goodsPrice;
private Integer orderStatus;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
private LocalDateTime payTime;
}

4.3 统一响应结果

package com.seckill.common.result;
import lombok.Data;
import java.io.Serializable;
/**
* 统一响应结果
*/
@Data
public class Result<T> implements Serializable {
  private static final long serialVersionUID = 1L;
  private Integer code;
  private String message;
  private T data;
  private Result() {}
  public static <T> Result<T> success() {
    return success(null);
    }
    public static <T> Result<T> success(T data) {
      Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage("success");
        result.setData(data);
        return result;
        }
        public static <T> Result<T> error(Integer code, String message) {
          Result<T> result = new Result<>();
            result.setCode(code);
            result.setMessage(message);
            return result;
            }
            public static <T> Result<T> error(ResultCode resultCode) {
              return error(resultCode.getCode(), resultCode.getMessage());
              }
              }
package com.seckill.common.result;
import lombok.Getter;
/**
* 响应状态码枚举
*/
@Getter
public enum ResultCode {
SUCCESS(200, "操作成功"),
ERROR(500, "系统错误"),
// 秒杀相关
SECKILL_NOT_START(1001, "秒杀活动未开始"),
SECKILL_END(1002, "秒杀活动已结束"),
SECKILL_STOCK_EMPTY(1003, "商品已售罄"),
SECKILL_REPEAT(1004, "请勿重复秒杀"),
SECKILL_LIMIT(1005, "访问过于频繁,请稍后重试"),
SECKILL_QUEUE(1006, "排队中,请稍后查询结果"),
// 用户相关
USER_NOT_LOGIN(2001, "用户未登录"),
USER_NOT_EXIST(2002, "用户不存在");
private final Integer code;
private final String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}

五、限流与防刷策略

5.1 限流架构

                    +----------------------+
                    |      用户请求         |
                    +----------+-----------+
                               |
                    +----------v-----------+
                    |    Nginx限流         |
                    |  limit_req_zone      |
                    +----------+-----------+
                               |
                    +----------v-----------+
                    |   Gateway限流        |
                    |  Sentinel/自定义     |
                    +----------+-----------+
                               |
                    +----------v-----------+
                    |   接口级别限流        |
                    |  @RateLimiter        |
                    +----------+-----------+
                               |
                    +----------v-----------+
                    |   用户级别限流        |
                    |  Redis + Lua         |
                    +----------------------+

5.2 Redis + Lua 实现分布式限流

package com.seckill.service.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
/**
* Redis配置类
*/
@Configuration
public class RedisConfig {
/**
* 限流Lua脚本
*/
@Bean
public DefaultRedisScript<Long> rateLimitScript() {
  DefaultRedisScript<Long> script = new DefaultRedisScript<>();
    script.setScriptSource(new ResourceScriptSource(
    new ClassPathResource("lua/rate_limit.lua")));
    script.setResultType(Long.class);
    return script;
    }
    /**
    * 库存扣减Lua脚本
    */
    @Bean
    public DefaultRedisScript<Long> stockDeductScript() {
      DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptSource(new ResourceScriptSource(
        new ClassPathResource("lua/stock_deduct.lua")));
        script.setResultType(Long.class);
        return script;
        }
        }

rate_limit.lua - 滑动窗口限流脚本:

-- 限流Lua脚本:滑动窗口算法
-- KEYS[1]: 限流的key
-- ARGV[1]: 限流阈值
-- ARGV[2]: 时间窗口(秒)
-- ARGV[3]: 当前时间戳
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
-- 移除时间窗口外的记录
redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000)
-- 获取当前窗口内的请求数
local count = redis.call('ZCARD', key)
if count < limit then
-- 添加当前请求
redis.call('ZADD', key, now, now .. '-' .. math.random())
redis.call('EXPIRE', key, window)
return 1  -- 允许通过
else
return 0  -- 限流
end

5.3 限流服务实现

package com.seckill.service.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
/**
* 限流服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class RateLimitService {
private final RedisTemplate<String, Object> redisTemplate;
  private final RedisScript<Long> rateLimitScript;
    private static final String RATE_LIMIT_PREFIX = "seckill:limit:";
    /**
    * 检查是否被限流
    * @param userId 用户ID
    * @param goodsId 商品ID
    * @param limit 限流阈值
    * @param windowSeconds 时间窗口(秒)
    * @return true-允许通过, false-被限流
    */
    public boolean tryAcquire(Long userId, Long goodsId, int limit, int windowSeconds) {
    String key = RATE_LIMIT_PREFIX + goodsId + ":" + userId;
    long now = System.currentTimeMillis();
    Long result = redisTemplate.execute(
    rateLimitScript,
    Collections.singletonList(key),
    limit, windowSeconds, now
    );
    return result != null && result == 1;
    }
    /**
    * IP维度限流
    */
    public boolean tryAcquireByIp(String ip, int limit, int windowSeconds) {
    String key = RATE_LIMIT_PREFIX + "ip:" + ip;
    long now = System.currentTimeMillis();
    Long result = redisTemplate.execute(
    rateLimitScript,
    Collections.singletonList(key),
    limit, windowSeconds, now
    );
    return result != null && result == 1;
    }
    }

5.4 自定义限流注解

package com.seckill.service.annotation;
import java.lang.annotation.*;
/**
* 限流注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
/**
* 限流key前缀
*/
String prefix() default "";
/**
* 限流阈值
*/
int limit() default 10;
/**
* 时间窗口(秒)
*/
int window() default 1;
/**
* 限流维度:USER/IP/GLOBAL
*/
LimitType type() default LimitType.USER;
enum LimitType {
USER,   // 用户维度
IP,     // IP维度
GLOBAL  // 全局维度
}
}
package com.seckill.service.aspect;
import com.seckill.common.exception.SeckillException;
import com.seckill.common.result.ResultCode;
import com.seckill.service.annotation.RateLimit;
import com.seckill.service.service.RateLimitService;
import com.seckill.service.utils.IpUtils;
import com.seckill.service.utils.UserContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* 限流切面
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class RateLimitAspect {
private final RateLimitService rateLimitService;
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
boolean allowed = false;
switch (rateLimit.type()) {
case USER:
Long userId = UserContext.getCurrentUserId();
allowed = rateLimitService.tryAcquire(
userId, 0L, rateLimit.limit(), rateLimit.window());
break;
case IP:
HttpServletRequest request = getRequest();
String ip = IpUtils.getClientIp(request);
allowed = rateLimitService.tryAcquireByIp(
ip, rateLimit.limit(), rateLimit.window());
break;
case GLOBAL:
// 全局限流实现
break;
}
if (!allowed) {
log.warn("请求被限流: type={}", rateLimit.type());
throw new SeckillException(ResultCode.SECKILL_LIMIT);
}
return point.proceed();
}
private HttpServletRequest getRequest() {
ServletRequestAttributes attrs = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
return attrs != null ? attrs.getRequest() : null;
}
}

六、库存扣减方案

6.1 库存扣减方案对比

+------------------+------------------+------------------+------------------+
|      方案        |       优点       |       缺点       |     适用场景     |
+------------------+------------------+------------------+------------------+
|   数据库扣减     |  数据强一致性    |  性能差,并发低   |   低并发场景     |
+------------------+------------------+------------------+------------------+
|  Redis预扣减    |  性能高          |  需处理一致性     |   高并发场景     |
+------------------+------------------+------------------+------------------+
|  Redis+MQ异步   |  性能最高        |  最终一致性       |   超高并发       |
+------------------+------------------+------------------+------------------+

6.2 Redis库存预热

package com.seckill.service.service;
import com.seckill.service.entity.SeckillGoods;
import com.seckill.service.mapper.SeckillGoodsMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 库存预热服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class StockWarmUpService {
private final RedisTemplate<String, Object> redisTemplate;
  private final SeckillGoodsMapper seckillGoodsMapper;
  private static final String STOCK_KEY_PREFIX = "seckill:stock:";
  private static final String GOODS_INFO_PREFIX = "seckill:goods:";
  /**
  * 系统启动时预热库存
  */
  @PostConstruct
  public void warmUpOnStartup() {
  log.info("系统启动,开始预热秒杀库存...");
  warmUpStock();
  }
  /**
  * 定时任务:每5分钟检查并预热即将开始的秒杀活动
  */
  @Scheduled(cron = "0 */5 * * * ?")
  public void scheduledWarmUp() {
  log.info("定时任务:检查秒杀活动库存预热...");
  warmUpStock();
  }
  /**
  * 预热库存到Redis
  */
  public void warmUpStock() {
  // 查询即将开始或正在进行的秒杀活动(未来30分钟内开始的)
  LocalDateTime now = LocalDateTime.now();
  LocalDateTime future = now.plusMinutes(30);
  List<SeckillGoods> goodsList = seckillGoodsMapper.selectActiveGoods(now, future);
    for (SeckillGoods goods : goodsList) {
    String stockKey = STOCK_KEY_PREFIX + goods.getId();
    String infoKey = GOODS_INFO_PREFIX + goods.getId();
    // 检查是否已预热
    if (Boolean.TRUE.equals(redisTemplate.hasKey(stockKey))) {
    log.debug("商品[{}]库存已预热,跳过", goods.getId());
    continue;
    }
    // 预热库存
    redisTemplate.opsForValue().set(stockKey, goods.getStockCount());
    // 预热商品信息
    redisTemplate.opsForValue().set(infoKey, goods);
    // 设置过期时间(活动结束后1小时过期)
    long expireSeconds = java.time.Duration.between(
    now, goods.getEndTime().plusHours(1)).getSeconds();
    redisTemplate.expire(stockKey, expireSeconds, TimeUnit.SECONDS);
    redisTemplate.expire(infoKey, expireSeconds, TimeUnit.SECONDS);
    log.info("商品[{}]库存预热完成,库存数量: {}", goods.getId(), goods.getStockCount());
    }
    }
    /**
    * 手动预热指定商品
    */
    public void warmUpByGoodsId(Long seckillGoodsId) {
    SeckillGoods goods = seckillGoodsMapper.selectById(seckillGoodsId);
    if (goods == null) {
    log.warn("商品[{}]不存在", seckillGoodsId);
    return;
    }
    String stockKey = STOCK_KEY_PREFIX + goods.getId();
    redisTemplate.opsForValue().set(stockKey, goods.getStockCount());
    log.info("商品[{}]手动预热完成,库存: {}", seckillGoodsId, goods.getStockCount());
    }
    }

6.3 Redis + Lua 原子扣减库存

stock_deduct.lua - 库存扣减脚本:

-- 库存扣减Lua脚本
-- KEYS[1]: 库存key
-- KEYS[2]: 已购买用户集合key
-- ARGV[1]: 用户ID
-- ARGV[2]: 扣减数量(默认1)
local stockKey = KEYS[1]
local boughtKey = KEYS[2]
local userId = ARGV[1]
local deductNum = tonumber(ARGV[2]) or 1
-- 检查用户是否已购买
if redis.call('SISMEMBER', boughtKey, userId) == 1 then
return -1  -- 重复购买
end
-- 获取当前库存
local stock = tonumber(redis.call('GET', stockKey) or 0)
if stock < deductNum then
return -2  -- 库存不足
end
-- 扣减库存
redis.call('DECRBY', stockKey, deductNum)
-- 记录用户已购买
redis.call('SADD', boughtKey, userId)
-- 返回剩余库存
return stock - deductNum

6.4 库存服务实现

package com.seckill.service.service;
import com.seckill.common.exception.SeckillException;
import com.seckill.common.result.ResultCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Arrays;
/**
* 库存服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class StockService {
private final RedisTemplate<String, Object> redisTemplate;
  private final RedisScript<Long> stockDeductScript;
    private static final String STOCK_KEY_PREFIX = "seckill:stock:";
    private static final String BOUGHT_KEY_PREFIX = "seckill:bought:";
    /**
    * 预扣减库存(Redis)
    * @return 剩余库存,-1表示重复购买,-2表示库存不足
    */
    public long preDeductStock(Long seckillGoodsId, Long userId) {
    String stockKey = STOCK_KEY_PREFIX + seckillGoodsId;
    String boughtKey = BOUGHT_KEY_PREFIX + seckillGoodsId;
    Long result = redisTemplate.execute(
    stockDeductScript,
    Arrays.asList(stockKey, boughtKey),
    userId.toString(), 1
    );
    if (result == null) {
    throw new SeckillException(ResultCode.ERROR);
    }
    return result;
    }
    /**
    * 回滚库存(订单取消或支付超时)
    */
    public void rollbackStock(Long seckillGoodsId, Long userId) {
    String stockKey = STOCK_KEY_PREFIX + seckillGoodsId;
    String boughtKey = BOUGHT_KEY_PREFIX + seckillGoodsId;
    // 库存+1
    redisTemplate.opsForValue().increment(stockKey);
    // 移除已购买记录
    redisTemplate.opsForSet().remove(boughtKey, userId.toString());
    log.info("库存回滚成功,seckillGoodsId={}, userId={}", seckillGoodsId, userId);
    }
    /**
    * 获取剩余库存
    */
    public Integer getStock(Long seckillGoodsId) {
    String stockKey = STOCK_KEY_PREFIX + seckillGoodsId;
    Object stock = redisTemplate.opsForValue().get(stockKey);
    return stock != null ? Integer.parseInt(stock.toString()) : 0;
    }
    /**
    * 检查用户是否已购买
    */
    public boolean hasUserBought(Long seckillGoodsId, Long userId) {
    String boughtKey = BOUGHT_KEY_PREFIX + seckillGoodsId;
    return Boolean.TRUE.equals(
    redisTemplate.opsForSet().isMember(boughtKey, userId.toString()));
    }
    }

七、异步下单与消息队列

7.1 异步下单流程

+------------+     +------------+     +------------+     +------------+
|  用户请求   | --> |  库存预检   | --> |  Redis扣减  | --> |  发送MQ    |
+------------+     +------------+     +------------+     +------------+
                                                               |
                   +-------------------------------------------+
                   |
                   v
+------------+     +------------+     +------------+     +------------+
|  返回排队   | <-- |  生成排队号  | <-- |   入队成功   | <-- | Producer  |
+------------+     +------------+     +------------+     +------------+
+------------+     +------------+     +------------+     +------------+
| Consumer   | --> |  创建订单   | --> |  扣减DB库存  | --> |  写入结果  |
+------------+     +------------+     +------------+     +------------+

7.2 RocketMQ消息生产者

package com.seckill.service.mq;
import com.alibaba.fastjson.JSON;
import com.seckill.service.dto.SeckillMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
/**
* 秒杀消息生产者
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SeckillMessageProducer {
private final RocketMQTemplate rocketMQTemplate;
private static final String SECKILL_TOPIC = "seckill-order-topic";
/**
* 发送秒杀消息(异步)
*/
public void sendSeckillMessage(SeckillMessage message) {
String jsonMessage = JSON.toJSONString(message);
rocketMQTemplate.asyncSend(SECKILL_TOPIC,
MessageBuilder.withPayload(jsonMessage).build(),
new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("秒杀消息发送成功: msgId={}, userId={}, goodsId={}",
sendResult.getMsgId(), message.getUserId(), message.getSeckillGoodsId());
}
@Override
public void onException(Throwable e) {
log.error("秒杀消息发送失败: userId={}, goodsId={}",
message.getUserId(), message.getSeckillGoodsId(), e);
// 发送失败,回滚Redis库存
// stockService.rollbackStock(message.getSeckillGoodsId(), message.getUserId());
}
});
}
/**
* 发送秒杀消息(同步,用于需要确保消息发送成功的场景)
*/
public SendResult sendSeckillMessageSync(SeckillMessage message) {
String jsonMessage = JSON.toJSONString(message);
return rocketMQTemplate.syncSend(SECKILL_TOPIC,
MessageBuilder.withPayload(jsonMessage).build());
}
}

7.3 消息DTO

package com.seckill.service.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 秒杀消息DTO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SeckillMessage implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 秒杀商品ID
*/
private Long seckillGoodsId;
/**
* 商品ID
*/
private Long goodsId;
/**
* 排队号
*/
private String queueNo;
/**
* 请求时间戳
*/
private Long timestamp;
}

7.4 RocketMQ消息消费者

package com.seckill.service.mq;
import com.alibaba.fastjson.JSON;
import com.seckill.service.dto.SeckillMessage;
import com.seckill.service.service.OrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
/**
* 秒杀消息消费者
*/
@Slf4j
@Component
@RequiredArgsConstructor
@RocketMQMessageListener(
topic = "seckill-order-topic",
consumerGroup = "seckill-order-consumer-group",
consumeMode = ConsumeMode.ORDERLY  // 顺序消费
)
public class SeckillMessageConsumer implements RocketMQListener<String> {
  private final OrderService orderService;
  @Override
  public void onMessage(String message) {
  log.info("收到秒杀消息: {}", message);
  try {
  SeckillMessage seckillMessage = JSON.parseObject(message, SeckillMessage.class);
  // 创建订单
  orderService.createOrder(seckillMessage);
  log.info("订单创建成功: userId={}, goodsId={}",
  seckillMessage.getUserId(), seckillMessage.getSeckillGoodsId());
  } catch (Exception e) {
  log.error("处理秒杀消息失败: {}", message, e);
  // 这里可以发送到死信队列,或者记录到数据库待后续处理
  throw e;  // 抛出异常,消息将被重试
  }
  }
  }

7.5 订单服务

package com.seckill.service.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.seckill.common.exception.SeckillException;
import com.seckill.common.result.ResultCode;
import com.seckill.service.dto.SeckillMessage;
import com.seckill.service.entity.SeckillGoods;
import com.seckill.service.entity.SeckillOrder;
import com.seckill.service.mapper.SeckillGoodsMapper;
import com.seckill.service.mapper.SeckillOrderMapper;
import com.seckill.service.utils.OrderNoGenerator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.TimeUnit;
/**
* 订单服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private final SeckillOrderMapper orderMapper;
private final SeckillGoodsMapper goodsMapper;
private final StockService stockService;
private final RedisTemplate<String, Object> redisTemplate;
  private static final String ORDER_RESULT_PREFIX = "seckill:result:";
  /**
  * 创建订单(异步消费时调用)
  */
  @Transactional(rollbackFor = Exception.class)
  public void createOrder(SeckillMessage message) {
  Long userId = message.getUserId();
  Long seckillGoodsId = message.getSeckillGoodsId();
  // 1. 再次检查是否已下单(幂等性)
  SeckillOrder existOrder = orderMapper.selectOne(
  new LambdaQueryWrapper<SeckillOrder>()
    .eq(SeckillOrder::getUserId, userId)
    .eq(SeckillOrder::getSeckillGoodsId, seckillGoodsId)
    );
    if (existOrder != null) {
    log.warn("用户已下单,跳过: userId={}, goodsId={}", userId, seckillGoodsId);
    // 写入成功结果
    writeResult(userId, seckillGoodsId, existOrder.getOrderNo(), true, "订单已存在");
    return;
    }
    // 2. 查询商品信息
    SeckillGoods goods = goodsMapper.selectById(seckillGoodsId);
    if (goods == null) {
    log.error("商品不存在: seckillGoodsId={}", seckillGoodsId);
    writeResult(userId, seckillGoodsId, null, false, "商品不存在");
    return;
    }
    // 3. 数据库扣减库存(乐观锁)
    int affected = goodsMapper.deductStock(seckillGoodsId, 1);
    if (affected == 0) {
    log.warn("数据库库存不足: seckillGoodsId={}", seckillGoodsId);
    // 回滚Redis库存
    stockService.rollbackStock(seckillGoodsId, userId);
    writeResult(userId, seckillGoodsId, null, false, "库存不足");
    return;
    }
    // 4. 创建订单
    String orderNo = OrderNoGenerator.generate();
    SeckillOrder order = new SeckillOrder();
    order.setOrderNo(orderNo);
    order.setUserId(userId);
    order.setGoodsId(goods.getGoodsId());
    order.setSeckillGoodsId(seckillGoodsId);
    order.setGoodsName(goods.getGoodsName());
    order.setGoodsPrice(goods.getSeckillPrice());
    order.setOrderStatus(0);  // 待支付
    orderMapper.insert(order);
    // 5. 写入秒杀结果
    writeResult(userId, seckillGoodsId, orderNo, true, "秒杀成功");
    log.info("订单创建成功: orderNo={}, userId={}, goodsId={}",
    orderNo, userId, seckillGoodsId);
    }
    /**
    * 写入秒杀结果到Redis
    */
    private void writeResult(Long userId, Long seckillGoodsId, String orderNo,
    boolean success, String message) {
    String key = ORDER_RESULT_PREFIX + userId + ":" + seckillGoodsId;
    SeckillResult result = new SeckillResult(success, orderNo, message);
    redisTemplate.opsForValue().set(key, result, 30, TimeUnit.MINUTES);
    }
    /**
    * 查询秒杀结果
    */
    public SeckillResult getSeckillResult(Long userId, Long seckillGoodsId) {
    String key = ORDER_RESULT_PREFIX + userId + ":" + seckillGoodsId;
    return (SeckillResult) redisTemplate.opsForValue().get(key);
    }
    /**
    * 秒杀结果内部类
    */
    @lombok.Data
    @lombok.AllArgsConstructor
    @lombok.NoArgsConstructor
    public static class SeckillResult {
    private boolean success;
    private String orderNo;
    private String message;
    }
    }

7.6 Mapper层

package com.seckill.service.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.seckill.service.entity.SeckillGoods;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
public interface SeckillGoodsMapper extends BaseMapper<SeckillGoods> {
  /**
  * 乐观锁扣减库存
  */
  @Update("UPDATE seckill_goods SET stock_count = stock_count - #{count} " +
  "WHERE id = #{id} AND stock_count >= #{count}")
  int deductStock(@Param("id") Long id, @Param("count") Integer count);
  /**
  * 查询活跃的秒杀活动
  */
  @Select("SELECT * FROM seckill_goods " +
  "WHERE start_time <= #{future} AND end_time >= #{now}")
    List<SeckillGoods> selectActiveGoods(@Param("now") LocalDateTime now,
      @Param("future") LocalDateTime future);
      }

八、秒杀接口实现

8.1 秒杀Controller

package com.seckill.service.controller;
import com.seckill.common.result.Result;
import com.seckill.common.result.ResultCode;
import com.seckill.service.annotation.RateLimit;
import com.seckill.service.dto.SeckillRequest;
import com.seckill.service.service.OrderService;
import com.seckill.service.service.SeckillService;
import com.seckill.service.utils.UserContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 秒杀接口
*/
@Slf4j
@RestController
@RequestMapping("/seckill")
@RequiredArgsConstructor
public class SeckillController {
private final SeckillService seckillService;
private final OrderService orderService;
/**
* 执行秒杀
*/
@PostMapping("/do")
@RateLimit(limit = 5, window = 1, type = RateLimit.LimitType.USER)
public Result<String> doSeckill(@Valid @RequestBody SeckillRequest request) {
  Long userId = UserContext.getCurrentUserId();
  Long seckillGoodsId = request.getSeckillGoodsId();
  log.info("秒杀请求: userId={}, goodsId={}", userId, seckillGoodsId);
  // 执行秒杀
  String queueNo = seckillService.doSeckill(userId, seckillGoodsId);
  return Result.success(queueNo);
  }
  /**
  * 查询秒杀结果
  */
  @GetMapping("/result/{seckillGoodsId}")
  public Result<OrderService.SeckillResult> getSeckillResult(
    @PathVariable Long seckillGoodsId) {
    Long userId = UserContext.getCurrentUserId();
    OrderService.SeckillResult result = orderService.getSeckillResult(userId, seckillGoodsId);
    if (result == null) {
    // 还在排队
    return Result.error(ResultCode.SECKILL_QUEUE);
    }
    return Result.success(result);
    }
    /**
    * 获取秒杀商品列表
    */
    @GetMapping("/goods/list")
    public Result<?> getGoodsList() {
      return Result.success(seckillService.getSeckillGoodsList());
      }
      /**
      * 获取秒杀商品详情
      */
      @GetMapping("/goods/{id}")
      public Result<?> getGoodsDetail(@PathVariable Long id) {
        return Result.success(seckillService.getSeckillGoodsDetail(id));
        }
        }

8.2 秒杀核心服务

package com.seckill.service.service;
import com.seckill.common.exception.SeckillException;
import com.seckill.common.result.ResultCode;
import com.seckill.service.dto.SeckillMessage;
import com.seckill.service.entity.SeckillGoods;
import com.seckill.service.mapper.SeckillGoodsMapper;
import com.seckill.service.mq.SeckillMessageProducer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
/**
* 秒杀核心服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SeckillService {
private final StockService stockService;
private final SeckillMessageProducer messageProducer;
private final SeckillGoodsMapper seckillGoodsMapper;
private final RedisTemplate<String, Object> redisTemplate;
  private static final String GOODS_INFO_PREFIX = "seckill:goods:";
  /**
  * 执行秒杀
  */
  public String doSeckill(Long userId, Long seckillGoodsId) {
  // 1. 检查秒杀活动状态
  SeckillGoods goods = getSeckillGoods(seckillGoodsId);
  checkSeckillTime(goods);
  // 2. 检查用户是否已购买(内存标记优化,减少Redis访问)
  if (stockService.hasUserBought(seckillGoodsId, userId)) {
  throw new SeckillException(ResultCode.SECKILL_REPEAT);
  }
  // 3. Redis预扣减库存
  long stockResult = stockService.preDeductStock(seckillGoodsId, userId);
  if (stockResult == -1) {
  throw new SeckillException(ResultCode.SECKILL_REPEAT);
  }
  if (stockResult == -2) {
  // 标记商品已售罄(内存标记)
  markGoodsSoldOut(seckillGoodsId);
  throw new SeckillException(ResultCode.SECKILL_STOCK_EMPTY);
  }
  // 4. 生成排队号
  String queueNo = generateQueueNo(userId, seckillGoodsId);
  // 5. 发送MQ消息,异步创建订单
  SeckillMessage message = SeckillMessage.builder()
  .userId(userId)
  .seckillGoodsId(seckillGoodsId)
  .goodsId(goods.getGoodsId())
  .queueNo(queueNo)
  .timestamp(System.currentTimeMillis())
  .build();
  messageProducer.sendSeckillMessage(message);
  log.info("秒杀请求已入队: userId={}, goodsId={}, queueNo={}",
  userId, seckillGoodsId, queueNo);
  return queueNo;
  }
  /**
  * 获取秒杀商品(优先从缓存获取)
  */
  private SeckillGoods getSeckillGoods(Long seckillGoodsId) {
  String key = GOODS_INFO_PREFIX + seckillGoodsId;
  SeckillGoods goods = (SeckillGoods) redisTemplate.opsForValue().get(key);
  if (goods == null) {
  goods = seckillGoodsMapper.selectById(seckillGoodsId);
  if (goods == null) {
  throw new SeckillException(ResultCode.ERROR);
  }
  }
  return goods;
  }
  /**
  * 检查秒杀时间
  */
  private void checkSeckillTime(SeckillGoods goods) {
  LocalDateTime now = LocalDateTime.now();
  if (now.isBefore(goods.getStartTime())) {
  throw new SeckillException(ResultCode.SECKILL_NOT_START);
  }
  if (now.isAfter(goods.getEndTime())) {
  throw new SeckillException(ResultCode.SECKILL_END);
  }
  }
  /**
  * 标记商品售罄
  */
  private void markGoodsSoldOut(Long seckillGoodsId) {
  // 可以使用本地缓存或Redis标记
  String key = "seckill:soldout:" + seckillGoodsId;
  redisTemplate.opsForValue().set(key, true);
  }
  /**
  * 生成排队号
  */
  private String generateQueueNo(Long userId, Long seckillGoodsId) {
  return String.format("%d-%d-%s",
  seckillGoodsId, userId, UUID.randomUUID().toString().substring(0, 8));
  }
  /**
  * 获取秒杀商品列表
  */
  public List<SeckillGoods> getSeckillGoodsList() {
    LocalDateTime now = LocalDateTime.now();
    return seckillGoodsMapper.selectActiveGoods(now, now.plusDays(1));
    }
    /**
    * 获取秒杀商品详情
    */
    public SeckillGoods getSeckillGoodsDetail(Long id) {
    return getSeckillGoods(id);
    }
    }

九、分布式锁实现

9.1 Redisson分布式锁

package com.seckill.service.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* 分布式锁服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DistributedLockService {
private final RedissonClient redissonClient;
private static final String LOCK_PREFIX = "seckill:lock:";
/**
* 尝试获取锁并执行操作
*/
public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime,
  TimeUnit unit, Supplier<T> supplier) {
    RLock lock = redissonClient.getLock(LOCK_PREFIX + lockKey);
    try {
    boolean acquired = lock.tryLock(waitTime, leaseTime, unit);
    if (!acquired) {
    log.warn("获取锁失败: lockKey={}", lockKey);
    return null;
    }
    log.debug("获取锁成功: lockKey={}", lockKey);
    return supplier.get();
    } catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    log.error("获取锁被中断: lockKey={}", lockKey, e);
    return null;
    } finally {
    if (lock.isHeldByCurrentThread()) {
    lock.unlock();
    log.debug("释放锁: lockKey={}", lockKey);
    }
    }
    }
    /**
    * 使用看门狗机制的锁(自动续期)
    */
    public <T> T executeWithWatchdog(String lockKey, Supplier<T> supplier) {
      RLock lock = redissonClient.getLock(LOCK_PREFIX + lockKey);
      try {
      // 不指定leaseTime,使用看门狗自动续期
      lock.lock();
      return supplier.get();
      } finally {
      if (lock.isHeldByCurrentThread()) {
      lock.unlock();
      }
      }
      }
      }

9.2 Redisson配置

package com.seckill.service.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Redisson配置
*/
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password:}")
private String password;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// 单机模式
config.useSingleServer()
.setAddress("redis://" + host + ":" + port)
.setPassword(password.isEmpty() ? null : password)
.setDatabase(0)
.setConnectionMinimumIdleSize(10)
.setConnectionPoolSize(64);
// 集群模式(生产环境推荐)
// config.useClusterServers()
//     .addNodeAddress("redis://node1:6379", "redis://node2:6379")
//     .setPassword(password);
return Redisson.create(config);
}
}

十、高可用与容错

10.1 Sentinel熔断降级配置

package com.seckill.service.config;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* Sentinel配置
*/
@Configuration
public class SentinelConfig {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
@PostConstruct
public void initRules() {
initFlowRules();
initDegradeRules();
}
/**
* 初始化限流规则
*/
private void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
  // 秒杀接口限流:每秒最多处理10000个请求
  FlowRule seckillRule = new FlowRule();
  seckillRule.setResource("doSeckill");
  seckillRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
  seckillRule.setCount(10000);
  seckillRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
  seckillRule.setWarmUpPeriodSec(10);  // 预热时间10秒
  rules.add(seckillRule);
  // 查询接口限流
  FlowRule queryRule = new FlowRule();
  queryRule.setResource("getSeckillResult");
  queryRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
  queryRule.setCount(50000);
  rules.add(queryRule);
  FlowRuleManager.loadRules(rules);
  }
  /**
  * 初始化熔断规则
  */
  private void initDegradeRules() {
  List<DegradeRule> rules = new ArrayList<>();
    // 慢调用比例熔断
    DegradeRule slowRule = new DegradeRule();
    slowRule.setResource("doSeckill");
    slowRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
    slowRule.setCount(100);  // 阈值RT(毫秒)
    slowRule.setTimeWindow(10);  // 熔断时长(秒)
    slowRule.setSlowRatioThreshold(0.5);  // 慢调用比例阈值
    slowRule.setMinRequestAmount(10);  // 最小请求数
    slowRule.setStatIntervalMs(10000);  // 统计时长(毫秒)
    rules.add(slowRule);
    // 异常比例熔断
    DegradeRule exceptionRule = new DegradeRule();
    exceptionRule.setResource("doSeckill");
    exceptionRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
    exceptionRule.setCount(0.5);  // 异常比例阈值
    exceptionRule.setTimeWindow(30);  // 熔断时长(秒)
    exceptionRule.setMinRequestAmount(10);
    rules.add(exceptionRule);
    DegradeRuleManager.loadRules(rules);
    }
    }

10.2 熔断降级处理

package com.seckill.service.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.seckill.common.result.Result;
import com.seckill.common.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 带熔断降级的秒杀服务
*/
@Slf4j
@Service
public class SeckillServiceWithSentinel {
@SentinelResource(
value = "doSeckill",
blockHandler = "doSeckillBlockHandler",
fallback = "doSeckillFallback"
)
public Result<String> doSeckillWithSentinel(Long userId, Long seckillGoodsId) {
  // 秒杀逻辑
  return Result.success("排队中");
  }
  /**
  * 限流/熔断处理
  */
  public Result<String> doSeckillBlockHandler(Long userId, Long seckillGoodsId,
    BlockException ex) {
    log.warn("秒杀接口被限流/熔断: userId={}, goodsId={}", userId, seckillGoodsId);
    return Result.error(ResultCode.SECKILL_LIMIT);
    }
    /**
    * 降级处理
    */
    public Result<String> doSeckillFallback(Long userId, Long seckillGoodsId,
      Throwable ex) {
      log.error("秒杀接口异常降级: userId={}, goodsId={}", userId, seckillGoodsId, ex);
      return Result.error(500, "系统繁忙,请稍后重试");
      }
      }

十一、订单超时处理

11.1 延迟队列方案

                    +-------------------+
                    |    创建订单        |
                    +--------+----------+
                             |
                    +--------v----------+
                    |  发送延迟消息      |
                    |  (30分钟后触发)    |
                    +--------+----------+
                             |
                             | 30分钟后
                             |
                    +--------v----------+
                    |  消费延迟消息      |
                    +--------+----------+
                             |
              +--------------+--------------+
              |                             |
    +---------v---------+         +---------v---------+
    |   订单已支付       |         |    订单未支付      |
    |   忽略消息         |         |    取消订单        |
    +-------------------+         |    回滚库存        |
                                  +-------------------+

11.2 RocketMQ延迟消息实现

package com.seckill.service.mq;
import com.alibaba.fastjson.JSON;
import com.seckill.service.dto.OrderTimeoutMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
/**
* 订单超时消息生产者
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class OrderTimeoutProducer {
private final RocketMQTemplate rocketMQTemplate;
private static final String TOPIC = "order-timeout-topic";
// RocketMQ延迟级别:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
private static final int DELAY_LEVEL_30_MIN = 16;  // 30分钟
/**
* 发送订单超时检查消息
*/
public void sendOrderTimeoutMessage(String orderNo, Long userId, Long seckillGoodsId) {
OrderTimeoutMessage message = new OrderTimeoutMessage();
message.setOrderNo(orderNo);
message.setUserId(userId);
message.setSeckillGoodsId(seckillGoodsId);
message.setCreateTime(System.currentTimeMillis());
String jsonMessage = JSON.toJSONString(message);
Message<String> msg = MessageBuilder
  .withPayload(jsonMessage)
  .build();
  // 发送延迟消息
  rocketMQTemplate.syncSend(TOPIC, msg, 3000, DELAY_LEVEL_30_MIN);
  log.info("订单超时检查消息已发送: orderNo={}", orderNo);
  }
  }
package com.seckill.service.mq;
import com.alibaba.fastjson.JSON;
import com.seckill.service.dto.OrderTimeoutMessage;
import com.seckill.service.service.OrderCancelService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
/**
* 订单超时消息消费者
*/
@Slf4j
@Component
@RequiredArgsConstructor
@RocketMQMessageListener(
topic = "order-timeout-topic",
consumerGroup = "order-timeout-consumer-group"
)
public class OrderTimeoutConsumer implements RocketMQListener<String> {
  private final OrderCancelService orderCancelService;
  @Override
  public void onMessage(String message) {
  log.info("收到订单超时消息: {}", message);
  try {
  OrderTimeoutMessage timeoutMessage = JSON.parseObject(message,
  OrderTimeoutMessage.class);
  // 处理订单超时
  orderCancelService.handleOrderTimeout(timeoutMessage.getOrderNo());
  } catch (Exception e) {
  log.error("处理订单超时消息失败: {}", message, e);
  }
  }
  }

11.3 订单取消服务

package com.seckill.service.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.seckill.service.entity.SeckillOrder;
import com.seckill.service.mapper.SeckillGoodsMapper;
import com.seckill.service.mapper.SeckillOrderMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 订单取消服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderCancelService {
private final SeckillOrderMapper orderMapper;
private final SeckillGoodsMapper goodsMapper;
private final StockService stockService;
/**
* 处理订单超时
*/
@Transactional(rollbackFor = Exception.class)
public void handleOrderTimeout(String orderNo) {
// 1. 查询订单
SeckillOrder order = orderMapper.selectOne(
new LambdaQueryWrapper<SeckillOrder>()
  .eq(SeckillOrder::getOrderNo, orderNo)
  );
  if (order == null) {
  log.warn("订单不存在: orderNo={}", orderNo);
  return;
  }
  // 2. 检查订单状态
  if (order.getOrderStatus() != 0) {
  // 非待支付状态,不处理
  log.info("订单状态非待支付,跳过: orderNo={}, status={}",
  orderNo, order.getOrderStatus());
  return;
  }
  // 3. 更新订单状态为已取消
  int affected = orderMapper.update(null,
  new LambdaUpdateWrapper<SeckillOrder>()
    .eq(SeckillOrder::getOrderNo, orderNo)
    .eq(SeckillOrder::getOrderStatus, 0)  // 乐观锁
    .set(SeckillOrder::getOrderStatus, 2)  // 已取消
    );
    if (affected == 0) {
    log.info("订单状态已变更,跳过: orderNo={}", orderNo);
    return;
    }
    // 4. 恢复数据库库存
    goodsMapper.deductStock(order.getSeckillGoodsId(), -1);  // 负数表示增加
    // 5. 恢复Redis库存
    stockService.rollbackStock(order.getSeckillGoodsId(), order.getUserId());
    log.info("订单超时取消成功: orderNo={}", orderNo);
    }
    }

十二、安全防护

12.1 接口防重放

package com.seckill.service.interceptor;
import com.seckill.common.exception.SeckillException;
import com.seckill.common.result.ResultCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;
/**
* 防重放攻击拦截器
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class ReplayAttackInterceptor implements HandlerInterceptor {
private final RedisTemplate<String, Object> redisTemplate;
  private static final String NONCE_PREFIX = "seckill:nonce:";
  private static final long NONCE_EXPIRE_SECONDS = 300;  // 5分钟
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
  Object handler) {
  // 获取请求头中的签名信息
  String timestamp = request.getHeader("X-Timestamp");
  String nonce = request.getHeader("X-Nonce");
  String signature = request.getHeader("X-Signature");
  // 1. 验证时间戳(防止重放攻击)
  if (!validateTimestamp(timestamp)) {
  throw new SeckillException(ResultCode.ERROR);
  }
  // 2. 验证nonce(防止重复请求)
  if (!validateNonce(nonce)) {
  throw new SeckillException(ResultCode.ERROR);
  }
  // 3. 验证签名
  if (!validateSignature(request, timestamp, nonce, signature)) {
  throw new SeckillException(ResultCode.ERROR);
  }
  return true;
  }
  private boolean validateTimestamp(String timestamp) {
  if (timestamp == null) {
  return false;
  }
  try {
  long ts = Long.parseLong(timestamp);
  long now = System.currentTimeMillis();
  // 请求时间与服务器时间差不超过5分钟
  return Math.abs(now - ts) < NONCE_EXPIRE_SECONDS * 1000;
  } catch (NumberFormatException e) {
  return false;
  }
  }
  private boolean validateNonce(String nonce) {
  if (nonce == null || nonce.isEmpty()) {
  return false;
  }
  String key = NONCE_PREFIX + nonce;
  Boolean success = redisTemplate.opsForValue().setIfAbsent(
  key, "1", NONCE_EXPIRE_SECONDS, TimeUnit.SECONDS);
  return Boolean.TRUE.equals(success);
  }
  private boolean validateSignature(HttpServletRequest request, String timestamp,
  String nonce, String signature) {
  // 获取密钥(可以从用户Token中获取)
  String secretKey = "user_secret_key";
  // 构建签名字符串
  String signStr = String.format("%s&%s&%s&%s",
  request.getMethod(),
  request.getRequestURI(),
  timestamp,
  nonce
  );
  // 计算签名
  String expectedSignature = DigestUtils.md5DigestAsHex(
  (signStr + "&" + secretKey).getBytes());
  return expectedSignature.equals(signature);
  }
  }

12.2 验证码防护

package com.seckill.service.service;
import com.google.code.kaptcha.Producer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 验证码服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class CaptchaService {
private final Producer captchaProducer;
private final RedisTemplate<String, Object> redisTemplate;
  private static final String CAPTCHA_PREFIX = "seckill:captcha:";
  private static final long CAPTCHA_EXPIRE_SECONDS = 300;  // 5分钟
  /**
  * 生成验证码
  */
  public CaptchaResult generateCaptcha() {
  // 生成验证码文本
  String captchaText = captchaProducer.createText();
  // 生成验证码图片
  BufferedImage image = captchaProducer.createImage(captchaText);
  // 转换为Base64
  String base64Image = imageToBase64(image);
  // 生成Token
  String captchaToken = UUID.randomUUID().toString();
  // 存储到Redis
  String key = CAPTCHA_PREFIX + captchaToken;
  redisTemplate.opsForValue().set(key, captchaText,
  CAPTCHA_EXPIRE_SECONDS, TimeUnit.SECONDS);
  return new CaptchaResult(captchaToken, "data:image/png;base64," + base64Image);
  }
  /**
  * 验证验证码
  */
  public boolean verifyCaptcha(String captchaToken, String captchaCode) {
  if (captchaToken == null || captchaCode == null) {
  return false;
  }
  String key = CAPTCHA_PREFIX + captchaToken;
  String storedCode = (String) redisTemplate.opsForValue().get(key);
  // 验证后删除
  redisTemplate.delete(key);
  return captchaCode.equalsIgnoreCase(storedCode);
  }
  private String imageToBase64(BufferedImage image) {
  try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
  ImageIO.write(image, "png", baos);
  return Base64.getEncoder().encodeToString(baos.toByteArray());
  } catch (IOException e) {
  log.error("图片转Base64失败", e);
  return "";
  }
  }
  @lombok.Data
  @lombok.AllArgsConstructor
  public static class CaptchaResult {
  private String token;
  private String image;
  }
  }

十三、性能优化

13.1 本地缓存优化

package com.seckill.service.cache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.seckill.service.entity.SeckillGoods;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 本地缓存(Caffeine)
*/
@Component
public class LocalCache {
/**
* 商品信息缓存
*/
private final Cache<Long, SeckillGoods> goodsCache = Caffeine.newBuilder()
  .maximumSize(1000)
  .expireAfterWrite(1, TimeUnit.MINUTES)
  .build();
  /**
  * 商品售罄标记缓存
  */
  private final Cache<Long, Boolean> soldOutCache = Caffeine.newBuilder()
    .maximumSize(10000)
    .expireAfterWrite(10, TimeUnit.SECONDS)
    .build();
    /**
    * 用户已购买标记缓存
    */
    private final Cache<String, Boolean> userBoughtCache = Caffeine.newBuilder()
      .maximumSize(100000)
      .expireAfterWrite(5, TimeUnit.MINUTES)
      .build();
      // 商品信息缓存操作
      public void putGoods(Long goodsId, SeckillGoods goods) {
      goodsCache.put(goodsId, goods);
      }
      public SeckillGoods getGoods(Long goodsId) {
      return goodsCache.getIfPresent(goodsId);
      }
      // 售罄标记操作
      public void markSoldOut(Long goodsId) {
      soldOutCache.put(goodsId, true);
      }
      public boolean isSoldOut(Long goodsId) {
      return Boolean.TRUE.equals(soldOutCache.getIfPresent(goodsId));
      }
      // 用户购买标记操作
      public void markUserBought(Long userId, Long goodsId) {
      userBoughtCache.put(userId + ":" + goodsId, true);
      }
      public boolean hasUserBought(Long userId, Long goodsId) {
      return Boolean.TRUE.equals(userBoughtCache.getIfPresent(userId + ":" + goodsId));
      }
      }

13.2 优化后的秒杀服务

package com.seckill.service.service;
import com.seckill.common.exception.SeckillException;
import com.seckill.common.result.ResultCode;
import com.seckill.service.cache.LocalCache;
import com.seckill.service.dto.SeckillMessage;
import com.seckill.service.entity.SeckillGoods;
import com.seckill.service.mq.SeckillMessageProducer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.UUID;
/**
* 优化后的秒杀服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OptimizedSeckillService {
private final LocalCache localCache;
private final StockService stockService;
private final SeckillMessageProducer messageProducer;
/**
* 优化后的秒杀流程
*/
public String doSeckill(Long userId, Long seckillGoodsId) {
// 第1层:本地缓存检查售罄标记(内存级别,纳秒级)
if (localCache.isSoldOut(seckillGoodsId)) {
throw new SeckillException(ResultCode.SECKILL_STOCK_EMPTY);
}
// 第2层:本地缓存检查用户是否已购买
if (localCache.hasUserBought(userId, seckillGoodsId)) {
throw new SeckillException(ResultCode.SECKILL_REPEAT);
}
// 第3层:获取商品信息(本地缓存 + Redis二级缓存)
SeckillGoods goods = getGoodsWithCache(seckillGoodsId);
// 第4层:检查秒杀时间
checkSeckillTime(goods);
// 第5层:Redis预扣减库存(原子操作)
long stockResult = stockService.preDeductStock(seckillGoodsId, userId);
if (stockResult == -1) {
localCache.markUserBought(userId, seckillGoodsId);
throw new SeckillException(ResultCode.SECKILL_REPEAT);
}
if (stockResult == -2) {
localCache.markSoldOut(seckillGoodsId);
throw new SeckillException(ResultCode.SECKILL_STOCK_EMPTY);
}
// 标记用户已购买
localCache.markUserBought(userId, seckillGoodsId);
// 如果库存即将售罄,标记售罄
if (stockResult <= 0) {
localCache.markSoldOut(seckillGoodsId);
}
// 第6层:发送MQ消息,异步创建订单
String queueNo = generateQueueNo(userId, seckillGoodsId);
SeckillMessage message = SeckillMessage.builder()
.userId(userId)
.seckillGoodsId(seckillGoodsId)
.goodsId(goods.getGoodsId())
.queueNo(queueNo)
.timestamp(System.currentTimeMillis())
.build();
messageProducer.sendSeckillMessage(message);
return queueNo;
}
private SeckillGoods getGoodsWithCache(Long seckillGoodsId) {
// 先查本地缓存
SeckillGoods goods = localCache.getGoods(seckillGoodsId);
if (goods != null) {
return goods;
}
// 再查Redis/DB
goods = stockService.getGoodsFromRedis(seckillGoodsId);
if (goods != null) {
localCache.putGoods(seckillGoodsId, goods);
}
return goods;
}
private void checkSeckillTime(SeckillGoods goods) {
LocalDateTime now = LocalDateTime.now();
if (now.isBefore(goods.getStartTime())) {
throw new SeckillException(ResultCode.SECKILL_NOT_START);
}
if (now.isAfter(goods.getEndTime())) {
throw new SeckillException(ResultCode.SECKILL_END);
}
}
private String generateQueueNo(Long userId, Long seckillGoodsId) {
return String.format("%d-%d-%s",
seckillGoodsId, userId, UUID.randomUUID().toString().substring(0, 8));
}
}

13.3 请求过滤优化

                    用户请求(100万QPS)
                           |
              +------------+------------+
              |        第1层过滤        |
              |    本地缓存售罄检查     |
              |    过滤掉90%请求        |
              +------------+------------+
                           |
                     10万QPS
                           |
              +------------+------------+
              |        第2层过滤        |
              |    用户重复购买检查     |
              |    过滤掉50%请求        |
              +------------+------------+
                           |
                      5万QPS
                           |
              +------------+------------+
              |        第3层过滤        |
              |    Redis库存预扣减      |
              |    按实际库存过滤       |
              +------------+------------+
                           |
                     1000 QPS
                           |
              +------------+------------+
              |        第4层           |
              |    消息队列异步处理     |
              |    最终创建订单         |
              +------------+------------+

十四、监控与告警

14.1 Prometheus监控指标

package com.seckill.service.monitor;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
/**
* 秒杀监控指标
*/
@Component
@RequiredArgsConstructor
public class SeckillMetrics {
private final MeterRegistry meterRegistry;
private Counter seckillRequestCounter;
private Counter seckillSuccessCounter;
private Counter seckillFailCounter;
private Counter stockEmptyCounter;
private Counter repeatBuyCounter;
private Timer seckillTimer;
@PostConstruct
public void init() {
// 秒杀请求总数
seckillRequestCounter = Counter.builder("seckill_request_total")
.description("秒杀请求总数")
.register(meterRegistry);
// 秒杀成功数
seckillSuccessCounter = Counter.builder("seckill_success_total")
.description("秒杀成功总数")
.register(meterRegistry);
// 秒杀失败数
seckillFailCounter = Counter.builder("seckill_fail_total")
.description("秒杀失败总数")
.register(meterRegistry);
// 库存不足次数
stockEmptyCounter = Counter.builder("seckill_stock_empty_total")
.description("库存不足次数")
.register(meterRegistry);
// 重复购买次数
repeatBuyCounter = Counter.builder("seckill_repeat_buy_total")
.description("重复购买次数")
.register(meterRegistry);
// 秒杀耗时
seckillTimer = Timer.builder("seckill_duration")
.description("秒杀接口耗时")
.register(meterRegistry);
}
public void recordRequest() {
seckillRequestCounter.increment();
}
public void recordSuccess() {
seckillSuccessCounter.increment();
}
public void recordFail() {
seckillFailCounter.increment();
}
public void recordStockEmpty() {
stockEmptyCounter.increment();
}
public void recordRepeatBuy() {
repeatBuyCounter.increment();
}
public void recordDuration(long durationMs) {
seckillTimer.record(durationMs, TimeUnit.MILLISECONDS);
}
}

14.2 日志规范

package com.seckill.service.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 秒杀日志切面
*/
@Slf4j
@Aspect
@Component
public class SeckillLogAspect {
@Around("execution(* com.seckill.service.service.SeckillService.doSeckill(..))")
public Object logSeckill(ProceedingJoinPoint point) throws Throwable {
Object[] args = point.getArgs();
Long userId = (Long) args[0];
Long goodsId = (Long) args[1];
long startTime = System.currentTimeMillis();
String traceId = generateTraceId();
log.info("[秒杀] traceId={}, userId={}, goodsId={}, action=START",
traceId, userId, goodsId);
try {
Object result = point.proceed();
long duration = System.currentTimeMillis() - startTime;
log.info("[秒杀] traceId={}, userId={}, goodsId={}, action=SUCCESS, " +
"duration={}ms, result={}",
traceId, userId, goodsId, duration, result);
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
log.warn("[秒杀] traceId={}, userId={}, goodsId={}, action=FAIL, " +
"duration={}ms, error={}",
traceId, userId, goodsId, duration, e.getMessage());
throw e;
}
}
private String generateTraceId() {
return String.valueOf(System.nanoTime());
}
}

十五、配置文件

15.1 application.yml

server:
port: 8080
tomcat:
threads:
max: 500
min-spare: 50
accept-count: 1000
spring:
application:
name: seckill-service
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
hikari:
minimum-idle: 10
maximum-pool-size: 50
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000
# Redis配置
redis:
host: localhost
port: 6379
password:
database: 0
lettuce:
pool:
max-active: 100
max-idle: 50
min-idle: 10
max-wait: 3000ms
# RocketMQ配置
rocketmq:
name-server: localhost:9876
producer:
group: seckill-producer-group
send-message-timeout: 3000
retry-times-when-send-failed: 2
retry-times-when-send-async-failed: 2
# MyBatis-Plus配置
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 日志配置
logging:
level:
com.seckill: debug
org.springframework: info
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
# 秒杀配置
seckill:
# 库存预热提前时间(分钟)
warmup-advance-minutes: 30
# 订单超时时间(分钟)
order-timeout-minutes: 30
# 用户限流阈值
user-rate-limit: 5
# IP限流阈值
ip-rate-limit: 10

十六、总结

16.1 核心技术点回顾

+------------------+------------------+----------------------------------+
|      层级        |      技术        |             作用                 |
+------------------+------------------+----------------------------------+
|     接入层       |    Nginx         |   负载均衡、静态资源、限流        |
+------------------+------------------+----------------------------------+
|     网关层       |    Gateway       |   统一入口、认证、路由            |
+------------------+------------------+----------------------------------+
|     限流层       |  Sentinel/Lua    |   多维度限流、熔断降级            |
+------------------+------------------+----------------------------------+
|     缓存层       |   Redis/Local    |   库存预热、分布式锁              |
+------------------+------------------+----------------------------------+
|     消息层       |    RocketMQ      |   异步削峰、延迟队列              |
+------------------+------------------+----------------------------------+
|     服务层       |   Spring Boot    |   业务逻辑处理                    |
+------------------+------------------+----------------------------------+
|     数据层       |   MySQL          |   数据持久化、乐观锁              |
+------------------+------------------+----------------------------------+

16.2 关键设计原则

  1. 流量漏斗:层层过滤,减少到达数据库的请求
  2. 异步解耦:消息队列削峰填谷,提高系统吞吐量
  3. 缓存为王:多级缓存策略,减少数据库压力
  4. 原子操作:Lua脚本保证库存扣减的原子性
  5. 降级兜底:熔断降级保护系统稳定性
  6. 幂等设计:确保重复请求不会产生副作用

16.3 生产环境建议

  1. 容量规划:根据预估QPS规划服务器数量
  2. 压力测试:上线前进行全链路压测
  3. 灰度发布:逐步放开流量,观察系统表现
  4. 监控告警:完善的监控体系,及时发现问题
  5. 应急预案:准备降级方案,应对突发情况

posted @ 2026-01-22 13:05  yangykaifa  阅读(424)  评论(0)    收藏  举报