得物技术

得物增长兑换商城的构架演进

目录

一、简介

二、诞生背景

三、业务架构

    1. 业务架构图

    2. 业务架构概述

四、玩法迭代

    1. 整体玩法总览

    2. RPC调用迭代

    3. 库存设计迭代

    4. 奖品横向迭代

    5. 兑换防刷迭代

    6. 项目稳定性建设

五、更多拓展

    1. 抽奖组件搭建器

    2. 引入增长的任务中台

    3. 打造简易的积分系统

六、总结

一

简介

在移动互联网蓬勃发展的今天,用户的选择日益多元化,App市场的竞争也愈发白热化。为了在激烈的市场竞争中脱颖而出,提升用户获取效率并增强用户粘性,越来越多的应用开始采用积分兑换、抽奖等互动玩法。这些精心设计的运营策略不仅能够满足用户的参与感和成就感需求,更能有效促进社交传播,提升品牌忠诚度。通过持续优化和迭代,开发者能够在竞争激烈的市场中占据优势地位,实现用户的可持续增长和长期留存。

基于提升系统复用性和稳定性的考量,我们构建了一个统一的兑换商城中台系统。该中台旨在为各上游业务线提供标准化的积分兑换和奖励机制,使各类应用和服务能够快速接入并享受便捷的服务支持。通过这一中台架构,我们不仅实现了核心业务逻辑的集中化管理与维护,更为后续的功能扩展(如抽奖、彩票等多样化玩法)奠定了坚实基础,从而显著提升了开发效率和用户体验。

二

诞生背景

在用户增长和留存策略的探索中,增长团队先后推出了多种互动类玩法,如每日签到、星愿森林、心愿海洋以及早期的点鞋成金等。这些玩法通过积分或虚拟货币的积累,不仅增强了用户的参与感,也为用户提供了丰富的奖励机制。然而,随着玩法的多样化,如何高效管理这些积分或货币的消耗途径,成为提升用户体验和趣味性的关键。

Image

每日签到

Image

星愿森林

Image

心愿海洋

Image

点鞋成金

Image

兑换专区

Image

兑换专区

Image

兑换专区

Image

兑换专区

面对这一挑战,团队意识到为每个互动玩法单独开发兑换功能不仅需要前后端的全面支持,还需重复搭建复杂的监控和预警系统以防范潜在的资损风险。这种重复开发的模式不仅成本高昂,而且难以保证系统的一致性和稳定性。因此,构建一个统一的积分商城,为各互动玩法提供通用化的兑换系统,成为了提升开发效率和确保系统安全性的必然选择。这样不仅优化了资源配置,也为后续的玩法扩展和用户体验升级奠定了坚实的基础。

在既有兑换业务的基础上,我们也成功沉淀并提炼了多个核心领域模型。后续基于这些通用化的领域模型,我们逐步探索并实现了多种新型玩法的快速落地,包括老虎机、盲盒以及抽奖券等多样化互动形式。

三

业务架构

业务架构图

Image

业务架构概述

