您好!作为一名资深后端开发工程师,我将为您提供秒杀和拼团活动的核心Java代码实现思路与示例。这些代码片段旨在演示关键逻辑,实际生产环境需结合具体业务需求、分布式组件(如Redis、消息队列、分布式锁)和 Spring/Spring Boot 框架进行更完善的集成。
秒杀的核心在于高并发下的库存扣减与订单创建的原子性及效率。
在生产环境中,库存通常由专门的库存服务管理,并利用 Redis 进行预热和快速扣减。
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
/**
* 库存服务模拟:实际生产环境会是更复杂的分布式库存服务
* 仅用于演示核心概念
*/
@Service
public class SeckillStockService {
@Autowired
private StringRedisTemplate redisTemplate;
// 假设商品ID为1001的秒杀商品
private static final String SECKILL_ITEM_ID = "seckill:item:1001";
private static final String SECKILL_STOCK_KEY = "seckill:stock:" + SECKILL_ITEM_ID;
// 初始化库存(假设从数据库加载)
public void initStock(int initialStock) {
// 生产环境应该从数据库加载,并缓存到Redis
redisTemplate.opsForValue().set(SECKILL_STOCK_KEY, String.valueOf(initialStock));
System.out.println("秒杀商品 " + SECKILL_ITEM_ID + " 库存初始化为: " + initialStock);
}
/**
* 尝试扣减库存
* 使用Redis的原子操作 INCRBY 实现高并发下的库存预扣
*
* @return true 扣减成功,false 库存不足
*/
public boolean deductStock() {
// 利用Redis的decrBy操作,如果结果小于0,说明库存不足
Long currentStock = redisTemplate.opsForValue().decrement(SECKILL_STOCK_KEY);
if (currentStock != null && currentStock >= 0) {
System.out.println("库存扣减成功,当前库存:" + currentStock);
return true;
} else {
// 扣减失败,将库存加回去,避免负数
if (currentStock != null) {
redisTemplate.opsForValue().increment(SECKILL_STOCK_KEY); // 加回去
}
System.out.println("库存不足或已售罄!");
return false;
}
}
/**
* 释放库存 (例如:支付超时未支付)
*/
public void releaseStock() {
redisTemplate.opsForValue().increment(SECKILL_STOCK_KEY);
System.out.println("库存释放成功!");
}
public int getCurrentStock() {
String stockStr = redisTemplate.opsForValue().get(SECKILL_STOCK_KEY);
return stockStr != null ? Integer.parseInt(stockStr) : 0;
}
}
订单服务负责接收秒杀请求,调用库存服务,并异步创建订单。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 秒杀订单服务模拟
* 实际生产环境会通过消息队列削峰
*/
@Service
public class SeckillOrderService {
@Autowired
private SeckillStockService seckillStockService;
// 模拟消息队列,实际使用Kafka/RabbitMQ
private final ExecutorService orderCreationExecutor = Executors.newFixedThreadPool(10);
/**
* 处理秒杀请求
* @param userId 用户ID
* @param seckillItemId 秒杀商品ID
* @return 订单ID或错误信息
*/
public String handleSeckillRequest(Long userId, String seckillItemId) {
// 1. 前置检查 (限流、黑名单等) - 这里简化,实际会更复杂
if (!"seckill:item:1001".equals(seckillItemId)) {
return "商品不存在或秒杀未开始";
}
// 2. 尝试预扣库存 (Redis原子操作)
if (!seckillStockService.deductStock()) {
return "秒杀失败:库存不足或已售罄";
}
// 3. 异步创建订单 (削峰填谷)
// 生产环境:将订单信息发送到消息队列,由消费者异步处理
orderCreationExecutor.submit(() -> {
try {
// 模拟订单创建和支付流程
String orderId = generateOrderId(userId, seckillItemId);
System.out.println("用户 " + userId + " 成功抢到商品 " + seckillItemId + ",生成订单:" + orderId);
// 模拟支付,实际支付会通过第三方支付平台回调
Thread.sleep(500); // 模拟支付耗时
// 支付成功后,需要进行数据库的最终库存扣减,并更新订单状态
// 数据库库存扣减通常通过数据库乐观锁或消息队列+TCC事务保证
System.out.println("订单 " + orderId + " 支付成功,准备发货...");
} catch (InterruptedException e) {
// 异常或支付超时,需要释放预扣的库存
seckillStockService.releaseStock();
System.err.println("订单创建或支付过程异常,库存已释放。");
Thread.currentThread().interrupt();
}
});
return "秒杀成功,订单正在处理中!";
}
private String generateOrderId(Long userId, String itemId) {
return "ORDER_" + userId + "_" + itemId + "_" + System.currentTimeMillis();
}
// 实际生产环境需要优雅关闭线程池
public void shutdown() {
orderCreationExecutor.shutdown();
try {
if (!orderCreationExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
orderCreationExecutor.shutdownNow();
}
} catch (InterruptedException e) {
orderCreationExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/seckill")
public class SeckillController {
@Autowired
private SeckillOrderService seckillOrderService;
@Autowired
private SeckillStockService seckillStockService;
@GetMapping("/initStock/{stock}")
public String initStock(@PathVariable int stock) {
seckillStockService.initStock(stock);
return "秒杀库存初始化成功:" + stock;
}
@GetMapping("/doSeckill/{userId}")
public String doSeckill(@PathVariable Long userId) {
// 假设秒杀商品ID固定为1001
String seckillItemId = "seckill:item:1001";
String result = seckillOrderService.handleSeckillRequest(userId, seckillItemId);
return result;
}
@GetMapping("/stock")
public String getCurrentStock() {
return "当前秒杀商品库存:" + seckillStockService.getCurrentStock();
}
}
decrBy原子操作是核心,避免超卖。拼团的核心在于管理拼团状态、人数,并在成团后触发订单处理。
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
public class GroupBuying {
public enum GroupStatus {
INITIATED, // 已发起,待成团
SUCCESS, // 已成团
FAILED // 拼团失败
}
private String groupId;
private String itemId; // 商品ID
private Long initiatorUserId; // 开团用户ID
private int requiredMembers; // 所需成团人数
private AtomicInteger currentMembers; // 当前参团人数
private GroupStatus status;
private LocalDateTime createTime;
private LocalDateTime expireTime; // 拼团截止时间
private List<GroupMember> members; // 参团成员列表
public GroupBuying(String itemId, Long initiatorUserId, int requiredMembers, long durationMinutes) {
this.groupId = "GB_" + UUID.randomUUID().toString().substring(0, 8);
this.itemId = itemId;
this.initiatorUserId = initiatorUserId;
this.requiredMembers = requiredMembers;
this.currentMembers = new AtomicInteger(1); // 开团者算一个
this.status = GroupStatus.INITIATED;
this.createTime = LocalDateTime.now();
this.expireTime = createTime.plusMinutes(durationMinutes);
this.members = new CopyOnWriteArrayList<>(); // 线程安全列表
this.members.add(new GroupMember(initiatorUserId, generateOrderId(initiatorUserId, itemId))); // 记录开团者
}
// 省略 Getter/Setter
public String getGroupId() { return groupId; }
public String getItemId() { return itemId; }
public Long getInitiatorUserId() { return initiatorUserId; }
public int getRequiredMembers() { return requiredMembers; }
public int getCurrentMembers() { return currentMembers.get(); }
public GroupStatus getStatus() { return status; }
public LocalDateTime getCreateTime() { return createTime; }
public LocalDateTime getExpireTime() { return expireTime; }
public List<GroupMember> getMembers() { return members; }
public synchronized boolean addMember(Long userId) {
if (status != GroupStatus.INITIATED || currentMembers.get() >= requiredMembers || LocalDateTime.now().isAfter(expireTime)) {
return false; // 不允许加入
}
if (members.stream().anyMatch(m -> m.getUserId().equals(userId))) {
return false; // 用户已在团中
}
currentMembers.incrementAndGet();
members.add(new GroupMember(userId, generateOrderId(userId, itemId)));
return true;
}
public synchronized void checkStatus() {
if (status == GroupStatus.INITIATED) {
if (currentMembers.get() >= requiredMembers) {
status = GroupStatus.SUCCESS;
System.out.println("拼团 " + groupId + " 成功!");
// TODO: 触发订单系统处理成团订单
} else if (LocalDateTime.now().isAfter(expireTime)) {
status = GroupStatus.FAILED;
System.out.println("拼团 " + groupId + " 失败,已过期!");
// TODO: 触发退款流程
}
}
}
private String generateOrderId(Long userId, String itemId) {
return "ORDER_" + userId + "_" + itemId + "_" + System.currentTimeMillis();
}
public static class GroupMember {
private Long userId;
private String orderId; // 参团时生成的订单ID
public GroupMember(Long userId, String orderId) {
this.userId = userId;
this.orderId = orderId;
}
public Long getUserId() { return userId; }
public String getOrderId() { return orderId; }
}
}
管理拼团的创建、加入和状态检查。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 拼团服务模拟
* 生产环境会使用数据库存储拼团信息,并结合消息队列/定时任务处理过期
*/
@Service
public class GroupBuyingService {
// 模拟存储所有拼团,生产环境用数据库+Redis
private final Map<String, GroupBuying> activeGroupBuyings = new ConcurrentHashMap<>();
// 用于定时检查过期拼团
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
@Autowired
private SeckillStockService seckillStockService; // 假设拼团也需要扣库存
public GroupBuyingService() {
// 每分钟检查一次所有活跃拼团的状态
scheduler.scheduleAtFixedRate(this::checkAllGroupBuyingsStatus, 0, 1, TimeUnit.MINUTES);
}
/**
* 发起拼团
* @param itemId 商品ID
* @param userId 开团用户ID
* @param requiredMembers 所需人数
* @param durationMinutes 持续时间(分钟)
* @return 拼团ID
*/
public String createGroupBuying(String itemId, Long userId, int requiredMembers, long durationMinutes) {
// 假设发起拼团也需要先锁定库存,成团成功再真实扣减
// 生产环境:这里可以考虑预扣一个库存或者先不扣,等成团再扣
if (!seckillStockService.deductStock()) { // 简化处理,直接扣减一个库存
System.out.println("创建拼团失败:库存不足");
return null;
}
GroupBuying group = new GroupBuying(itemId, userId, requiredMembers, durationMinutes);
activeGroupBuyings.put(group.getGroupId(), group);
System.out.println("用户 " + userId + " 发起了拼团 " + group.getGroupId() + ",目标 " + requiredMembers + " 人,截止时间 " + group.getExpireTime());
return group.getGroupId();
}
/**
* 参与拼团
* @param groupId 拼团ID
* @param userId 参团用户ID
* @return true 参与成功,false 失败
*/
public boolean joinGroupBuying(String groupId, Long userId) {
GroupBuying group = activeGroupBuyings.get(groupId);
if (group == null) {
System.out.println("参团失败:拼团不存在");
return false;
}
if (group.getStatus() != GroupBuying.GroupStatus.INITIATED) {
System.out.println("参团失败:拼团状态不允许加入(已成功/已失败)");
return false;
}
if (LocalDateTime.now().isAfter(group.getExpireTime())) {
System.out.println("参团失败:拼团已过期");
return false;
}
// 尝试加入,并进行库存预扣
// 生产环境:这里也需要分布式锁保证addMember的原子性
synchronized (group) { // 简单粗暴的锁,生产环境用Redisson等分布式锁
if (!seckillStockService.deductStock()) { // 简化处理,直接扣减一个库存
System.out.println("参团失败:库存不足");
return false;
}
if (group.addMember(userId)) {
System.out.println("用户 " + userId + " 成功加入拼团 " + groupId + ",当前人数:" + group.getCurrentMembers() + "/" + group.getRequiredMembers());
group.checkStatus(); // 检查是否成团
return true;
} else {
seckillStockService.releaseStock(); // 加入失败,释放库存
System.out.println("参团失败:无法加入(可能已满员或已加入)");
return false;
}
}
}
/**
* 定时任务:检查所有活跃拼团的状态,处理过期或已成团的
*/
private void checkAllGroupBuyingsStatus() {
System.out.println("--- 检查所有拼团状态 ---");
activeGroupBuyings.forEach((groupId, group) -> {
group.checkStatus();
if (group.getStatus() == GroupBuying.GroupStatus.SUCCESS || group.getStatus() == GroupBuying.GroupStatus.FAILED) {
// 生产环境:这里应该把已完成/失败的拼团持久化到数据库,并从活跃列表中移除
// 为了简化,这里只是打印日志
System.out.println("拼团 " + groupId + " 状态为 " + group.getStatus() + ",将移出活跃列表。");
// 实际操作应该是:
// 1. 持久化GroupBuying对象到数据库
// 2. 根据状态触发后续流程(发货/退款)
// 3. activeGroupBuyings.remove(groupId);
}
});
// 移除已完成/失败的拼团 (这里简化为手动移除,实际更复杂)
activeGroupBuyings.entrySet().removeIf(entry ->
entry.getValue().getStatus() == GroupBuying.GroupStatus.SUCCESS ||
entry.getValue().getStatus() == GroupBuying.GroupStatus.FAILED
);
}
public GroupBuying getGroupBuying(String groupId) {
return activeGroupBuyings.get(groupId);
}
public void shutdown() {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/groupbuying")
public class GroupBuyingController {
@Autowired
private GroupBuyingService groupBuyingService;
@GetMapping("/create/{itemId}/{userId}/{requiredMembers}/{durationMinutes}")
public String createGroup(
@PathVariable String itemId,
@PathVariable Long userId,
@PathVariable int requiredMembers,
@PathVariable long durationMinutes) {
String groupId = groupBuyingService.createGroupBuying(itemId, userId, requiredMembers, durationMinutes);
if (groupId != null) {
return "拼团发起成功,团号:" + groupId + ",请分享给好友!";
} else {
return "拼团发起失败,请重试。";
}
}
@GetMapping("/join/{groupId}/{userId}")
public String joinGroup(
@PathVariable String groupId,
@PathVariable Long userId) {
if (groupBuyingService.joinGroupBuying(groupId, userId)) {
return "成功加入拼团 " + groupId + "!";
} else {
return "加入拼团失败,请检查团号或拼团状态。";
}
}
@GetMapping("/status/{groupId}")
public String getGroupStatus(@PathVariable String groupId) {
GroupBuying group = groupBuyingService.getGroupBuying(groupId);
if (group != null) {
return "拼团 " + groupId + " 当前人数:" + group.getCurrentMembers() + "/" + group.getRequiredMembers() +
",状态:" + group.getStatus() + ",截止时间:" + group.getExpireTime();
} else {
return "拼团不存在。";
}
}
}
为了运行上述代码,您需要一个 Spring Boot 项目,并添加以下Maven依赖:
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data Redis Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 如果使用Redisson作为分布式锁,可选 -->
<!--
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.25.1</version> <!-- 替换为最新版本 -->
</dependency>
-->
<!-- 对于消息队列,例如Kafka -->
<!--
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
-->
并在 application.properties (或 application.yml) 中配置 Redis 连接:
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0
# spring.redis.password=
生产环境注意事项:
synchronized锁,这只适用于单机应用。在分布式环境下,必须使用Redisson、ZooKeeper或基于Redis的分布式锁来保证操作的原子性。这些Java代码片段提供了一个起点,帮助您理解秒杀和拼团的核心逻辑。实际的系统需要更全面、健壮的架构设计和实现。如果您对其中某个模块的分布式实现有更深入的疑问,欢迎随时提问!