淘先锋技术网

首页 1 2 3 4 5 6 7

一 需求描述

实现一个大转盘活动,用户有三次抽奖机会,每次抽奖后给用户返回中奖结果。

中奖概率和奖品在活动前由产品经理给出

一等奖——中奖概率为 1 %,1000 元红包

二等奖——中奖概率为 5 %,100 元红包

三等奖——中奖概率为 10 %,10 元红包

其余为没有中奖

用户通过具体页面可以看到中奖纪录。

二 需求分析

我们发现中奖概率的格式是有问题的,只有中奖概率和奖品,没有奖品数量。没有奖品数量就是对预算没有控制,如果中奖数量超过了预算就会造成损失,所以在需求中要补充奖品数量和活动总预算。

直接实现该需求是比较简单的——对用户的抽奖次数做校验,然后在每次抽奖的时候利用随机数查看用户是否在中奖区间来确定抽奖结果。

但是,作为架构师,要对需求有预判。对于产品的日常运营来说,活动是常规需求,每个月都有形形色色的活动。例如,大转盘抽奖、九宫格抽奖、开箱子抽奖等。这些抽奖活动有不同的前端表现、中奖概率和获取抽奖资格的途径,但底层抽奖逻辑是共性的,我们可以把共性抽象出来,把抽奖部分做成通用的系统,供上层逻辑调用。如果能实现该系统,那么有很多好处。

提升效率:抽奖部分只需修改配置文件即可实现,实现需求时只需要重点关注表现层即可。

提升稳定性:核心抽奖逻辑固定,经过严格测试和多个项目验证,每次活动都是复用代码,并不修改代码,减少了由于程序问题导致发送奖品出错而造成的经济损失。

三 实现方案

1 实现功能分析

1)外部交互

  • 增加用户抽奖次数的功能

  • 获取抽奖纪录的功能

  • 执行抽奖操作,返回结果的功能

2)内部逻辑

  • 修改用户抽奖次数的功能

  • 配置奖品库存和中奖概率等属性功能

  • 处理用户抽奖逻辑

  • 针对系统的稳定性和逻辑的严谨性的功能

整体项目模块结构如下图

2 对外交互设计

由于系统是一个通用系统,会同时服务多个活动。对于每个活动,我们可以用活动 ID 来区分。活动 ID 是一个通用字段,所以在下面的设计中,默认都带有活动 ID。

1)增加抽奖资格

增加抽奖资格是针对一个具体用户的,只需要外部传递过来用户的 UID 即可。

由于修改接口涉及用户数据,比较敏感,所以要有鉴权操作。只有该活动对应的逻辑服务才有权限给用户增加抽奖次数。另外,还要在内部做一些控制,监控增加的抽奖次数,不要超过配置文件中设置的增加抽奖资格的上限。

针对请求,我们只需要返回增加的结果即可。如果成功,则返回增加的次数,如果失败,则返回失败原因。

要区分失败的错误码,识别是系统内部的错误还是增加次数达到上限的业务错误。

2)获取抽奖记录

该功能只涉及读操作,拉取用户抽奖的历史记录。这里需要考虑分页查询,因为是通用抽奖系统,所以不同活动的抽奖次数不一定,有些活动的抽奖次数会很多。做成分页查询,前端可以根据展示需求按需拉取抽奖记录。

3)执行抽奖操作

此功能对外部调用的业务来说,交互比较简单,只需要传入 UID、给出抽奖结果即可。

返回结果的状态码部分要设计得通用一些,能够区分不同的情况。

成功;需要返回用户的中奖信息,中奖信息包括奖品名字,奖品数量等奖品属性。返回中奖信息的协议要满足可扩展性,后续增加新属性时能直接扩展。但有一个特殊情况,未中奖是失败,还是另一种成功呢?

未中奖作为一种特殊的“奖品”,是一种不错的实现方式。因为没中奖也是一种正常逻辑。未中奖和中其他奖品采用相同的返回格式,能够实现逻辑通用。这种方案需要调用方针对未中奖进行特殊处理。

失败:

在失败的时候,要区分是系统失败还是业务失败。系统失败是后端系统的原因,后端考虑好柔性即可。由于这个业务的特点,还有很多业务侧相关的逻辑。例如:活动是有时效的,可能是活动过期了;用户没有抽奖资格却来抽奖;当前奖品库存已空。

4)内部逻辑设计

a 数据结构

(1)奖品部分

主要信息包括奖品名称、奖品总数量、已经抽取数量和中奖概率权重。

(2)用户部分

有两部分数据:用户抽奖属性数据和用户抽奖记录数据

用户抽奖属性数据:主要包括用户已经抽奖的次数、用户一共有多少抽奖次数和用户抽奖相关数据。一个用户在一个活动中仅有一条数据,

用户抽奖记录数据:用户抽奖记录的流水,用户在什么时候参与抽奖并获得什么奖品。通过流水能够展示用户中奖记录,而且为后续发奖提供依据。

b 逻辑实现

逻辑实现除了实现常规的增删改查,更多的是要考虑针对熊抽奖场景,如何保障系统的安全性,以及用户体验的柔性。

风险控制

  • 要控制抽奖次数的上限

  • 奖品要有库存的概念

  • 奖品要有价值预算

幂等

在实现业务逻辑的时候,需要考虑很多细节。例如,分布式系统通过网络处理请求,有时会造成重复请求命令,或者读写冲突,要通过幂等的方式避免这些问题。

(1)设定抽奖和减少抽奖次数的时机

应该先扣减抽奖次数,再调用抽奖逻辑更新奖品状态。

(2)中奖纪录要有订单ID,防止因为程序重试导致写多条中奖纪录

可以设置中奖订单 ID 的内容为{UID}-{活动ID]-{第多少次抽奖中奖}

(3)在发奖环节保证不多发奖品

发奖状态字段有三个状态:未处理(默认)、发放中和已发送,三个状态依次改变,除此之外,不能随意调整状态:未处理转换成发放中,发放中转换成已发送。

(4)在发奖环节增加审查逻辑

5)柔性

对于抽奖环节,可以针对抽奖失败进行柔性处理。

如果在抽奖过程中后端系统出现内部错误,导致抽奖没有真实执行,则可以采取两种方案:一是给出明确提示,表明当前系统有问题,请用户间隔一段时间后再试(后端要有告警,及时修复问题);二是按照未中奖,或者默认的一种奖品进行发放,让用户体验正常。但也要有告警通知,因为此时系统已经处于有问题的状态。

如果奖品库存不足,则有三种方案可供选择:按照未中奖处理;发放次一级奖品;发放默认奖品。

四 整体架构设计

1 整体架构如下图

2 抽奖流程如下图

3 发放奖品流程如下图

五 案例总结

1 审视需求

架构师在接到需求后,不要进入惯性思维来思考如何实现需求,而是要思考需求是否合理,是否有漏洞和风险。例如,最初的需求中并没有考虑活动预算,直接按照表面意思来实现需求是有风险的。

2 产品思维

架构师要具有产品思维,能够对需求进行预判。对于一个表面看是活动类的需求,能够分析这个需求是否是长期需求,是否有必要实现通用的抽象的方案。如果答案是肯定的,那么接下来再对具体的方案进行抽象。

3 积极思考

实现一个需求时要想到抽象复用。