业务架构主要分4级:

  • 上层业务

    ▪ 主要是有使用兑换商城功能的上游应用,包括星愿森林、每日签到、心愿海洋、增长新客、社区抽奖、学生答题等应用。

  • 玩法沉淀

    ▪ 主要包括三类玩法,包括积分兑换、老虎机/盲盒,以及抽奖券等。玩法核心所依赖的领域模型基本相似,各个系统都能得到了很好的复用。而在玩法层面,我们主要使用了设计模式中的模板模式,优先搭建好每个玩法的骨架,然后将一些关键步骤交由上游应用自定义实现。

  • 领域模型

    ▪ 活动:对应每个新接入的应用都需要新建一个活动模型,而各类自定义的业务层面的逻辑都是在活动维度上实现的。

    ▪ 模板:模板作为上下关系的重要承接,一个活动能够创建多个模板,而每个模板下又可以关联多个奖品。之所以存在模板这个中间层,是有多个原因的。 

       ▫ 原因1:模板和活动之间的关系,我们可以以星愿森林为例,一个活动他是存在多个兑换专区的,包含了合种专区、超值专区等,这些是需要通过模板维度来划分的。

       ▫ 原因2:模板和奖品之间的关系,奖品需要关联到不同的资产,创建一个奖品的成本较高。而将奖品设计成可以被绑定到多个模板上,奖品这个资产就可以得到一个很好的复用。同时每个模板中该奖品有其独立的库存模块,那么模板之间就不会互相影响了。

       ▫ ...

    ▪ 奖品:后台不同类型的奖品统一都需要在奖品库内创建。这样我们就能在不同的活动和模板中在奖品库内选出该奖品。每次新增新的类型奖品,需要实现该新类型奖品的发奖能力,如优惠券、津贴、现金等都依赖不同的下游应用进行发放。

    ▪ 库存:目前支持分时间段库存、每日库存以及总库存逻辑频控,包含用户及奖品的发放频控、包含每日频次、周期循环频次以及总频次。

  • 下游依赖

    ▪ 主要是兑换商城所依赖的下游应用,包括营销中台、商品聚合中心、提现中台、口令服务、增长配置中心等。

四

玩法迭代

整体玩法总览

本次玩法迭代的介绍主要以积分兑换玩法为主,参考样式如下:

Image

要想兑换具体的商品需要经过一系列流程,下图为兑换的主流程介绍,包含了用户从发起兑换到最终兑换成功或失败的流程。主要为用户选择某个奖品兑换需要处理的一系列业务逻辑。

如上文所说,在玩法层面,主要使用了设计模式中的模板模式,利用自己领域模型的能力,优先搭建好兑换玩法的骨架,比如有效性校验、库存扣减、奖品发放等逻辑,然后将一些关键步骤如前置自定义检查、扣减货币、回滚操作交由上游应用自定义实现。

玩法内置逻辑是由商城内部的领域模型实现,主要包含通用逻辑如下:

  1. 各个模型的有效性校验:确保兑换请求的合法性。

  2. 库存及频控模型的前置校验:检查库存和频控模型,确保兑换请求的可行性。

  3. 库存扣减逻辑:扣减奖品库存,确保库存数据的实时性和准确性。

  4. 奖励模型的发放逻辑:发放用户所兑换的奖品。

  5. 兑换成功后置处理:进行兑换成功的后置处理,如记录日志、更新用户状态等。

即下图灰色模块是由商城自己的玩法模板实现。

需业务方对接逻辑主要包含:

  1. 前置自定义校验:实现特定业务规则的前置校验。

  2. 货币扣减:扣除用户相应的积分或虚拟货币。

  3. 兑换失败后的货币回滚:在兑换失败时,将扣除的积分或虚拟货币返还给用户。

即黄色箭头及模块是由商城侧发起,调用上游接入应用,由上游应用处理相关逻辑,然后通知商城成功与否。

Image

RPC调用迭代

1.0版本

在最初的版本中,黄色模块(如前置自定义检查、扣减货币、回滚操作)的实现方式较为繁琐。具体流程如下:

  1. 上游业务方提供protobuf文件:每个新接入的业务方需要提供自己的protobuf文件。

  2. 生成go-grpc的server及client端桩代码:根据protobuf文件生成go-grpc的server和client端桩代码。

  3. 商城服务编写代码调用client端桩代码:在商城服务中编写代码,调用client端桩代码发起RPC调用。

这种实现方式存在以下问题:

  • 开发成本高:每次新接入一个业务,都需要在商城服务和接入方同时编写代码。

  • 维护复杂:随着业务接入数量的增加,代码维护和管理的复杂度显著提升。

Image

2.0版本

