拼团系统设计与实现-高并发下的库存与订单处理

项目背景

拼团是电商平台重要的营销手段,通过社交裂变实现用户增长。本文分享拼团系统的核心设计与实现,支撑高并发场景下的库存扣减和订单处理。

系统架构

整体架构

1
2
3
4
5
用户发起拼团 → 库存预扣 → 创建拼团单 → 分享邀请 → 成团/失败

Redis 库存层

异步订单处理(MQ)

核心流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@RestController
public class GroupBuyController {

@Autowired
private GroupBuyService groupBuyService;

/**
* 发起拼团
*/
@PostMapping("/group-buy/create")
public Result<GroupBuyOrder> createGroupBuy(
@RequestBody CreateGroupBuyRequest request) {

// 参数校验
validateRequest(request);

// 创建拼团
GroupBuyOrder order = groupBuyService.createGroupBuy(
request.getUserId(),
request.getProductId(),
request.getQuantity()
);

return Result.success(order);
}
}

核心功能实现

1. 库存预扣设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@Service
public class GroupBuyStockService {

@Autowired
private StringRedisTemplate redisTemplate;

@Autowired
private RedissonClient redissonClient;

private static final String STOCK_KEY_PREFIX = "groupbuy:stock:";
private static final String LOCK_KEY_PREFIX = "lock:groupbuy:";

/**
* 预扣库存(Redis 原子操作)
*/
public boolean preDeductStock(Long productId, Integer quantity) {
String stockKey = STOCK_KEY_PREFIX + productId;

// Lua 脚本保证原子性
String luaScript = """
local stock = tonumber(redis.call('get', KEYS[1]))
local deduct = tonumber(ARGV[1])

if stock == nil then
return -1 -- 库存未初始化
end

if stock >= deduct then
redis.call('decrby', KEYS[1], deduct)
return 1 -- 扣减成功
else
return 0 -- 库存不足
end
""";

Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(stockKey),
quantity.toString()
);

if (result == null || result == 0) {
return false; // 库存不足
}

if (result == -1) {
throw new BusinessException("库存未初始化");
}

return true;
}

/**
* 回滚库存
*/
public void rollbackStock(Long productId, Integer quantity) {
String stockKey = STOCK_KEY_PREFIX + productId;
redisTemplate.opsForValue().increment(stockKey, quantity);
}
}

2. 拼团单管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@Service
public class GroupBuyOrderService {

@Autowired
private GroupBuyOrderMapper orderMapper;

@Autowired
private GroupBuyStockService stockService;

@Autowired
private RabbitTemplate rabbitTemplate;

/**
* 创建拼团单
*/
@Transactional
public GroupBuyOrder createGroupBuyOrder(
Long userId,
Long productId,
Integer quantity) {

// 1. 预扣库存
boolean success = stockService.preDeductStock(productId, quantity);
if (!success) {
throw new BusinessException("库存不足");
}

try {
// 2. 创建拼团单
GroupBuyOrder order = new GroupBuyOrder();
order.setOrderNo(generateOrderNo());
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setStatus(GroupBuyStatus.CREATED);
order.setExpireTime(LocalDateTime.now().plusHours(24));

orderMapper.insert(order);

// 3. 发送延迟消息(24小时后检查成团状态)
GroupBuyDelayMessage message = new GroupBuyDelayMessage();
message.setOrderId(order.getId());
message.setExpireTime(order.getExpireTime());

rabbitTemplate.convertAndSend(
"groupbuy.delay.exchange",
"groupbuy.delay.routingkey",
message,
msg -> {
msg.getMessageProperties()
.setDelay(24 * 60 * 60 * 1000); // 24小时
return msg;
}
);

return order;

} catch (Exception e) {
// 回滚库存
stockService.rollbackStock(productId, quantity);
throw e;
}
}
}

