ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

高并发抽奖系统架构设计与性能优化实践

高并发抽奖系统架构设计与性能优化实践

1. 项目概述

这是一个关于大营销平台抽奖系统的技术架构解析,主要聚焦在高并发场景下的系统设计、分布式事务处理、性能优化等核心问题。系统采用了DDD领域驱动设计,结合多种设计模式和分布式技术,实现了高可用、高性能的抽奖服务。

提示:本文将从架构设计、核心业务实现、分布式事务、性能优化等维度,全面解析一个高并发抽奖系统的技术实现方案。

2. 架构设计与DDD领域驱动

2.1 领域划分与解耦

在DDD架构中,我们将系统划分为以下几个核心领域:

  1. 策略域(Strategy):负责抽奖的核心逻辑,包括概率计算、规则处理等
  2. 活动域(Activity):管理活动状态、用户参与次数等
  3. 奖品域(Award):处理奖品发放和记录
  4. 资产域(Asset):管理用户积分等资产

领域层通过定义Repository接口与基础设施层解耦。例如:

public interface StrategyRepository { Strategy getStrategyById(Long strategyId); void saveStrategy(Strategy strategy); }

基础设施层则实现这些接口,完成具体的数据库或Redis操作。

2.2 模板方法模式的应用

抽奖过程被抽象为"抽奖前、中、后"三个阶段:

public abstract class AbstractRaffleStrategy { public final Award performRaffle(User user) { // 1. 抽奖前处理 preRaffle(user); // 2. 核心抽奖逻辑 Award award = doRaffle(user); // 3. 抽奖后处理 return postRaffle(user, award); } protected abstract void preRaffle(User user); protected abstract Award doRaffle(User user); protected abstract Award postRaffle(User user, Award award); }

这种设计保证了核心流程的稳定性,同时允许子类灵活扩展各阶段的具体实现。

3. 核心业务实现

3.1 责任链模式的应用

责任链模式用于抽奖前的规则校验:

public interface LogicChain { StrategyAwardVO logic(User user, Long strategyId); void setNext(LogicChain next); } public class BlackListLogicChain implements LogicChain { private LogicChain next; @Override public StrategyAwardVO logic(User user, Long strategyId) { if(isInBlackList(user)) { return new StrategyAwardVO(DEFAULT_AWARD_ID); } return next != null ? next.logic(user, strategyId) : null; } @Override public void setNext(LogicChain next) { this.next = next; } }

责任链的顺序通过数据库配置,支持动态调整:

INSERT INTO strategy_config(strategy_id, rule_models) VALUES (1, 'blacklist,weight,default');

3.2 规则树引擎设计

对于复杂的后置处理逻辑,我们采用规则树引擎:

public class RuleTreeEngine { public Award process(RuleTreeVO tree, Context context) { TreeNodeVO currentNode = tree.getRootNode(); while(!currentNode.isLeaf()) { String ruleValue = evalRule(currentNode, context); currentNode = findNextNode(currentNode, ruleValue); } return currentNode.getAward(); } }

规则树节点关系存储在数据库中:

CREATE TABLE rule_tree_node_line ( id BIGINT PRIMARY KEY, tree_id BIGINT, node_from BIGINT, node_to BIGINT, rule_limit_value VARCHAR(50) );

4. 分布式事务处理

4.1 本地消息表模式

为了保证MQ消息的可靠投递,采用本地消息表模式:

@Transactional public void drawLottery(User user) { // 1. 记录中奖结果 awardRecordDao.insert(awardRecord); // 2. 写入本地消息表 localMessageDao.insert(message); // 3. 发送MQ消息 try { mqProducer.send(message); localMessageDao.updateStatus(message.getId(), SENT); } catch(Exception e) { // 发送失败,由定时任务重试 } }

定时任务扫描未发送的消息进行重试:

@Scheduled(fixedRate = 60000) public void retryFailedMessages() { List<LocalMessage> messages = localMessageDao.selectUnsent(); messages.forEach(this::retrySend); }

4.2 幂等性设计

消费者端通过唯一索引保证幂等:

CREATE TABLE award_distribution ( id BIGINT PRIMARY KEY, order_id VARCHAR(64) UNIQUE, user_id BIGINT, award_id BIGINT, status TINYINT );

消费者处理逻辑:

public void handleMessage(Message message) { String orderId = message.getOrderId(); AwardDistribution distribution = awardDistributionDao.selectByOrderId(orderId); if(distribution != null && distribution.isCompleted()) { return; // 已处理过,直接返回 } if(distribution == null) { try { awardDistributionDao.insert(new AwardDistribution(orderId)); } catch(DuplicateKeyException e) { // 并发插入冲突,说明其他线程已处理 return; } } // 执行发奖逻辑 distributeAward(orderId); // 更新状态 awardDistributionDao.updateStatus(orderId, COMPLETED); }

5. 性能优化方案

5.1 Redis库存扣减

采用分段锁优化热点Key问题:

public boolean deductStock(Long awardId) { String stockKey = "stock_" + awardId + "_" + (userId.hashCode() % 10); Long remaining = redisTemplate.opsForValue().decrement(stockKey); if(remaining >= 0) { return true; } else { // 当前分段无库存,尝试其他分段 redisTemplate.opsForValue().increment(stockKey); return tryOtherSegments(awardId); } }

5.2 规则树预热

活动预热时将规则树加载到Redis:

public void armory(Long strategyId) { RuleTreeVO tree = ruleTreeDao.selectByStrategyId(strategyId); String key = "rule_tree:" + strategyId; redisTemplate.opsForValue().set(key, serialize(tree)); // 同时加载到本地缓存 localCache.put(key, tree); }

5.3 分布式ID生成

采用雪花算法生成分布式ID:

public class SnowflakeIdGenerator { private final long workerId; private long sequence = 0L; private long lastTimestamp = -1L; public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨异常"); } if (lastTimestamp == timestamp) { sequence = (sequence + 1) & SEQUENCE_MASK; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - EPOCH) << TIMESTAMP_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence; } }

6. 监控与运维

6.1 日志追踪

通过TraceId实现全链路追踪:

public class TraceInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String traceId = request.getHeader("Trace-Id"); if(traceId == null) { traceId = UUID.randomUUID().toString(); } MDC.put("traceId", traceId); return true; } }

日志格式配置:

%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n

6.2 监控告警

关键指标监控:

  1. 接口响应时间
  2. Redis库存消耗速率
  3. MQ消息积压情况
  4. Canal同步延迟
@Scheduled(fixedRate = 60000) public void monitorCanalDelay() { long delay = getCanalDelay(); if(delay > THRESHOLD) { alertService.sendAlert("Canal延迟超过阈值:" + delay); } }

7. 经验总结与避坑指南

在实际开发中,我们积累了一些重要经验:

  1. 线程安全:ThreadLocal必须及时清理,否则在复用线程时会导致数据错乱

    try { threadLocal.set(value); // 业务逻辑 } finally { threadLocal.remove(); }
  2. 缓存一致性:本地缓存需要处理失效问题

    @EventListener public void handleRuleChangeEvent(RuleChangeEvent event) { localCache.invalidate(event.getStrategyId()); }
  3. 分布式锁:Redisson的看门狗机制可以避免锁过期问题

    RLock lock = redissonClient.getLock("lock_key"); try { lock.lock(); // 业务逻辑 } finally { lock.unlock(); }
  4. 分库分表:避免不带分片键的全表扫描查询,可以通过ES等方案解决

  5. 异常处理:分布式环境下需要考虑各种异常情况,如网络抖动、服务不可用等

这个抽奖系统经过多次迭代和优化,目前能够支持500-800 TPS的并发量。通过合理的架构设计和技术选型,我们解决了高并发、分布式事务、数据一致性等核心问题。未来还可以在以下方面继续优化:

  1. 引入更精细化的服务降级策略
  2. 加强风控能力,防止刷单行为
  3. 实现冷热数据分离,提升查询性能
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进