抽奖系统设计面试高频问题 & 核心方案(含设计模式 + 架构思路)

第一部分:核心交易链路设计(模板方法、责任链、组合模式)

Q1. 面试官:我看你的简历里提到了 “抽奖系统”,抽奖的核心流程通常很长(参数校验、黑名单、权重计算、库存校验、兜底逻辑等)。你是如何组织代码,保证主流程清晰,同时又能灵活扩展各种规则的?

  • 考察点:模板方法模式 (Template Method)
  • 对应代码逻辑:AbstractRaffleStrategy(定义骨架)-> DefaultRaffleStrategy(实现细节)
  • 参考回答
    1. 场景:抽奖流程是标准化的:参数校验 -> 责任链规则过滤 (黑名单 / 权重) -> 随机数计算 -> 决策树规则过滤 (库存 / 次数锁) -> 返回结果
    2. 设计:使用模板方法模式,定义抽象基类并编排固定执行顺序(骨架)
    3. 落地:将具体规则过滤逻辑定义为抽象方法,由子类实现
    4. 收益:保证核心交易链路稳定性,防止新人打乱流程;子类可专注具体规则调用逻辑

Q2. 面试官:你刚才提到了 “规则过滤”,比如黑名单、白名单、权重规则等。如果业务方明天要加一个 “地区限制” 规则,你的代码需要大改吗?你是怎么设计的?

  • 考察点:责任链模式 (Chain of Responsibility)、开闭原则 (OCP)
  • 对应代码逻辑:ILogicChain 接口及其实现类 (BlackListLogicChain, RuleWeightLogicChain)
  • 参考回答
    1. 场景:抽奖前需多重校验,且校验顺序和组合可能变化
    2. 设计:使用责任链模式,将每个规则封装成独立的 Handler 节点
    3. 落地:通过 Spring 注入所有 Chain 实现,利用数据库配置的 rule_models 字段(如 "rule_blacklist,rule_weight"),在工厂中动态组装链条
    4. 收益:完全符合开闭原则,新增 “地区限制” 只需新增 Handler 类并配置数据库,无需修改主流程代码

Q3. 面试官:抽奖拿到奖品 ID 后,可能还会遇到 “库存不足”、“需要抽奖 N 次解锁” 等情况,这部分逻辑是非线性的(比如库存不足要走兜底奖品),这部分你是怎么处理的?

  • 考察点:组合模式 (Composite Pattern) / 决策树 (Decision Tree)
  • 对应代码逻辑:RuleTreeNodeVO(节点)、RuleTreeNodeLineVO(连线)、DecisionTreeEngine(引擎)
  • 参考回答
    1. 场景:抽奖后逻辑为多叉分支(如:库存扣减成功 -> 发奖;库存不足 -> 兜底节点;次数不够 -> 拦截节点)
    2. 设计:未使用简单 if-else,而是设计规则引擎(决策树),本质是组合模式的应用
    3. 落地:将 “库存判断”“次数锁”“兜底” 抽象为树节点,节点间通过 “连线” 逻辑(如 ALLOW, TAKE_OVER)连接
    4. 收益:业务流转配置化,运营人员可动态调整规则(如库存不足报错 / 发兜底奖),提升系统灵活性

第二部分:业务扩展性与解耦(策略模式、工厂模式)

Q4. 面试官:现在的营销活动玩法很多,比如 “积分兑换抽奖”、“签到送抽奖次数”、“直接购买抽奖次数”。这些不同的交易行为,你的系统是如何兼容的?

  • 考察点:策略模式 (Strategy Pattern)
  • 对应代码逻辑:ITradePolicy 接口及其实现 (CreditPayTradePolicy, RebateNoPayTradePolicy)
  • 参考回答
    1. 场景:用户获取额度方式不同(扣减积分 / 直接返利入账)
    2. 设计:使用策略模式,定义交易策略接口,针对不同方式实现不同处理逻辑
    3. 落地:通过 Map 结构注入策略,运行时根据前端传入的 tradeType 自动路由到对应策略类执行
    4. 收益:消除 Service 层大量 if-else 代码,新增玩法只需新增策略类