3. 分布式任务协调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Component
public class GroupBuyExpireTask {

@Autowired
private RedissonClient redissonClient;

@Autowired
private GroupBuyOrderService orderService;

/**
* 处理过期拼团单(每5分钟执行)
* 使用分布式锁防止集群重复处理
*/
@Scheduled(fixedRate = 5 * 60 * 1000)
public void processExpiredOrders() {
String lockKey = "lock:groupbuy:expire-task";
RLock lock = redissonClient.getLock(lockKey);

try {
// 尝试获取锁,等待10秒,锁持有5分钟
boolean acquired = lock.tryLock(10, 5 * 60, TimeUnit.SECONDS);

if (!acquired) {
log.info("未获取到分布式锁,跳过本次执行");
return;
}

// 查询过期拼团单
List<GroupBuyOrder> expiredOrders = orderService
.getExpiredOrders(LocalDateTime.now());

for (GroupBuyOrder order : expiredOrders) {
try {
orderService.closeOrder(order.getId());
log.info("关闭过期拼团单: {}", order.getId());
} catch (Exception e) {
log.error("处理过期拼团单失败: {}", order.getId(), e);
}
}

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}

4. 异步回调处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Component
public class GroupBuyCallbackListener {

@Autowired
private GroupBuyOrderService orderService;

@RabbitListener(queues = "groupbuy.callback.queue")
public void handleCallback(GroupBuyCallbackMessage message) {
log.info("收到拼团回调: {}", message.getOrderId());

try {
switch (message.getEventType()) {
case "GROUP_SUCCESS":
orderService.handleGroupSuccess(message.getOrderId());
break;
case "GROUP_FAIL":
orderService.handleGroupFail(message.getOrderId());
break;
case "PAY_SUCCESS":
orderService.handlePaySuccess(message.getOrderId());
break;
case "PAY_FAIL":
orderService.handlePayFail(message.getOrderId());
break;
default:
log.warn("未知事件类型: {}", message.getEventType());
}
} catch (Exception e) {
log.error("处理回调失败: {}", message.getOrderId(), e);
// 可加入死信队列处理
}
}
}

5. 用户标签与活动投放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Service
public class UserTagService {

@Autowired
private StringRedisTemplate redisTemplate;

/**
* 基于 Redis Bitmap 构建用户标签
*/
public void tagUser(Long userId, String tag) {
String bitmapKey = "user:tag:" + tag;
redisTemplate.opsForValue().setBit(bitmapKey, userId, true);
}

/**
* 检查用户是否有标签
*/
public boolean hasTag(Long userId, String tag) {
String bitmapKey = "user:tag:" + tag;
return Boolean.TRUE.equals(
redisTemplate.opsForValue().getBit(bitmapKey, userId)
);
}

/**
* 获取标签用户数量
*/
public Long countTaggedUsers(String tag) {
String bitmapKey = "user:tag:" + tag;
return redisTemplate.execute(
new DefaultRedisScript<>(
"return redis.call('bitcount', KEYS[1])",
Long.class
),
Collections.singletonList(bitmapKey)
);
}

/**
* 多标签交集(精准投放)
*/
public List<Long> getUsersWithAllTags(List<String> tags) {
String tempKey = "user:tag:temp:" + System.currentTimeMillis();
String[] bitmapKeys = tags.stream()
.map(tag -> "user:tag:" + tag)
.toArray(String[]::new);

// BITOP AND 操作
redisTemplate.execute((RedisCallback<Void>) connection -> {
connection.bitOp(
RedisStringCommands.BitOperation.AND,
tempKey.getBytes(),
bitmapKeys[0].getBytes(),
Arrays.copyOfRange(bitmapKeys, 1, bitmapKeys.length)
.stream()
.map(String::getBytes)
.toArray(byte[][]::new)
);
return null;
});

// 获取结果并清理
// ...

return userIds;
}
}

性能优化

异步多线程优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Service
public class GroupBuyCalculationService {

@Autowired
private ExecutorService executorService;

/**
* 异步计算拼团优惠
*/
public GroupBuyCalculation calculateAsync(Long productId, Integer quantity) {
// 并行查询商品信息、优惠活动、用户等级
CompletableFuture<Product> productFuture = CompletableFuture
.supplyAsync(() -> productService.getById(productId), executorService);

CompletableFuture<Promotion> promotionFuture = CompletableFuture
.supplyAsync(() -> promotionService.getCurrentPromotion(productId), executorService);

CompletableFuture<UserLevel> levelFuture = CompletableFuture
.supplyAsync(() -> userService.getUserLevel(), executorService);

// 等待所有结果
CompletableFuture.allOf(productFuture, promotionFuture, levelFuture).join();

// 组装结果
return assembleCalculation(
productFuture.join(),
promotionFuture.join(),
levelFuture.join(),
quantity
);
}
}

总结

拼团系统的核心设计要点:

  1. 库存控制:Redis 预扣 + 异步同步,保证不超卖
  2. 状态管理:状态机模型,清晰定义流转规则
  3. 任务调度:分布式锁 + 延迟队列,处理过期订单
  4. 异步处理:MQ 解耦,提升系统吞吐量
  5. 数据存储:Bitmap 存储标签,节省空间

通过合理的设计,系统可以支撑高并发场景下的拼团业务。


本文基于电商营销系统实践,支撑日均数万级拼团订单


拼团系统设计与实现-高并发下的库存与订单处理
https://zxyblog.top/2024/11/15/拼团系统设计与实现-高并发下的库存与订单处理/
作者
zxy
发布于
2024年11月15日
许可协议