为了减少商城侧的开发成本,我们借鉴了Java的SPI(Service Provider Interface)概念,对RPC调用进行了优化。具体实现如下:

  1. 商城统一生成protobuf的server端代码:商城统一生成protobuf的server端代码,作为服务提供者接口。

  2. 活动模块统一配置调用路由:在商城服务的活动模块中,统一配置调用路由,实现服务的动态路由和调用。

  3. 接入方引用protobuf生成的server端桩代码:每次有新业务接入,接入方只需引用对应protobuf生成的server端桩代码,并通过反向注册机制实现服务的注册和调用。

这种实现方式带来了以下优势:

  • 开发成本降低 :商城层无需再为每个新接入的业务编写代码,只需进行应用配置。

  • 维护简化 :通过统一的服务提供者接口和调用路由,代码维护和管理的复杂度显著降低。

  • 扩展性强 :新业务的接入更加便捷,系统的扩展性和灵活性得到提升。

通过这种优化,我们不仅实现了RPC调用的高效复用,还为未来的业务扩展和系统优化奠定了坚实的基础。这种模块化、可配置的设计理念,不仅提升了开发效率,还增强了系统的灵活性和可扩展性。

Image

库存设计迭代

一开始库存只有每日及总库存的概念,实现也比较简单。为了保障库存扣减的并发安全,使用Redis作为库存的存储。具体的库存扣减则通过提前设置好Redis值并不断扣减即可。由于Redis的单线程特性,也不用担心并发重复扣减的风险。Redis一直扣减直至数值小于0就表示库存已被耗尽。

rest, err := redis.decr(key)if err {  ...  return err}
if rest < 0 {  return limitErr}

而后需求迭代中出现了分时间段库存,每天需要分成多个时段来分配库存,每个时间段的库存如果未被耗尽会累积到下一个时段。这个时候通过简单的Decr命令就不能满足需求了。

  • 方案一 :为每个时段单独设置一个Redis的key,但这样会导致某个时段的库存被浪费,不符合功能需求。要实现库存累积效果,需要在进入下一个时段时将上一个时段的库存加到下一个时段,增加了实现复杂度。

  • 方案二 :继续使用同一个库存key,但需要拆分成两次Redis命令调用,首先判断当前时段的库存是否足够,然后进行扣减库存。这种非事务性的执行方式可能导致并发问题,难以确保库存被准确扣减。

考虑到实现成本,我们选择了方案二。具体逻辑如下:

Image
  • 当前剩余未释放库存 = 总库存 - 当前已释放库存(即timeStock)

    ▪ 当前已释放库存是当前已过时段各个库存的累加。

  • 如果当前库存 < 当前剩余未释放库存,表示当前时段已无可使用库存,返回-1。

  • 否则,返回当前剩余未释放库存-1。

Image

为了解决并发问题,我们引入了Lua脚本,确保库存扣减的原子性。

LUA脚本伪代码:

# KEYS[1] 为 redis的key# ARGV[1] 为 提前算出的剩余未释放库存# return的值 < 0 代表库存扣减失败local _args1 = ARGV[1]local stock = redis.call('GET', KEYS[1])if stock < _args1 then    return -1else    local rest = redis.call('DECR', KEYS[1])    return restendreturn -1

奖品横向迭代

Image

商城内部的奖品都统一维护在奖品库里,每个类型的奖品都有通用属性和扩展属性,同时不同的奖品背后对应着不同的资产,需要有独立的发奖逻辑(资产发放)。因此每增加一个类型的奖品,不仅需要增加奖品类型的枚举,也需要对接下游资产的发放功能。

商城从一开始便不断的在新增不同类型的奖品,从优惠券、代金券、津贴,到后面拓展出现金、虚拟奖品、道具等奖品。

而一些特殊的奖品,比如抽奖券,单独实现一个发奖逻辑就不能实现一个完整功能,为此我们还特地拓展出抽奖券玩法。具体玩法是:用户需要在开奖前获得抽奖券,这时会发放一个抽奖码给用户,我们会在指定时间根据规则进行开奖并通知获奖的用户。这里除了发放抽奖券的逻辑之外,我们还需要一个独立的开奖功能。

开奖流程如下图:

Image