Q5. 面试官:关于发奖环节,奖品种类很多(优惠券、实物、积分、微信红包),每种奖品的发放逻辑都不一样(调不同接口),你怎么管理的?

  • 考察点:策略模式 + 适配器思想
  • 对应代码逻辑:IDistributeAward 接口及其实现 (UserCreditRandomAward 等)
  • 参考回答
    1. 设计:使用策略模式,定义统一发奖接口 distributeAward
    2. 落地:每种奖品类型对应一个实现类,发奖时根据奖品配置的 Key 找到对应 Service 执行
    3. 收益:隔离不同下游渠道对接逻辑,新增奖品类型(如爱奇艺会员)只需新增适配器实现类

Q6. 面试官:你刚才提到的责任链和规则树,它们的节点对象在运行时是怎么创建和管理的?是每次 new 出来的吗?

  • 考察点:工厂模式 (Factory Pattern)、原型模式 (Prototype)、Spring Bean 作用域
  • 对应代码逻辑:DefaultChainFactory,@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  • 参考回答
    1. 设计:使用工厂模式组装链条
    2. 细节:责任链是有状态的(next 指针),并发环境下不能用单例;利用 Spring 的 Prototype(原型模式)作用域,每次工厂组装链条时从容器中获取新 Bean 实例
    3. 收益:既利用 Spring 依赖注入能力,又保证多线程下的线程安全

第三部分:高并发与性能优化(享元模式、观察者模式)

Q7. 面试官:我看你的系统支持高并发抽奖。如果几万人同时抽奖,每次都要去计算概率(比如 0.1% vs 20%),CPU 压力会很大,你是怎么优化的?

  • 考察点:享元模式 (Flyweight Pattern) 思想 / 查表法 / 预热
  • 对应代码逻辑:StrategyArmoryDispatch(装配兵工厂)、storeStrategyAwardSearchRateTable
  • 参考回答
    1. 痛点:实时计算随机数区间在大流量下消耗 CPU
    2. 设计:采用空间换时间思想(类似享元模式的预处理)
    3. 落地:活动开始前通过 “兵工厂” 模块预热,将概率(如 20%)转换为查找表(Map),如 10000 个位置中 2000 个存奖品 A 的 ID
    4. 收益:抽奖时生成随机整数 O (1) 从 Redis Map 中取值,无需复杂计算,极大提升吞吐量

Q8. 面试官:用户中奖后,涉及扣减库存、写入中奖记录、发奖、发短信等一系列操作。如果全部同步执行,接口响应会很慢,你怎么解决?

  • 考察点:观察者模式 (Observer Pattern) / 发布 - 订阅模式
  • 对应代码逻辑:SendAwardMessageEvent(MQ 发送)
  • 参考回答
    1. 设计:使用发布 - 订阅模式(基于 MQ)
    2. 落地:抽奖核心流程仅负责 “计算结果” 和 “落库中奖记录(待发奖状态)”,落库成功后立即发送 MQ 消息
    3. 收益:①解耦:抽奖系统不依赖发奖系统;②削峰:耗时操作异步处理,保证 C 端用户体验

Q9. 面试官:在扣减库存时,为了防止超卖,你肯定用了 Redis。但 Redis 扣减完,怎么保证数据库库存也同步更新呢?

  • 考察点:异步处理、最终一致性
  • 对应代码逻辑:RuleStockLogicTreeNode -> awardStockConsumeSendQueue
  • 参考回答
    1. 设计:采用延迟双删 / 异步同步策略
    2. 落地:Redis decr 扣减成功视为抢购成功,立即发送 MQ 消息(或写入延迟队列),由后台 Worker 异步更新数据库库存
    3. 收益:将数据库高并发写压力转移到 Redis 和 MQ,保护数据库

第四部分:DDD 与代码组织(建造者、状态模式)

Q10. 面试官:你的代码里有很多复杂的对象,比如 “聚合根” 或者 “订单实体”,参数非常多,你是怎么构建这些对象的?

  • 考察点:建造者模式 (Builder Pattern)
  • 对应代码逻辑:CreateQuotaOrderAggregate.builder()...build()
  • 参考回答
    1. 设计:广泛使用建造者模式(Lombok @Builder)
    2. 收益:DDD 中聚合根构建涉及多个实体(如订单 + 账户 + 流水),Builder 模式提升代码可读性,且可在 build () 阶段加入统一参数校验,确保领域对象合法

