深入剖析优惠券核心架构设计
很多网站都有优惠券业务,但是剖析优惠券架构的技术文章却很少,优惠券看似简单,但内部涉及用户、商品、平台、店铺等综合维度下各种营销玩法,复杂度一下会提高很多。所以写了这篇文章来看看优惠券是如何技术架构的。
IT 界有句名言,”talk is cheap, show me the code“。废话不都说,开始进入正题
我们先简单了解下优惠券都包含哪些重要信息,做了个优惠券信息的思维导图
优惠券创建完成后,接下来就是透出、使用。透出涉及的位置很多,比如活动会场、开机屏,但流量最大的还是在商品详情页。
而使用主要体现在下面几个环节:优惠券领取、下单时优惠券查询、核销等。另外还有一些周边的附加操作,比如优惠券的过期处理、数据统计、用户券包展示等。接下来对于一些核心操作,我们重点剖析如何技术设计。
一、商品详情页展示
商品详情、产品介绍是几乎每个产品运营、营销推广人员都会面对的工作。作为产品对外的传播物料,直接影响到了产品的转化,若是电商渠道更是直接影响产品销售额。
商品详情除了商品图片、标题、SKU规格、库存、价格、成交记录、评价、商品描述等信息外,还有一块非常重要的信息,那就是优惠券。如下图所示,会提示商品可用的优惠券,并引导用户领取,并促成下单交易。
优惠券设置除了基本信息外,还有适用的商品列表,但单独存储在优惠券的关联商品表中。ER模型如下图所示
商品详情页查找优惠券,只需要按item_id反查商品表即可,就可以查到所有配置到该商品的优惠券。由于是通用页面,所以无视用户登录属性,所有人看到的页面都是一样。具体的校验规则(比如有些券只能新人领取)会放在用户”点击领取“时做逻辑控制校验。
逻辑看似简单,但对于一个电商网站而言,商品详情页的访问量通常是比较大的,如何支持高并发访问,我们一般会借助缓存,有专门的”优惠券同步中心“,将运营发布的优惠券按商品维度维护到Cache中,采用空间换时间的方式,提前预热好数据,这样当商详页访问时只需要查缓存即可,可以支持比较高的QPS。
有人可能会继续问,优惠券有自己的生命周期,如果券过期了怎么维护缓存的数据一致性。因为商品详情需要查优惠券的详细信息,所以接口内部会引入stream流的 filter机制,会对优惠券的可用时间做校验计算,将失效的券过滤掉。另外,我们会发一个优惠券过期的消息,由”优惠券同步中心“接受MQ消息对缓存中已经失效的优惠券做清理。
二、优惠券领取
优惠券并不是每个人都可以领取的,有很多限制条件,比如”有些优惠券只有新人可以领“,”有些优惠券一天只能领一张“,”有些券需要完成任务或达到一定门槛才可以领“,等等。看似复杂,但”物以类聚人以群分“,我们可以采用相似聚合、分层来处理,一切都简单很多。
我们简单看下优惠券领取的流程图:
优惠券的领取,逻辑相对简单些,需要关注券模板、领取记录,用户标签等几个维度校验。如果满足条件就可以给用户发放优惠券。
券模板校验:
优惠券模板本身设置领取时间,只有在区间时间内才有机会领取优惠券,当然是否能最终领取还取决于其他条件。
券库存。正如商品一样,没有库存的商品是无法下单售卖
当日发放限制。为了防止用户对券哄抢,同时为了延长活动的黏性效果,会限量每天发放优惠券数量。
优惠券不是随便乱发的,毕竟要计入公司的支出成本,一旦涉及到钱的问题便是个严肃的问题。定位好用户群体,什么样的优惠券发给什么类型的用户才能获得最大收益。所以我们在创建优惠券时,除了设置适用的商品范围,还要限制优惠券的用户类型。人尽其用,物尽其才。
适用商品范围一般在页面展示或下单时才用到。而用户类型则相反,需要前置控制,也就是说在用户领取时校验,一旦用户领取成功就属于用户个人财产,只要没有过期都可以使用。
用户领取记录校验:
根据券模板的领取条件限制,以及用户的领取记录,判断是否还可以领取
用户标签校验:
有些券限制只有新人才可以领取。如:新人专享活动
券也可以跟用户等级挂钩,只有达到设置的等级才可以领取
黑名单用户。用风控挂钩,识别风险刷券用户
自定义用户。可以给用户打一些特定标签。并针对该类型的用户发放。
技术挑战:
风控如何接入,领券接口的QPS会比较高,对风控接口性能有较高要求
券缓存如何设计。一般会按变化的频率做拆分。券模板本身内容可以封装一个缓存模型。其中的券库存由于经常变化,需要单独剥离处理,采用缓存+数据库。但如何保证两者的数据一致性需要我们特别关注
领取记录同样采用缓存+数据库
用户标签。由于不用的优惠券会限制发放给不用的用户人群,所以我们会根据券模板设置的用户标,采用策略模式,调用外部服务,实时查询用户是否满足领取条件。
另外优惠券计算接口,也会返回不可用的优惠券,并标明不可用原因,引导用户调整下单商品。
三、下单页优惠券计算
用户对勾选的商品(也可能是购物车批量商品下单)创建订单时,会计算有哪些优惠券可以使用。
如上图所示,页面输出信息很简单,只显示可以当前可用且按优惠力度排序的优惠券,但如何计算出满足当前订单的优惠券,后台涉及哪些复杂业务逻辑,又会遇到哪些技术难点挑战,下面来我们来分析下。
整体的架构思想还是采用过滤机制,首先查出用户下所有的优惠券,然后根据优惠的各种标记位做相应的策略处理。
渠道过滤:
上面的图是多点APP的券包截图,该券限制使用条件必须是”多点配送(到家可用)“,如果是”门店自提“则订单无法使用该优惠券。所以我们可以清楚判断,优惠券请求接口势必要传入配送方式参数。
下单时计算可用的优惠券,首要一条就是判断订单中的商品是否在优惠券的商品范围里。常见的商品作用范围有哪些:
单品:创建单品优惠券,限制只有该商品才可以使用
部分商品:可以与活动绑定,也可以独立设置,只有指定的这些商品才可以适用优惠券
所有商品:全场券,所有售卖的商品都可使用。
店铺商品:店铺优惠券,限制只有购买本店铺的商品才可以使用,当然要满足金额门槛
商品分类:类目券,根据商品的类目属性做适用条件
商品品牌:按品牌决定是否可以使用优惠券
商品标签:为指定商品打上自定义标签,然后优惠券模板中配置
指定商品/排除特殊商品:针对特殊商品做给运营人员的灵活配置。
渠道商品:根据商品进货或者销售渠道定义优惠券范围
区域商品:根据商品销售区域划分,此类优惠券社区电商用的较多。
订单范围就是订单金额满减、满赠、包邮等条件下可使用的优惠券。
上图优惠券就是商家券,除了”商家券“的标记外,还会设置商家的id,也就是下单的商品中属于这家的商品才会累计校验金额门槛。
四、优惠券核销
五、其他功能
优惠券一般都有适用的商品范围,可作为一个独立页面对外呈现。下图是商家券的详情页,同时提供了”推荐“、”热销“、”价格“,三个维度的排序。
一般这种玩法都是借助搜索来技术实现的,创建优惠券模板后,会以商品的维度将数据同步ES中,索引结构:
{
"properties" : {
"couponId" : {
"type" : "long"
},
"itemId" : {
"type" : "long"
},
"sort" : {
"type" : "long"
},
"salesCount" : {
"type" : "long"
},
"salePrice" : {
"type" : "long"
},
"gmtCreated" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"gmtModified" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
至于搜索数据的维护,尤其是商品销量定时写入,这些都是些常规的业务实现,这里就不一一累述了。
用户的卡券包实现很简单,只需分页查询用户的券表即可,加上过滤条件”用户券的状态要是未使用“。如果是社区电商,一般有区域限制,券列表展示会根据当前区域做判断,如果当前区域不可用会置灰。
往期推荐
关注【微观技术】
我们热衷于收集&分享高并发、系统架构、微服务、消息中间件、 RPC框架、高性能缓存、搜索、分布式数据框架、分布式协同服务、分布式配置中心、中台架构、领域驱动设计、系统监控、系统稳定性等技术知识。