1. 项目概述
这是一个关于大营销平台抽奖系统的技术架构解析,主要聚焦在高并发场景下的系统设计、分布式事务处理、性能优化等核心问题。系统采用了DDD领域驱动设计,结合多种设计模式和分布式技术,实现了高可用、高性能的抽奖服务。
提示:本文将从架构设计、核心业务实现、分布式事务、性能优化等维度,全面解析一个高并发抽奖系统的技术实现方案。
2. 架构设计与DDD领域驱动
2.1 领域划分与解耦
在DDD架构中,我们将系统划分为以下几个核心领域:
- 策略域(Strategy):负责抽奖的核心逻辑,包括概率计算、规则处理等
- 活动域(Activity):管理活动状态、用户参与次数等
- 奖品域(Award):处理奖品发放和记录
- 资产域(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%n6.2 监控告警
关键指标监控:
- 接口响应时间
- Redis库存消耗速率
- MQ消息积压情况
- Canal同步延迟
@Scheduled(fixedRate = 60000) public void monitorCanalDelay() { long delay = getCanalDelay(); if(delay > THRESHOLD) { alertService.sendAlert("Canal延迟超过阈值:" + delay); } }7. 经验总结与避坑指南
在实际开发中,我们积累了一些重要经验:
线程安全:ThreadLocal必须及时清理,否则在复用线程时会导致数据错乱
try { threadLocal.set(value); // 业务逻辑 } finally { threadLocal.remove(); }缓存一致性:本地缓存需要处理失效问题
@EventListener public void handleRuleChangeEvent(RuleChangeEvent event) { localCache.invalidate(event.getStrategyId()); }分布式锁:Redisson的看门狗机制可以避免锁过期问题
RLock lock = redissonClient.getLock("lock_key"); try { lock.lock(); // 业务逻辑 } finally { lock.unlock(); }分库分表:避免不带分片键的全表扫描查询,可以通过ES等方案解决
异常处理:分布式环境下需要考虑各种异常情况,如网络抖动、服务不可用等
这个抽奖系统经过多次迭代和优化,目前能够支持500-800 TPS的并发量。通过合理的架构设计和技术选型,我们解决了高并发、分布式事务、数据一致性等核心问题。未来还可以在以下方面继续优化:
- 引入更精细化的服务降级策略
- 加强风控能力,防止刷单行为
- 实现冷热数据分离,提升查询性能