抽奖券的玩法迭代:

抽奖券玩法的核心在于开奖定时任务的逻辑,这类抽奖券活动不同于奖品兑换,单次活动可能有百万人参与,一万人中奖的情况。那么这样开奖Job的实时计算量就过于庞大了,测试环境按照这个量级进行开奖的话,耗时就已达到10多分钟之久。这个时长对于开奖脚本来说是不可接受的了,不仅用户体验达不到预期,而且时长太长,这段时间如果服务一旦出现其他情况(如新功能部署、容器节点被调度等)中断开奖流程更是会阻塞流程。

为此我们做了以下迭代优化:

  1. 开奖Job的实时计算前置:在用户兑换抽奖券时,系统提前为用户打好标签,并随机计算出一个分数。将用户的分数加入到Redis的有序队列中。这样,在开奖时只需从有序队列中取数,大大减少了实时计算量。

  2. 开奖Job引入并发操作:允许多场次和多奖品同时进行匹配,显著提高开奖效率。当然,为了避免并发操作引起的数据错乱,在关键流程中加入了Mutex锁,确保数据的一致性和完整性。

  3. 异常通知与一键重开功能:在应用出现各类意外情况导致开奖流程被阻塞时,我们会有异常通知告知负责人,并提供一键重开的功能。

最终效果:经过以上优化,正式版上线时,100万人的开奖时长从10多分钟缩短了30倍,仅需20秒即可完成开奖。这不仅大幅提升了用户体验,还增强了系统的稳定性和可靠性。

兑换防刷迭代

针对于防刷策略,项目从最开始只是通过接口验签及H5参数加密进行防控,但这种方式的破解成本比较低,也很容易被黑灰产刷接口。到后面我们便系统梳理了从业务层、接口层及黑灰产防控三方面对代码进行加固。

  • 业务层防刷

    ▪ 首先是奖品维度的频控,这是基于3.2-业务架构概述中模板模型实现的,包含每日频次、周期循环频次以及总频次。

    ▪ 其次是活动维度的频控,这个是在兑换流程中,商城中台给业务方提供了开放能力。在兑换前置环节可以自定义校验逻辑,比如N天可兑换N次。

Image
Image
  • 接口层防刷

    ▪ 这个主要是依赖于得物统一的流控中心,可以针对于单个接口配置限流和熔断策略,这里就不过多赘述了。

  • 黑灰产防控

    ▪ 针对于这部分流量,我们首先是通过接口验签和时间校验先过滤一部分。

    ▪ 其次是针对IP及设备的黑名单过滤。

    ▪ 还有就是针对于高价值奖品,我们会在前置环节预埋token,只有通过正常流程的用户才能进行兑换。

项目稳定性建设

商城作为一个潜在的资损高危区,在稳定性建设上投入大量资源是必要的。本文我们首先聚焦于问题发现及应急止血方面的工作,包括通过监控和告警快速发现问题,以及通过一键止损开关及时对发生异常的业务进行熔断。

监控告警

在监控告警方面,我们采用了多种技术手段来确保系统的稳定性和及时发现潜在问题:

业务监控组件:对于一些明细数据的同环比对比,我们使用了得物通用的业务监控组件。这种组件能够实时监控业务数据的变化,帮助我们快速发现异常情况。

Prometheus SDK埋点:对于一些需要分位数统计的数据,我们使用了Golang的Prometheus SDK进行埋点,并统一收集上报。

最终这些数据统一接入到增长的监控大盘中,能够清晰地看到各个接入方实时的访问数据、兑换数量及面额等。

Image

告警配置:根据采集到的数据,我们根据规则指标制作了各类告警通知,确保在异常情况发生时能够及时响应:

  • 事前预警 :奖品库存的实时通知与查看,确保库存充足。

  • 事中告警 :奖品数量及金额的同环比变化,及时发现异常波动。

  • 事后统计 :奖品的核销率及T+1天金额对账,确保数据准确无误。

Image
Image
Image

