本文目录导读:

我来为您设计一个完整的Java秒杀系统案例,涵盖核心业务逻辑、并发控制和数据库设计。
系统架构设计
graph TB
A[客户端] --> B[负载均衡]
B --> C[Web层]
C --> D[业务层]
D --> E[Redis缓存]
D --> F[MQ消息队列]
D --> G[数据库]
核心代码实现
数据库表设计
-- 秒杀商品表
CREATE TABLE seckill_goods (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
goods_name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock_count INT NOT NULL,
seckill_price DECIMAL(10,2) NOT NULL,
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
version INT DEFAULT 0,
status TINYINT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- 秒杀订单表
CREATE TABLE seckill_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
goods_id BIGINT NOT NULL,
order_no VARCHAR(32) UNIQUE NOT NULL,
seckill_price DECIMAL(10,2) NOT NULL,
status TINYINT DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
KEY idx_user_goods(user_id, goods_id)
) ENGINE=InnoDB;
-- 用户表
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
phone VARCHAR(20),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
项目依赖配置 (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Apache Commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
</project>
实体类代码
// SeckillGoods.java
@Data
@TableName("seckill_goods")
public class SeckillGoods {
@TableId(type = IdType.AUTO)
private Long id;
private String goodsName;
private BigDecimal price;
private Integer stockCount;
private BigDecimal seckillPrice;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
@Version
private Integer version;
private Integer status;
private Date createTime;
}
// SeckillOrder.java
@Data
@TableName("seckill_order")
public class SeckillOrder {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long goodsId;
private String orderNo;
private BigDecimal seckillPrice;
private Integer status;
private Date createTime;
}
Mapper接口和XML
// SeckillGoodsMapper.java
@Mapper
public interface SeckillGoodsMapper extends BaseMapper<SeckillGoods> {
// 乐观锁更新库存
@Update("UPDATE seckill_goods SET stock_count = stock_count - 1, " +
"version = version + 1 WHERE id = #{goodsId} " +
"AND stock_count > 0 AND version = #{version}")
int deductStockWithVersion(@Param("goodsId") Long goodsId,
@Param("version") Integer version);
}
// SeckillOrderMapper.java
@Mapper
public interface SeckillOrderMapper extends BaseMapper<SeckillOrder> {
// 递归查询,防止重复秒杀
@Select("SELECT * FROM seckill_order WHERE user_id = #{userId} " +
"AND goods_id = #{goodsId} LIMIT 1")
SeckillOrder findOrderByUserAndGoods(@Param("userId") Long userId,
@Param("goodsId") Long goodsId);
}
Redis配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// String序列化
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// Jackson序列化
Jackson2JsonRedisSerializer<?> jacksonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
template.setValueSerializer(jacksonSerializer);
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
// 秒杀库存预缓存
@Bean
public RedisTemplate<String, Integer> stockRedisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Integer> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
消息队列配置
@Configuration
public class RabbitMQConfig {
public static final String SECKILL_QUEUE = "seckillQueue";
public static final String SECKILL_EXCHANGE = "seckillExchange";
public static final String SECKILL_ROUTING_KEY = "seckill";
@Bean
public Queue seckillQueue() {
return new Queue(SECKILL_QUEUE, true);
}
@Bean
public TopicExchange seckillExchange() {
return new TopicExchange(SECKILL_EXCHANGE);
}
@Bean
public Binding binding() {
return BindingBuilder.bind(seckillQueue())
.to(seckillExchange())
.with(SECKILL_ROUTING_KEY);
}
// 消息转换器
@Bean
public Jackson2JsonMessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
秒杀服务核心实现
@Service
@Slf4j
public class SeckillService {
@Autowired
private SeckillGoodsMapper seckillGoodsMapper;
@Autowired
private SeckillOrderMapper seckillOrderMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
// 使用ConcurrentHashMap做本地缓存(可选)
private final ConcurrentHashMap<Long, Boolean> localizedSuccessMap =
new ConcurrentHashMap<>();
/**
* 秒杀入口(优化版本)
*/
public SeckillResult seckill(Long userId, Long goodsId) {
// 1. 参数校验
if (userId == null || goodsId == null) {
return SeckillResult.error("参数错误");
}
// 2. 检查秒杀时间段
SeckillGoods goods = seckillGoodsMapper.selectById(goodsId);
if (goods == null || goods.getStatus() != 1) {
return SeckillResult.error("商品不存在或不在秒杀活动中");
}
Date now = new Date();
if (now.before(goods.getStartTime()) || now.after(goods.getEndTime())) {
return SeckillResult.error("不在秒杀时间段内");
}
// 3. 检查是否已秒杀过(Redis布隆过滤器或Set)
String seckillKey = "seckill:user:" + userId + ":goods:" + goodsId;
Boolean alreadySeckilled = redisTemplate.hasKey(seckillKey);
if (Boolean.TRUE.equals(alreadySeckilled)) {
return SeckillResult.error("您已经秒杀过该商品");
}
// 4. Redis预减库存(使用Lua脚本保证原子性)
String stockKey = "seckill:goods:stock:" + goodsId;
Long stock = stringRedisTemplate.opsForValue().decrement(stockKey);
if (stock == null || stock < 0) {
// 库存不足,恢复库存
if (stock != null && stock < 0) {
stringRedisTemplate.opsForValue().increment(stockKey);
}
return SeckillResult.error("商品已被抢完");
}
// 5. 写入成功标识,标记已抢到
redisTemplate.opsForValue().set(seckillKey, "1", 5, TimeUnit.MINUTES);
// 6. 异步发送MQ消息,创建订单
SeckillMessage message = new SeckillMessage();
message.setUserId(userId);
message.setGoodsId(goodsId);
message.setGoodsInfo(goods);
try {
rabbitTemplate.convertAndSend(
RabbitMQConfig.SECKILL_EXCHANGE,
RabbitMQConfig.SECKILL_ROUTING_KEY,
message
);
} catch (Exception e) {
log.error("MQ发送失败,回滚库存", e);
// 回滚Redis库存
stringRedisTemplate.opsForValue().increment(stockKey);
redisTemplate.delete(seckillKey);
return SeckillResult.error("系统繁忙,请重试");
}
// 7. 返回结果
SeckillResult result = SeckillResult.success();
result.setData(true);
return result;
}
/**
* 传统查询秒杀流程(备份方案)
*/
@Transactional
public SeckillResult traditionalSeckill(Long userId, Long goodsId) {
// 校验是否已秒杀
SeckillOrder existingOrder = seckillOrderMapper
.findOrderByUserAndGoods(userId, goodsId);
if (existingOrder != null) {
return SeckillResult.error("您已经抢购过该商品");
}
// 数据库乐观锁扣减库存
SeckillGoods goods = seckillGoodsMapper.selectById(goodsId);
int result = seckillGoodsMapper.deductStockWithVersion(
goodsId, goods.getVersion());
if (result == 0) {
return SeckillResult.error("库存不足");
}
// 创建订单
SeckillOrder order = new SeckillOrder();
order.setUserId(userId);
order.setGoodsId(goodsId);
order.setOrderNo(generateOrderNo());
order.setSeckillPrice(goods.getSeckillPrice());
order.setStatus(0);
seckillOrderMapper.insert(order);
return SeckillResult.success(order);
}
/**
* 生成唯一订单号
*/
private String generateOrderNo() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
return sdf.format(new Date()) +
String.format("%04d", ThreadLocalRandom.current().nextInt(10000)) +
String.format("%04d", (int)(Math.random() * 10000));
}
}
MQ消息消费者
@Component
@Slf4j
public class SeckillMessageConsumer {
@Autowired
private SeckillOrderMapper orderMapper;
@Autowired
private SeckillGoodsMapper goodsMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@RabbitListener(queues = RabbitMQConfig.SECKILL_QUEUE)
public void handlerSeckillMessage(SeckillMessage message) {
log.info("收到秒杀消息: {}", JSON.toJSONString(message));
// 幂等性处理(通过数据库唯一索引或Redis标记)
String key = "seckill:order:" + message.getUserId() + ":" + message.getGoodsId();
Boolean alreadyCreated = redisTemplate.hasKey(key);
if (Boolean.TRUE.equals(alreadyCreated)) {
log.warn("订单已创建,跳过处理");
return;
}
try {
SeckillResult result = createOrder(message);
if (result.isSuccess()) {
// 设置标记,5分钟内重复消息直接跳过
redisTemplate.opsForValue().set(
key, "1", 5, TimeUnit.MINUTES);
log.info("订单创建成功: userId={}, goodsId={}",
message.getUserId(), message.getGoodsId());
} else {
log.warn("订单创建失败: {}", result.getMessage());
}
} catch (Exception e) {
log.error("订单创建异常", e);
// 可以选择重试或记录失败日志
}
}
@Transactional
public SeckillResult createOrder(SeckillMessage message) {
SeckillGoods goods = goodsMapper.selectById(message.getGoodsId());
if (goods == null) {
return SeckillResult.error("商品不存在");
}
// 再次检查是否存在订单
SeckillOrder existing = orderMapper.findOrderByUserAndGoods(
message.getUserId(), message.getGoodsId());
if (existing != null) {
return SeckillResult.error("订单已存在");
}
// 创建订单(数据库层保证唯一性)
SeckillOrder order = new SeckillOrder();
order.setUserId(message.getUserId());
order.setGoodsId(message.getGoodsId());
order.setOrderNo(generateOrderNo());
order.setSeckillPrice(goods.getSeckillPrice());
order.setStatus(0);
try {
orderMapper.insert(order);
return SeckillResult.success(order);
} catch (DuplicateKeyException e) {
log.error("订单重复冲突", e);
return SeckillResult.error("订单已存在");
}
}
}
Redis预加载库存
@Component
public class StockInitializer implements ApplicationRunner {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private SeckillGoodsMapper goodsMapper;
@Override
public void run(ApplicationArguments args) throws Exception {
// 项目启动时加载所有进行中的秒杀商品库存到Redis
List<SeckillGoods> goodsList = goodsMapper.selectList(
new QueryWrapper<SeckillGoods>()
.eq("status", 1)
.gt("end_time", new Date())
);
for (SeckillGoods goods : goodsList) {
String key = "seckill:goods:stock:" + goods.getId();
stringRedisTemplate.opsForValue().set(
key, String.valueOf(goods.getStockCount()));
log.info("预加载商品库存: goodsId={}, stock={}",
goods.getId(), goods.getStockCount());
}
}
}
定时任务恢复库存
@Component
@Slf4j
public class StockRecoveryTask {
@Autowired
private SeckillGoodsMapper goodsMapper;
@Scheduled(cron = "0 0/5 * * * ?") // 每5分钟执行一次
public void recoverAbnormalOrders() {
log.info("开始恢复异常订单的库存");
// 查询超时未支付的订单并恢复库存
// 可以通过关联查询或定时扫描实现
List<SeckillOrder> expiredOrders = getExpiredOrders();
for (SeckillOrder order : expiredOrders) {
// 恢复库存
goodsMapper.increaseStock(order.getGoodsId());
// 更新订单状态为已取消
updateOrderStatus(order, -1);
log.info("恢复订单库存: orderId={}, goodsId={}",
order.getId(), order.getGoodsId());
}
}
private List<SeckillOrder> getExpiredOrders() {
// 查询超时未支付订单(例如5分钟未支付)
return orderMapper.selectList(
new QueryWrapper<SeckillOrder>()
.eq("status", 0)
.lt("create_time", new Date(System.currentTimeMillis() - 5*60*1000))
);
}
}
控制器层
@RestController
@RequestMapping("/api/seckill")
public class SeckillController {
@Autowired
private SeckillService seckillService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 获取秒杀商品列表
*/
@GetMapping("/list")
public ApiResponse<List<SeckillGoods>> getSeckillGoods() {
// 从Redis或数据库查询
return ApiResponse.success(seckillGoodsService.getActiveGoods());
}
/**
* 获取商品详情
*/
@GetMapping("/detail/{goodsId}")
public ApiResponse<SeckillGoods> getDetail(@PathVariable Long goodsId) {
return ApiResponse.success(seckillGoodsService.getById(goodsId));
}
/**
* 秒杀接口
* 使用限流(RateLimiter)控制并发
*/
@PostMapping("/{goodsId}")
@RateLimiter(limits = 100, time = 1, unit = TimeUnit.SECONDS)
public ApiResponse<SeckillResult> seckill(
@PathVariable Long goodsId,
@RequestHeader("userId") Long userId) {
SeckillResult result = seckillService.seckill(userId, goodsId);
return ApiResponse.success(result);
}
/**
* 查询秒杀结果(轮询接口)
*/
@GetMapping("/result/{goodsId}")
public ApiResponse<SeckillResult> querySeckillResult(
@PathVariable Long goodsId,
@RequestHeader("userId") Long userId) {
return ApiResponse.success(seckillService.queryResult(userId, goodsId));
}
}
限流配置
@Aspect
@Component
public class RateLimiterAspect {
private static final ConcurrentHashMap<Method, com.google.common.util.concurrent.RateLimiter>
limiters = new ConcurrentHashMap<>();
@Around("@annotation(rateLimiter)")
public Object around(ProceedingJoinPoint joinPoint,
RateLimiter rateLimiter) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
com.google.common.util.concurrent.RateLimiter limiter =
limiters.computeIfAbsent(method, m ->
com.google.common.util.concurrent.RateLimiter.create(rateLimiter.limits()));
if (!limiter.tryAcquire()) {
throw new BusinessException("系统繁忙,请稍后再试");
}
return joinPoint.proceed();
}
}
统一返回结果
@Data
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(200);
response.setMessage("success");
response.setData(data);
return response;
}
public static <T> ApiResponse<T> error(int code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(code);
response.setMessage(message);
return response;
}
}
性能优化策略
多级缓存架构
| 层级 | 类型 | 失效时间 | |
|---|---|---|---|
| L1 | 本地缓存 | 商品基础信息 | 5分钟 |
| L2 | Redis | 库存数据 | 持久 |
| L3 | 数据库 | 订单数据 | 持久 |
并发控制措施
// 滑动窗口限流
public class SlidingWindowRateLimiter {
private final int maxRequests;
private final Deque<Long> timestamps = new ArrayDeque<>();
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 清理过期时间戳
while (!timestamps.isEmpty() &&
now - timestamps.peekFirst() > 60000) {
timestamps.pollFirst();
}
if (timestamps.size() < maxRequests) {
timestamps.addLast(now);
return true;
}
return false;
}
}
测试用例
@SpringBootTest
@RunWith(SpringRunner.class)
public class SeckillServiceTest {
@Autowired
private SeckillService seckillService;
@Test
public void testConcurrentSeckill() throws InterruptedException {
int threadCount = 1000;
CountDownLatch latch = new CountDownLatch(threadCount);
AtomicInteger successCount = new AtomicInteger();
AtomicInteger failCount = new AtomicInteger();
ExecutorService executor = Executors.newFixedThreadPool(50);
for (int i = 0; i < threadCount; i++) {
final int userId = i;
executor.submit(() -> {
try {
SeckillResult result = seckillService.seckill(
(long) userId, 1L);
if (result.isSuccess()) {
successCount.incrementAndGet();
} else {
failCount.incrementAndGet();
}
} catch (Exception e) {
failCount.incrementAndGet();
} finally {
latch.countDown();
}
});
}
latch.await(30, TimeUnit.SECONDS);
executor.shutdown();
log.info("成功人数: {}", successCount.get());
log.info("失败人数: {}", failCount.get());
assertEquals(10, successCount.get()); // 假设库存为10
}
}
部署架构建议
- 应用层:独立部署秒杀服务,使用Nginx+多个实例
- 数据层:MySQL主从复制,Redis集群
- 中间件:RabbitMQ集群部署
- 监控:使用Prometheus+Grafana监控系统
这个案例涵盖了秒杀系统的核心要点:库存预减、异步削峰、防重复提交、限流降级等关键技术,实际生产中还需要增加更完善的监控、告警和容灾机制。