您当前位置:资讯中心 >其他 >浏览文章

春节活动 - 高峰值奖励发放技术方案

来源:互联网 日期:2022/8/31 14:48:27 阅读量:(0)

作者|张健

1. 背景

2022年春节活动在8款字节系 APP 上线,包含了红包雨、集年味卡和烟火大会等诸多玩法。红包雨、集卡开奖和烟火大会都存在高峰值突发流量。其中,红包雨活动会在10分钟内给几千万甚至上亿用户发放上亿现金奖励,且大多数请求集中在前3分钟。在项目启动时,红包雨活动作为最大的流量来源,预估的发红包峰值流量有180万 QPS 。

为了保证用户体验、活动效果和资金安全,红包雨系统需要保证超高的稳定性。在系统设计上不能强依赖任何外部系统,在极端情况下仅需要红包雨服务可用,用户请求即可正常处理并返回结果。奖励系统作为红包系统的下游服务,负责用户奖励的入账,需要承载最高180万 QPS 的奖励发放请求,并且在出现异常情况时保证用户体验无损,奖励可以最终入账,做到不超发不少发。

图片

2. 技术挑战

2.1 峰值流量高

除夕当天会进行7场红包雨,从12:00起每小时进行一场,集卡开奖和烟火大会于19:30开始。当晚20:00前后,红包雨、集卡开奖和烟火大会的发奖流量将会叠加在一起,届时可能产生超过200万 QPS 的发奖流量。下游资产中台服务仅提供30万 QPS 的现金红包、40万 QPS 的优惠券入账能力。奖励系统需要削峰限流,异步入账奖励,确保下游服务不过载。

2.2 奖励种类多

除现金红包外,在集卡和烟火大会场景会发放10多种优惠券、实物奖励、头像挂件等。不同的优惠券由不同的下游系统发放,且每个系统的吞吐能力不同,甚至部分系统只能提供2000 TPS 的处理能力。奖励系统在进行削峰限流时,不同奖励种类限流的阈值需要根据下游系统吞吐能力进行个性化配置。下游系统能力有限的情况下,需要保证现金优先入账。

2.3 系统高可靠

引入消息队列进行奖励异步发放后,需要尽可能保证奖励事件的可靠投递和可靠消费,任何奖励最终都要入账,还需兼顾消息队列集群的稳定和容灾。

在内部服务出灾的情况下,或奖励事件在消息队列中堆积时,需要做到用户无感知,用户在活动钱包页可见奖励流水,随时可以正常提现。除通过消费奖励事件入账外,还需引入用户提现行为触发强制入账的能力,与此同时还要保证安全可靠,不能被黑产攻击造成资金损失。

3. 技术方案

基于春节活动峰值流量高、稳定性要求高的特点,为了保证高峰值流量下奖励系统稳定可靠,技术方案选型时选择了基于消息队列削峰、异步处理请求的总体方案。奖励发放的大概流程如下:

图片

在奖励事件生产侧,为了尽可能降低上游接入方的开发成本,基于不同接入场景特性,由奖励系统提供奖励 SDK ,并定义简单清晰的发奖接口,供接入方选用。奖励事件的可靠投递由 SDK 内部保证。奖励事件 MQ 使用了公司内 ByteMQ 和 RocketMQ 两种消息队列,防止因单个消息队列集群宕机导致整个系统不可用。

在奖励事件消费侧,针对每一个 Topic 创建一个消费者服务,四个消费者功能完全一致。由消费者服务保证消息可靠消费和消费限速。

除激励金币外,其他奖励类型通过资产中台服务调用各个下游发放。春节活动期间,资产中台暂未支持发奖请求的削峰,需要在奖励系统前置进行。业务上,同一订单号只能发放一种奖励一次,由于资产中台和激励中台系统之间数据隔离,需要奖励系统支持单一订单号跨服务发放幂等。

3.1 奖励SDK设计

SDK 以代码“内嵌”的方式运行在接入方服务内,可以避免 RPC 方式网络传输、请求数据序列化和返回数据反序列化带来的时延和性能消耗。尽管 SDK 的整体时延和性能优于 RPC 方式,对 SDK 本身的稳定性、性能消耗和接口响应时延依然有非常高的要求。以红包雨场景为例,发奖接口需要50ms内返回,若响应时间超过50ms将会增加整个活动玩法接口的处理时间,影响红包雨服务的吞吐量,最终会影响用户参与春节活动的体验。

奖励 SDK 在功能上实现了奖励Token 的生成和存储和奖励事件的可靠投递。 接口设计上面向不同接入场景针对性地提供定制接口,最大限度的降低使用方的理解和接入成本,减少开发周期。

为了保证 SDK 代码结构清晰,并具有较高的拓展性和可维护性,在代码结构层面,SDK 内部使用了分层设计,分为了对外接口层、内部接口层和内部实现层。

3.1.1 对外接口层

对外接口层定义了暴露给使用者的外部接口,除初始化、反初始化等接口和通用的异步发奖接口外,还为红包雨、烟火大会和集卡分别提供差异化定制接口。通用异步发奖接口定义和奖励 RPC 服务的异步发奖接口保持一致,通过调用 RPC 接口和通过 SDK 发奖的接入方可以低成本的双向迁移。

定制接口结合使用场景的特点,固化诸如活动 ID、场景 ID、奖励类型等通用参数,减少接口入参个数,函数名称语义更清晰,可进一步降低接入方的使用成本,提升接入方代码的可读性和可维护性。对于部分场景,还承担了全局幂等 ID的拼接工作。

发奖请求除用户信息(用户 ID、设备 ID 和 AppID )、奖励信息(奖励类型、数值)外,还需携带一个全局唯一 ID 作为订单号,以实现根据订单号幂等的能力。订单号由接入方根据活动信息和用户信息拼接而成。所有的接口都支持调用方写入拓展字段(Map 格式的键值对)保存业务自定义信息。

3.1.2 内部接口层

内部接口层提供了通用的奖励异步发放接口(SendBonus)、Token 生成和存储接口(GenBonusToken)、初始化接口和反初始化接口。外部接口基于内部接口进行差异化封装,提供更细化的功能。内部接口层对上层屏蔽内部实现细节。

以异步发放接口 SendBonus 函数为例,主要集成了参数检查、打点监控、虚拟队列(Queue)选择、奖励消息的构造和发送、奖励 Token 的生成和存储等功能。参数校验通过后,SendBonus 接口即返回奖励 Token,供上层调用者使用(一般是返回给前端和客户端)。

/*
SendBonus
@act 活动信息
@user 用户信息
@bonus 奖励信息
*/
func SendBonus(ctx context.Context, act Activity, user User, bonus *BonusContent) (string, error) {
// 参数检查
if err := CheckParams(act, user); err != nil {
// 输出错误日志,监控异常请求
return "", err
}

// 检查奖励类型是否合法
cfg, err := CheckBonus(bonus)
if err != nil {
// 输出错误日志,监控异常请求
return "", err
}

// 构造奖励消息
message := &event.BonusEvent{...}

// SendEvent内部根据奖励属性选择队列
if err = queue.SendEvent(ctx, message); err != nil {
return GenBonusToken(ctx, act, user, info, true), err
}

// 构造并返回奖励Token
return GenBonusToken(ctx, act, user, info, true), nil
}
关键字:
声明:我公司网站部分信息和资讯来自于网络,若涉及版权相关问题请致电(63937922)或在线提交留言告知,我们会第一时间屏蔽删除。
有价值
0% (0)
无价值
0% (10)

分享转发:

发表评论请先登录后发表评论。愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。