当前位置: 首页 > 科技观察

春节钱包大流量奖励系统入账及展示的设计与实现

时间:2023-03-17 18:12:57 科技观察

春节钱包大流量奖励系统入口展示的设计与实现对于本期工作的介绍和总结,首先整体介绍业务背景和技术架构,然后说明具体的实现方案对每个难点,最后做一个抽象的总结,希望对后续的活动有所指导。一、背景&挑战&目标1.1业务背景(1)支持八段:字节产品2022春节活动需要支持八段APP产品(包括抖音/抖音火山/抖音至尊版/西瓜/今日头条/今日头条速递版/番茄小说/番茄聆听)奖励可互换。用户可在上述任一端参与活动,获得的奖励可在其他端提取使用。(2)玩法多变:主要有卡片收集、好友页红包雨、红包雨、卡片收集抽奖和烟花汇演等。(3)奖励多样:奖励类型有现金红包、补贴视频红包、商业广告券、电商券、支付券、消费金融券、保险券、信用卡券、茶券、电影票、dou+券、抖音文创券、头像挂件等。1.2核心挑战(1)设计实现八端打赏入口展示互通大流量解决方案。最高估计是360wQPS奖励。(2)奖励多场景,玩法多变;奖励种类繁多,共有10多种奖励。连接到多个下游系统。(3)从奖励体系的稳定性、用户体验、资金安全和基础运营能力等方面进行全面保障,确保赛事的顺利进行。1.3最终目标(1)奖励入口:设计实现八端奖励互通的奖励入口系统,连接多个奖励下游系统,平滑不同奖励下游之间的差异,对上游屏蔽底层奖励入口细节,以及设计一个统一的接口协议来提供Upstream业务。提供统一的错误处理机制,幂等入账能力,奖励预算控制。(2)奖励展示/使用:设计实现活动钱包页面,支持展示用户在八段获得的奖励,支持用户查看、提现(现金)、使用优惠券/挂件等。(3)基础capabilities:【基础sdk】提供查询红包余额、累计收益、用户是否在春节活动期间获得奖励等基础sdk,供业务方查询使用。【预算控制】打通上游奖励发放端的算法策略,实现大额卡券入口的库存控制能力,防止超发。【提现控制】跨年夜多轮奖励发放后,将向用户提供灰度提现能力以及提现时尚未入账的处理能力。【运营介入】活动页面灵活的运营配置能力,支持快速发布公告,及时到达用户手中。针对黑天鹅事件,支持批量补发优惠券和红包功能。(4)稳定性保障:在大流量入口场景下,保障钱包核心路径的稳定性和完善性,通过资源扩容、限流、熔断等常见的稳定性保障手段,保障用户奖励、降级、自下而上、资源隔离等方向核心体验。(5)资金安全:在大额入账场景下,通过幂等、对账、监控、告警等机制,保障资金安全,尽可能将用户资产送出。(6)活动隔离:内测活动、灰度活动、春节官方活动三个阶段的奖励录入和展示数据是分开的,互不影响。2.产品需求介绍用户可在任意端参与字节跳动的春节活动,获得奖励。以抖音红包下现金红包的场景为例。具体业务流程如下:登录抖音→参与活动→活动钱包页面→点击提现按钮→进入提现页面→进行提现→提现结果页面,也可以从钱包页面。发奖核心场景:领卡:抽卡时发放各种卡券,锦鲤还发放大额现金红包,抽卡时发放奖金和券;红包雨:红包、优惠券、视频补贴红包,其中红包和优惠券分别最高180wQPS;烟花秀:红包、优惠券和头像挂件。3.钱包资产中台设计与实现在2022春节活动中,UG主要负责活动的玩法实现,包括具体活动相关的业务逻辑和稳定性保障,如领卡、红包雨、和烟花会议。钱包的定位是实现高流量场景下奖励进入、奖励展示、奖励使用和资金安全的相关任务。其中,资产中心负责奖励分配和奖励展示。3.1春节资产资产中台整体架构图如下:钱包资产中台核心系统划分如下:资产下单层:汇聚八端奖励入口链路,提供统一的接口协议给对接上游活动业务方如UG、激励中台、视频发放红包等奖励功能,同时屏蔽上游下游对接奖励业务的逻辑处理,支持预算控制、补偿、订单号权、etc.活动钱包api层:汇聚八端奖励展示链接,同时支持大流量场景3.2资产订单中心设计核心发行模型:说明:活动ID唯一区分一个活动,本次春节分配一个单独的父活动ID与场景ID和特定类型的奖励一一对应,定义了r的唯一配置在这种情况下颁发的奖励。场景ID可以配置的能力包括:发放奖励单副本;是否需要赔偿;限流配置;是否进行库存控制;帐户。为可选服务访问提供可插入功能。实现效果:实现不同Activity之间的配置隔离。每个activity的配置都是树形结构,实现一个activity可以下发多个reward,一个reward可以下发多个rewardID。一个rewardID可以有多个分发场景,支持不同场景的个性化配置订单号设计:资产订单层支持订单号维度的幂等性,订单号设计逻辑为${actID}_${scene_id}_${rain_id}_${award_type}_${statge},从单号设计层面保证不会超发,每个场景的奖励用户最多只能领取一次。4.解决核心难点4.1难点一:支持八端奖励数据交换前面的背景已经介绍过了。2022春节活动共有8款产品终端参与。其中,抖音系和今日头条系APP的账号体系不同。因此,无法通过用户ID开启奖励互通。具体解决方案是字节账户中心打通八端账户体系,为每个用户生成唯一的actID(手机号优先级最高,如果不同终端登录的手机号相同,不同终端上的actID相同)。在钱包端,基于字节账户平台提供的唯一actID,设计并实现了一个通用的解决方案,支持八端奖励的录入、查看和使用,即绑定每个用户的奖励数据actID,通过actID维度实现录入和查询,可以实现八端奖励互通。示意图如下:4.2难点二:高场景场景奖励入口实现年度春节活动,发现金红包是最关键的部分,今年也不例外。有以下几个原因:DiscoveryGold红包估计最大流量180wTPS。现金红包本身价值很高,需要保证资金安全。用户对现金高度敏感,在保证用户体验和功能完整性的同时还要考虑成本问题。如上所述,发现黄金红包面临着比较大的技术挑战。发红包其实是一种交易行为,资金流向是从公司的成本再到个人账户。(1)从技术角度,需要支持订单号维度的幂等性,多次请求同一个订单号只记一次。订单号生成逻辑为${actID}_${scene_id}_${rain_id}_${award_type}_${statge},从订单号设计层面保证不超发。(2)支持高并发,传统的解决方案有以下两种:以上两种传统意义上的技术方案都有明显的缺点,那么大家想一想,有什么方案既能节省资源又能保证用户体验呢?最后,采用最重要的是红包雨代币方案。具体方案是采用异步记账加少量分布式存储的方式实现,方案较为复杂。以下是详细介绍。4.2.1红包雨代币方案:本次春节活动有红包雨/集卡抽奖/烟花活动下的红包派发场景。如前所述,奖励分配的最高预估QPS为180wQPS。按照现有的记账设计,需要大量的存储和计算资源来支撑。根据预估发放红包数量/商品最长可接受投递时间,计算出钱包实际入账最低支持TPS为30w,所以有一个压单的过程在实际分布中。设计目标:当预估分配给用户的活跃度(180w)与实际账号(30w)存在较大差距时,保证用户的核心体验。用户在浏览和使用前端页面时,无法感知到按下订单的过程,即不影响浏览和使用体验。相关展示的数据包括余额、累计收益和红包流量,使用包括提现等具体设计方案:我们在大流量场景下,每次给用户发红包都会生成一个加密token(使用非对称)加密,包括红包的元信息:红包数量、actID、发放时间等),分别保存在客户端和服务端(容灾互备),每个用户都有一个token列表。每次发放红包,都会在Redis中记录token的入账状态,然后将用户在活跃钱包页面看到的现金红包流量、余额等数据与注册红包列表结合起来+tokenlist-accounting/tokeninaccount结果列表。同时,为保证用户提现体验不感知红包压单过程,在进入提现页面或点击提现时,强制记录未计入代币列表,确保提现余额。用户取款时的账户是应该记录的总金额,用户的取款过程没有被阻塞。示意图如下:token数据结构:token使用pb格式,存储消耗实际比使用json小一倍,节省了请求网络的带宽和存储成本;同时也降低了序列化和反序列化的CPU消耗。//红包雨令牌结构类型RedPacketTokenstruct{AppIDint64`protobuf:varint,1,optjson:AppID,omitempty`//结束IDActIDint64`protobuf:varint,2,optjson:UserID,omitempty`//ActIDActivityIDstring`protobuf:bytes,3,optjson:ActivityID,omitempty`//ActivityIDSceneIDstring`protobuf:bytes,4,optjson:SceneID,omitempty`//SceneIDAmountint64`protobuf:varint,5,optjson:Amount,omitempty`//红包数量OutTradeNostring`protobuf:bytes,6,optjson:OutTradeNo,omitempty`//订单号OpenTimeint64`protobuf:varint,7,optjson:OpenTime,omitempty`//绘制timeRainIDint32`protobuf:varint,8,opt,name=rainIDjson:rainID,omitempty`//红包雨IDStatusint64`protobuf:varint,9,opt,name=statusjson:status,omitempty`//账号status}token状态机流程:在调用账户真正入账前,会设置为processing(2)状态,调用账户成功到success(8)状态。没有发红包失败,后续重试即可成功。Token安全保证:采用非对称加密算法,尽可能保证存放在其中的客户端不被破解。加密算法是一个秘密仓库,限制其他人的访问。同时考虑到在极端情况下,如果token加密算法被黑客破译,可以通过监控告警检测到,可以降级。4.2.2活跃钱包页面显示红包的要求背景:活跃钱包页面显示的红包是三个数据源的组合:现金红包入口、现金提取和c2c红包。按照创建时间倒序排列,需要支持分页,可以降级,保证用户体验不感知发现金红包压包过程。4.3难点三:发送奖励链接的稳定性保障比较依赖系统的稳定性呢?解决方案:现金红包录入最基本的功能是录入用户收到的红包,同时支持幂等和预算控制(避免超发),红包账户的幂等设计强依赖数据库维护事务一致性。但是,如果出现极端情况,中间环节就可能出现问题。如果是弱依赖,需要在不影响分发主进程的情况下降级。向钱包方向发送红包的最短路径是依靠服务实例的计算资源和MySQL存储资源实现现金红包。红包强弱看梳理图解:4.4难点四:大批量发放优惠券预算控制要求背景:晚上7点30分开始烟花汇演。在春节除夕。对卡券发行进行库存控制,防止超发。具体实现:(1)钱包资产中心维护每个优惠券模板ID的消费量和发放量。(2)每张优惠券发放前,算法策略会读取钱包sdk,获取优惠券模板ID的消耗量和库存总量。同时,会设置一个门槛。卡券余额不足10%时,不发券(用券或祝福盖)。(3)同时,钱包资产中心会在优惠券发放过程中累加每个优惠券模板ID的消费量(使用Redisincr命令原子累加消费量),然后与活跃库存总量进行比较。如果消耗量大于库存总量,那么Rejection,防止多发,也是一个自下而上的过程。具体流程:优化方向:(1)大流量下使用Redis计数,单个key会出现hotkey问题,需要通过key拆分来解决。(2)在大流量场景下操作Redis会出现超时问题。回到上游处理,上游会不断重试发放优惠券,消耗库存多,发放少。本次春节活动实际活动库存以预估库存为准。添加5%数量级用于缓解超时引起的罕见问题。4.5难点五:高QPS场景热键读写稳定性保障要求背景:晚上7点30分烟花活动开始。最大流量预估为读180wQPS,写30wQPS。这是一个典型的超大流量,hotkey,更新延迟不敏感,非数据强一致性场景(数量一直在累积),容灾和降级处理必须同时进行。1%。4.5.1方案一提供SDK接入方式,复用主会场机器实例资源。比较容易想到在高QPS下使用Redis分布式缓存实现单key的读写,但是单key的读写会命中一个实例。压测后单实例的瓶颈是3wQPS。所以一种优化是拆分多个key,然后使用本地缓存覆盖底线。具体编写过程:设计拆分100个key,每次发红包使用incr命令根据请求的actID%100累计个数。因为不能保证幂等性,超时后不会重试。读取过程:与写入过程类似,先读取本地缓存。如果本地缓存值为0,则读取各个Redis的key值,累加在一起返回。问题:(1)拆分100个key时,会出现readdiffusion的问题。需要申请更多的Redis资源,存储成本比较高。而且可能会出现读取超时的问题,不能保证一次能把所有的key都读取成功,所以返回的结果可能会比上一次少。(2)在容灾方案上,如果申请Redis备份,同样需要更多的存储资源,额外的存储成本。4.5.2方案二的设计思路:在方案一实现的基础上进行优化,同时考虑数的持续积累、成本节约和容灾方案的实现。写场景使用本地缓存结合写请求进行原子累加,读场景返回本地缓存的值减少额外的存储资源。使用Redis实现中心化存储,最后大家读取的是同一个值。具体设计方案:每个docker实例启动时,都会执行定时任务,定时任务分为读Redis任务和写Redis任务。读取过程:本地定时任务每秒执行一次,读取一个Redis单键的值,如果获取到的值大于本地缓存,则更新本地缓存的值。对外暴露的sdk可以直接返回本地缓存的值。有一个问题需要注意。每次实例启动的第一秒内都没有数据,有数据时会阻塞读取返回。写入过程:因为读取是通过读取本地缓存完成的(本地缓存不会过期),所以只需要处理并发情况下的写入即可。本地缓存写变量使用go的atomic.AddInt64支持对本地写缓存的值进行原子累加。每次执行更新Redis的定时任务,先将本地写缓存复制到amount变量中,然后原子地从本地写缓存中减去amount的值,最后将amount的值incr到Redis的单键中实现Redis的单键值一直累加。容灾方案是使用备份Redis集群,写入时双写。一旦主机组挂掉,设计了一个配置开关,支持读取备份的Redis。两个Redis集群的数据一致性是通过定时任务来实现的。本方案调用Redis的流量与实例数成正比。据调查,读端服务在主会场有20000个实例,写端服务在资产中心有8000个实例。因此,Redis实际需要支持的QPS为28000/定时任务执行间隔(单位:s)。经过压力测试验证,Redis单实例可以支持单key20000次get和8kincr操作。因此,定时任务的执行时间间隔设置为1s。如果实例较多,可以考虑延长执行间隔。具体编写流程图如下:4.5.3方案对比结论:综合考虑实现效果、资源成本和容灾,最终选择方案2上线。4.6难点六:需要母子活动平滑切换背景:为了保证本次春节活动的最终上线效果和投放质量,实际分三个阶段进行。(1)第一阶段为内部人员测试阶段。(2)第二阶段为外部预演阶段,选择部分外部用户对春节活动功能进行验证(灰度放量)。(3)第三阶段正式春节活动。产品的要求是这三个阶段是独立的阶段,包括用户奖励、展示奖励和使用奖励的隔离。技术挑战:上游有多个调用钱包发送奖励,钱包下游有多个奖励业务,沟通成本高,大家自己改,配置错误概率比较高,无法修改变化同步,会有更大的技术挑战安全隐患。设计思路:钱包资产中心作为记录奖励的唯一入口,汇聚了整个活动配置开关的实现。设计父活动和子活动的层次配置。上游请求参数统一传给父活动ID,代表春节活动。钱包资产中心根据请求时间决定使用哪个子活动配置进行奖励,实现不同时间段的不同活动。产品需求。降低通信成本,降低配置错误概率,并可同步切换,大大提高研发和测试效率。示意图:4.7难点7:大流量场景下的资金安全保障为了保证大流量大预算现金红包发放的安全性,钱包在这次春节活动中做了三件事:现金红包发放和整体预算控制拦截单笔交易截获现金红包发放金额上限。大流量红包场景的资金对账。小时级对帐:支持红包雨/集卡/烟花红包h+1小时级对帐,部分场景设置bottom-uph+2校验。准实时对账:红包雨中已经记录的红包数据,通过钱包资产中台和活动端进行实时对账。是否有异常入账,如果发现报警,会有应急预案进行处理。5.通用模式抽象在经历了春节超大流量活动的设计和实现之后,有一些总结和经验分享给大家。5.1容灾降级在大流量场景下,为了保证赛事最终的上线效果,必须做好容灾。参考业界常见的实施方案,如降级、限流、熔断、资源隔离,以及根据预估的参与人数和活动效果进行使用和存储预估。5.1.1限流级别(1)限流采用api层nginx入站限流、分布式入站限流、分布式出站限流。这些限流器是字节跳动公司层面的公共中间件,已经过大流量验证。(2)首先进行了实际的单例压测,根据单例承载的流量和春节活动期间对服务的预估流量进行扩容。结合下游情况,tlb的入站流量,入站流量限制和出站流量限制分别进行了详细和完整的配置。限流的目标:保证自身服务的稳定性,防止外部预期流出击垮自身服务,防止雪崩效应,保证核心业务和用户核心体验。简单集群限流是实例维度的限流。每个实例限流的QPS=总的配置限流QPS/实例数。对于多机低QPS,可能会有不准确的地方。需要经过实际压测,及时调整配置值。对于分布式入流量和出流量限制,两种使用方式如下,分别支持高QPS和低QPS,只是SDK的用法和功能不同。一般低QPS要求精度高,采用redis计数方式,用户自备redis集群。高QPS要求精度低,退化为单实例限流QPS/tce实例总数。5.1.2降级级别对于大流量场景,每个核心功能都必须有相应的降级方案,以保证核心链路在紧急情况下的稳定性。(1)春节奖励的入账和活动钱包页面的方向已经准备好充分的运营方案。共有26个降级开关。关键时刻弃车保帅,避免单点问题影响核心环节。(2)以黄金红包链接为例,钱包方向最终的完整降级方案是只依赖docker和MySQL,其他依赖可以降级。如果MySQLmaster出现问题,可以紧急联系master,虽然最后一个没用,但是前提一定要设计好,保证活动安全。5.1.3资源隔离级别(1)无需重新发明轮子,提高开发效率。由于钱包资产中心也支持日常的抖音资产分发需求,所以本次春节活动也复用了现有的接口和代码流程来支持奖励分发。(2)同时,针对本次春节活动,在服务层面实现集群隔离,打造专属的活动集群,隔离底层存储资源,做到活跃流量和常规流量互不影响.5.1.4存储预估(1)不仅需要考虑验证Redis或MySQL存储是否能够承受相应的流量,还需要根据实际采集参与和分布数据预估存储资源是否充足。(2)对于字节跳动的Redis组件,可以纵向扩展(每个实例增加存储,最大10G),也可以横向扩展(单个机房最多500个实例),因为Redis是在三个机房同步的,所以计算存储时,只考虑一个机房的存储限制。必须预留足够的缓冲区,因为水平扩展是一个非常缓慢的过程。在紧急情况下存储资源不足时,只能通过配置开关提前移除依赖的存储,这需要提前设计。5.1.5压测级别本次春节活动,钱包奖励入口和活动钱包页面均通过了全链路压测的全面验证。以下是一些经验的总结。在压测前,需要建立一个监控仪表板,对整个链路进行压测,以便在压测过程中及时、方便地发现问题。对于MySQL数据库,在红包雨等大流量官方活动开始前,进行小流量压测,预热数据库,在流量高峰前提前建链,减少耗时官方活动期间大量搭建链接,保证红包链接的数据库级别。稳定。压测过程中必须传输压测标准,支持全链路识别压测流量进行特殊逻辑处理,不干扰正常线上业务。压测流量没有特殊处理,压测流量的处理流程与线上流量一致。压测过程中,需要验证计算资源和存储资源是否能够承受预估流量,梳理压测方案,根据历史经验设置合理的初始流量,逐步增加压测流量,观察各种实时压力测试指标。存储资源压测数据应与线上数据隔离。对于MySQL和Bytekv,就是建压测表,对于Redis,Abase,就是在onlinekey上加压测前缀。测压数据应及时清理。Redis和Abase过期时间短,过期机制更方便。如果忘记设置过期时间,可以根据写好的脚本识别测压标准的前缀,删除即可。压测结束后,还要关注存储资源的各项指标是否达到预期。5.2微服务思想在日常的技术设计中,大家会遵守微服务设计的原则和规范,根据系统职责和核心数据模型拆分不同的模块,在互不影响的情况下提高开发迭代的效率。然而,微服务也有其缺点。对于流量非常大的场景,功能比较复杂,会经过多条链路,非常消耗计算资源。本次春节活动资产中台提供sdk包代替rpc进行微服务链接聚合,对外提供查询余额、判断用户是否获得奖励、强制入账等功能。最大访问流量数千万级,相比使用微服务架构节省了数万个CPU核的计算资源。6、系统未来演进方向梳理上下游需求痛点,优化资产中台设计与实现,提升基础能力,优化服务结构,提供一站式服务,让接入activity可以更专注于activity业务逻辑的研发。加强实时和离线数据看板能力建设,让奖励分配数据展示更清晰、更准确。加强配置和文件建设,对内降低对接活动的对接成本,对外提高活动商务方的访问效率。