Q11. 面试官:订单有 “待支付”、“已完成”、“已取消” 等状态,活动也有 “开启”、“关闭” 状态。你在代码里是怎么管理这些状态流转的?

  • 考察点:状态模式 (State Pattern)(简化版 / 守卫逻辑)
  • 对应代码逻辑:ActivityBaseActionChain 中的状态校验,OrderStateVO
  • 参考回答
    1. 设计:未为每个状态写独立类(标准状态模式),但应用了状态模式的守卫思想
    2. 落地:在责任链头部或 Service 入口统一校验 StateVO(如仅 ActivityState.OPEN 可进入抽奖链条)
    3. 收益:防止非法状态下的业务操作,保证领域模型一致性

第五部分:系统设计综合

Q12. 面试官:你的系统中有一个 “兵工厂” 接口和一个 “调度” 接口,为什么要分开?

  • 考察点:接口隔离原则 (ISP)、单一职责原则 (SRP)
  • 对应代码逻辑:IStrategyArmory(装配)vs IStrategyDispatch(抽奖)
  • 参考回答
    1. 设计:遵循接口隔离原则
    2. 落地:①Armory 接口负责 “写”(预热缓存、装配数据),由运营后台调用;②Dispatch 接口负责 “读”(抽奖计算),由 C 端高并发调用
    3. 收益:避免 C 端误调用装配逻辑导致缓存重置,方便权限控制和部署隔离

Q13. 面试官:如果现在要搞一个 “签到返利” 的功能,签到满 7 天送一个抽奖次数,你的系统架构支持吗?

  • 考察点:观察者模式 + 策略模式 的综合运用
  • 对应代码逻辑:BehaviorRebateService(行为返利)
  • 参考回答
    1. 设计:支持,系统内置 “行为返利” 模块
    2. 落地:用户 “签到” 视为行为事件,系统接收后通过策略模式匹配返利规则(如:行为类型 = 签到,条件 = 连续 7 天),匹配成功后调用 “充值策略” 添加抽奖次数
    3. 收益:全程解耦,新增行为返利规则无需修改核心流程

Q14. 面试官:在抽奖过程中,如果 Redis 挂了,你的系统会怎么样?有降级方案吗?

  • 考察点:兜底策略 (Design for Failure)
  • 对应代码逻辑:RuleLuckLogicTreeNode(兜底节点)
  • 参考回答
    1. 设计:内置兜底机制
    2. 落地:规则树中若中间规则(如库存扣减)异常 / 超时,决策树引擎捕获异常并路由到兜底节点(LuckAward)
    3. 收益:保证用户体验,系统组件故障时用户可获得 “谢谢参与” 或小积分奖励,而非报错页面

Q15. 面试官:最后问一个架构层面的,你觉得你的项目是贫血模型还是充血模型?

  • 考察点:DDD (领域驱动设计) 理解
  • 参考回答
    1. 定位:基于 DDD 分层架构,倾向于贫血模型与充血模型结合
    2. 解释:①实体类(Entity)主要承载数据(偏贫血);②核心业务逻辑未堆砌在 Service 中,通过领域服务(Domain Service,如 RaffleStrategy)配合设计模式(责任链、规则树)组织;③部分核心逻辑(如解析规则字符串)封装在实体(StrategyEntity)中(偏充血)
    3. 总结:既避免实体过于臃肿,又防止 Service 层沦为流程脚本,适合复杂业务系统

面试总结建议

  1. 描述时优先使用 “模块”“组件”“流程” 等词汇,避免主动报类名(除非面试官要求手写代码)
  2. 回答结构遵循 “场景→设计→落地→收益”,逻辑清晰且贴合面试语境
  3. 结合设计模式时,重点突出 “为什么选该模式”(解决什么问题),而非仅讲模式定义
  4. 高频强调 “解耦”“扩展性”“高并发”“用户体验” 等核心指标,贴合面试官关注点
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