一键止损开关

为了在发生异常时能够快速响应,我们引入了一键止损开关。这个开关能够在检测到异常情况时,立即对相关业务进行熔断,防止资损进一步扩大。通过这种机制,我们能够在最短时间内控制问题,减少损失。

五

更多拓展

抽奖组件搭建器

为了进一步提升抽奖玩法的易用性和灵活性,我们与前端团队联合打造了可视化的H5抽奖组件。该组件基于商城中台,提供了完整的抽奖解决方案,运营人员只需按照SOP文档进行配置,即可快速接入抽奖玩法并投入使用,真正实现了抽奖玩法的“开箱即用”。

产品特性:

  • 开箱即用:  提供完整的前后端组件, 支持九宫格/翻拍/老虎机等样式;

  • 中奖控制:  中奖次数及中奖频次控制;

  • 库存控制:  奖品每日/总库存控制, 分时段释放;

  • 多种奖品:  优惠券/代金券/津贴/虚拟奖品等;

部分组件样式如下,也支持在可视化的H5搭建器内自定义组件样式。

Image

在抽奖组件的设计中,抽奖次数的获取是一个关键环节。最初,这一功能完全依赖于业务方自行实现,这导致只有已经接入过商城玩法的业务方才能使用H5的抽奖组件。为了提升组件的通用性和易用性,我们进行了以下优化:

引入增长的任务中台

为了简化抽奖次数的获取流程,我们引入了增长的任务中台。通过这一中台,用户可以完成配置的指定任务来获得积分,这些积分可以直接用于参与抽奖。

Image

打造简易的积分系统

我们打造了一个完整的积分系统,实现了从获得积分、消耗积分到领取奖励的闭环流程。具体实现如下:

  1. 获得积分:用户通过完成指定任务(任务中台提供了非常丰富的任务类型)获得积分。

  2. 消耗积分:用户使用积分参与抽奖,系统自动扣除相应积分。

  3. 领取奖励:用户中奖后,系统自动发放奖品,完成整个抽奖流程。

这种闭环流程的设计带来了多重优势:

  • 简化接入流程 :业务方无需自行实现抽奖次数的获取逻辑,降低了接入成本。

  • 增强通用性 :所有业务方都可以通过积分系统使用H5抽奖组件,提升了组件的适用范围。

六

总结

本文讲述了增长兑换商城整体的业务框架及部分功能的实现细节。兑换商城作为一个中台,承接了不同上下游提出的需求,很多功能的实现都需要考虑到通用性及拓展性,而一些复杂需求或功能的实现,是否会加重配置的难度,影响后续业务方的接入成本,都是需要在项目迭代中不断思考的问题。不仅如此,在迭代过程中的稳定性保障也会是商城自始至终的基本要求。

对于兑换商城之后的规划:

  1. 拓展玩法:引入更潮流、更具吸引力的玩法,以保持用户的持续参与和新鲜感。通过不断创新,确保商城始终处于用户增长的前沿。

  2. 降本提效:输出简版玩法及后台配置,维护每个版本的Changelog及SOP文档,确保迭代过程透明且可追溯。精简业务接入流程,减少开发成本和维护成本,提升整体效率。

  3. 交流进步:本文作为大纲综述,简要的讲解了兑换商城整体的架构,后续将输出更深入的博客文章,详细探讨兑换商城的核心技术实现、优化策略。通过不断的技术交流,促进项目的成熟和优化,确保商城系统的高效稳定运行。

往期回顾

1.得物自研DGraph4.0推荐核心引擎升级之路

2.大语言模型的训练后量化算法综述 | 得物技术

3.如何合理规划Elasticsearch的索引|得物技术

4. DPP推荐引擎架构升级演进之路|得物技术

5.Cursor 在前端需求开发工作流中的应用|得物技术

文 / 麦饼

关注得物技术,每周一、三更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

“

扫码添加小助手微信

如有任何疑问,或想要了解更多技术资讯,请添加小助手微信:

Image