共计 8888 个字符,预计需要花费 23 分钟才能阅读完成。
本文由字节跳动技术团队开发工程师王浩分享,有较多订正。
1、引言
对于挪动互联网时代的用户来说,短视频利用再也不是看看视频就完事,尤其抖音这种头部利用,曾经是除了传统 IM 即时通讯软件以外的新型社交产品了。
对于中国人一年一度最重的节日——春节来说,红包是必不可少的节日特定社交元素,而抖音天然不会被错过。在 2022 年的春节流动期间,抖音将视频和春节红包相结合,用户能够通过拍视频发红包的形式来给粉丝和好友送祝福。
本文将要分享的是春节期间海量红包社交活动为抖音所带来的各种技术挑战,以及抖音技术团队是如何在实践中一一解决这些问题的。
学习交换:
- 挪动端 IM 开发入门文章:《新手入门一篇就够:从零开发挪动端 IM》
- 开源 IM 框架源码:https://github.com/JackJiang2…(备用地址点此)
(本文已同步公布于:http://www.52im.net/thread-39…)
2、系列文章
《社交软件红包技术解密(一):全面解密 QQ 红包技术计划——架构、技术实现等》
《社交软件红包技术解密(二):解密微信摇一摇红包从 0 到 1 的技术演进》
《社交软件红包技术解密(三):微信摇一摇红包雨背地的技术细节》
《社交软件红包技术解密(四):微信红包零碎是如何应答高并发的》
《社交软件红包技术解密(五):微信红包零碎是如何实现高可用性的》
《社交软件红包技术解密(六):微信红包零碎的存储层架构演进实际》
《社交软件红包技术解密(七):支付宝红包的海量高并发技术实际》
《社交软件红包技术解密(八):全面解密微博红包技术计划》
《社交软件红包技术解密(九):谈谈手 Q 春节红包的设计、容灾、运维、架构等》
《社交软件红包技术解密(十):手 Q 客户端针对 2020 年春节红包的技术实际》
《社交软件红包技术解密(十一):最全解密微信红包随机算法(含演示代码)》
《社交软件红包技术解密(十二):解密抖音春节红包背地的技术设计与实际》(* 本文)
3、先看看红包业务玩法
抖音的整个红包流动玩法分为 B2C 和 C2C 两种玩法,上面对这两个玩法的流程简略介绍下,也不便读者了解后续的技术问题和解决思路。
3.1 B2C 红包
在 B2C 红包玩法中,用户须要先来抖音或者抖 Lite 加入春节红包雨流动,有肯定概率在春节红包雨流动中取得红包补贴。
用户能够在取得补贴后间接跳转到相机页面,或者在之后拍摄视频跳转到相机页面,在相机页面用户拍摄完视频后会看到一个红包挂件,在挂件中能够看到已发放补贴。
用户抉择补贴后点击下一步实现投稿后即可实现视频红包的发放。
如上图所示,从左至左:“图 1”是春节红包雨流动、“图 2”是红包补贴、“图 3”是红包挂件、“图 4”是 B2C 的红包发送 tab 页。
3.2 C2C 红包
在 C2C 红包玩法中,用户拍摄视频点击挂件,填写红包的金额和个数信息,抉择红包的支付范畴后,点击发送红包会拉起收银台,用户领取实现后点击下一步公布视频,即可实现 C2C 红包的发放。
如上图所示,从左至左:“图 1”是 C2C 红包发送 Tab 页、“图 2”是领取界面、“图 3”是红包领取后挂件展现。
3.3 红包支付
B2C 和 C2C 红包的支付流程都是一样的。
用户在抖音刷视频遇到有视频红包的视频时,视频下方有个支付红包按钮,用户点击红包支付,会弹出到红包封面。
用户点击红包封面的开红包后即可支付红包,支付胜利后会显示支付后果弹窗,在支付后果中用户能够看支付金额,以及跳转到支付详情页,在支付详情页中能够看到这个红包其余用户的支付手气。
如上图所示,从左至左:“图 1”是红包视频、“图 2”是红包封面、“图 3”是红包支付后果、“图 4”是红包支付详情。
最初,C2C 视频红包有个推广视频,大家能够更形象地理解下整体操作流程:(点击下方视频能够播放 ▼)
4、技术挑战
4.1 通用红包零碎的设计问题
在上节中有提到,本次春节流动(指的是 2022 年春节)须要同时反对 B2C 和 C2C 两种类型的红包。
这两个类型的红包既有一些类似的业务,也有很多不同的业务。
在相同点上:他们都包含红包的发放和支付这两个操作。在不同点上:比方 B2C 的红包发放须要通过应用补贴来发送,而 C2C 的红包发放须要用户去实现领取。B2C 的红包用户支付后须要去提现,而 C2C 的红包用户支付后间接到零钱。
因而须要设计一个通用的红包零碎来反对多种红包类型。
另外:对于红包零碎自身而言,除了发领红包外,还波及到一些红包信息的查问,以及各种状态机的推动,这些功能模块之间如何划分也是须要思考的一个点。
4.2 大流量补贴的发放解决问题
后面提到过:B2C 红包玩法会先进行补贴的发放。在春节流动期间,每场红包雨都会有大量的用户进入参加,如果将这些流量间接打到数据库,将须要大量的数据库资源。而春节期间数据库的资源是十分稀缺的,如何缩小这部分的资源耗费也是一个须要思考的问题。
4.3 红包支付计划的选型问题
在红包业务中,支付是一个高频的操作。
在支付形式的设计中,须要业务场景思考一个红包是否会被多个用户同时支付。多个用户同时去支付同一个红包可能会导致热点账户问题,成为零碎性能的瓶颈。
解决热点账户问题也有多个计划,咱们须要联合视频红包的业务场景特点来选取适合的计划。
4.4 稳定性容灾问题
在本次春节流动中,包含 B2C 和 C2C 两种业务流程,其中每个业务流量链路都依赖很多的上游服务和根底服务。
在这种大型流动中,如果呈现黑天鹅事件时,如何疾速止损,缩小对系统的整体影响,是一个必须要思考的问题。
4.5 资金平安保障问题
在春节流动期间,B2C 会发放大量的红包补贴,如果补贴产生超发,或者补贴的核销呈现问题,一个补贴被屡次核销,将会造成大量的资损。
另外 C2C 也波及到用户的资金流入流出,如果用户支付红包后如果发现钱变少了,也可能会造成大量的客诉和资损。
因而资金平安这块须要做好短缺的筹备。
4.6 红包零碎的压测问题
在传统的压测形式中,咱们个别会对某个大流量接口进行压测从而失去零碎的瓶颈。
然而在红包零碎中,用户的发领和查都是同时进行的,并且这几个接口之间也是相互依赖的,比方须要先发红包,有了红包后能力触发多集体的支付,支付实现后才能够查看支付详情。
如果应用传统的单接口的压测形式,首先 mock 数据会十分艰难。和领取相应的压测数据因为波及实名还须要非凡生成,而且单个接口单个接口的压测很难失去零碎的实在瓶颈。
因而如何对系统进行全链路的压测从而失去零碎精确的瓶颈也是咱们须要解决的一个问题。
认清了咱们要面临的技术挑战和技术难题之后,接下来将分享咱们是如何在实践中一一解决这些问题的。
5、通用红包零碎的设计实际
对于红包零碎,外围包含三个操作:
1)红包发送;
2)红包的支付;
3)未支付红包的退款。
另外:咱们还会须要去查一些红包的信息和支付的信息等。对于发送、支付和退款这三个外围操作,咱们须要对它们的状态进行一个保护。同时在咱们的业务场景中,还存在 B2C 特有的补贴的发放,咱们也须要保护补贴的状态。
在下面初步介绍红包零碎后,能够看到红包的几个功能模块:
1)发放;
2)支付;
3)退款;
4)补贴发放;
5)各种信息查问;
6)状态机的保护等。
对红包的性能进行梳理后,咱们开始对红包的模块进行划分。
模块划分准则:
1)性能内聚,每个零碎只解决一个工作(不便之后零碎的开发和迭代,以及问题的排查);
2)API 网关层只进行简略的 proxy 解决;
3)异步工作拆解;
4)读写拆散,将红包的外围操作和红包的查问分成两个服务。
模块划分后果:
1)红包网关服务:HTTP API 网关,对外对接客户端和 h5,对内封装各个系统 rpc 接口,限流,权限管制、降级等性能;
2)红包外围服务:次要承载红包外围性能,包含红包的发放、支付、退款,以及红包补贴的发放,保护红包状态机,红包的状态推动;
3)红包查问服务:次要承载红包查问性能,包含红包详情、红包发送状态、红包支付状态、红包支付详情、红包补贴信息;
4)红包异步服务:次要承载红包异步工作,保障状态机的流转,包含红包的转账,红包的退款,以及红包补贴的状态推动;
5)红包根底服务:次要承载红包各个系统的公共调用,例如对 DB,redis、tcc 的操作,公共常量和工具类,相当于红包的根底工具包;
6)红包对账服务:次要承载红包和财经的对账逻辑,按天和财经对账。
最初整个视频红包的零碎架构如图所示:
6、大流量补贴的发放解决实际
6.1 同步处分发放
在红包补贴发放链路流程中,为了应答春节的大流量,整个链路流程也经验过几次计划的迭代。
在最后的方案设计中,咱们是依照同步的补贴发放流程来解决的,上游链路调用红包零碎接口发券,发券胜利后用户感知到券发放胜利,能够应用该券来发放红包。
最后计划的整体流程如下图:
下面计划的一个问题是在春节流动期间,整个链路都须要能扛住流动期间的总流量,而且最终流量都会打到数据库,而数据库的资源在春节期间也是比拟紧缺的。
6.2 异步处分发放
为了解决同步处分发放的问题,咱们将整体流程改为通过 MQ 进行削峰,从而升高上游的流量压力。
相当于是从同步改为异步,用户参加流动后会先下发一个加密 Token 给客户端,用于客户端的展现以及和服务端的交互解决。
流动异步发券计划如下图:
这样算是解决了大流量的问题,然而相应地引入了其余的问题。。。
在最后计划中:用户的红包补贴都会先在红包零碎中落库,后续用户对补贴的查问和核销咱们都能在红包数据库中找到对应的记录。
然而在异步的形式中:整个补贴的入账预估须要 10min,而用户在 APP 界面感知到发券后可能马上就会开始应用用补贴来发放视频红包,或者会去红包挂件查看本人曾经支付的红包补贴,而此时补贴还未在红包零碎中入账。
6.3 最终计划
为了解决下面问题,咱们对红包补贴的视频红包发放和红包补贴查问的整个逻辑进行了批改。即在用户应用红包补贴进行视频红包发放时,咱们会先对该补贴进行一个入库操作,入库胜利后才能够用这个补贴进行红包发放。
另外对于查问接口:咱们无奈感知到所有补贴是否齐全入账,因而每次查问时咱们都须要去处分发放端查问全量的 Token 列表。同时咱们还须要查问出数据库中用户的补贴,对这两局部数据进行一次 merge 操作,能力失去全量的补贴列表。
在下面的流程中:为了解决 MQ 异步会有提早的问题,咱们在用户进行申请时被动地进行入账,而用户被动的操作包含应用补贴发放红包和查问补贴。
咱们为什么只在补贴发放红包时入账而在查问补贴时不入账呢?
因为用户的查问行为是一个高频行为,同时波及到批量的操作,在操作 DB 前咱们无奈感知该补贴是否入账,所以会波及到 DB 的批量解决,甚至用户每次来查问时咱们都须要反复这个操作,会导致大量的 DB 资源节约。
而补贴的发放时入账则是一个低频的,单个补贴的操作,咱们只须要在用户核销时入账即可,这样能够大量加重数据库的压力,节俭数据库资源。
7、红包支付计划的选型实际
在视频红包支付的技术计划中,咱们也有一些计划的抉择和思考,这里和大家分享下。
7.1 乐观锁计划
如上图所示:也是最常见的思路(咱们称为计划一),在用户支付时对数据库的红包进行加锁,而后扣减金额,而后开释锁实现整个红包支付。
这个计划的长处是清晰明了,然而这种计划的问题会导致多个用户同时来支付红包时,会造成数据库行锁的抵触,须要排队期待。
当排队申请过多时会造成数据库链接的节约,影响整体零碎的性能。同时在上游长时间未收到反馈导致超时,用户侧可能会不停重试,导致整体数据库链接被耗尽,从而导致系统解体。
7.2 红包预拆分计划
计划一的问题是多个用同时支付会造成锁抵触,不过解锁锁抵触能够通过拆分的形式,来将锁化成更细的粒度,从而进步单个红包的支付并发量。
具体计划如下(咱们称为计划一):
在计划二中,对发红包的流程进行了一个改变,即在发红包时会对红包进行一个预拆分的解决,将红包拆成多个红包,这样就实现了锁粒度的细化,在用户支付红包时从之前的争抢单个红包锁变为当初多个红包锁调配。
从而在支付红包时问题就变为如何给用户调配红包。
一种罕用的思路是当用户申请支付红包时,通过 redis 的自增办法来生成序列号,该序列号即对应该支付那一个红包。然而这种形式强依赖 redis, 在 redis 网络抖动或者 redis 服务异样时,须要降级到去查问 DB 还未支付的红包来获取序列号,整体实现比较复杂。
7.3 最终计划
在视频红包的场景中,整个业务流程是用户拍摄视频发红包,而后在视频举荐 feed 流中刷到视频时,才会触发支付。
绝对于微信和飞书这种典型 IM 即时通讯的群聊场景,视频红包中同一个红包的支付并发数并不会很高,因为用户刷视频的操作以及 feed 流自身就实现了流量的打散。所以对于视频红包来说,支付的并发数并不会很高。
从业务的角度来看:在需要实现上,咱们在用户支付实现后须要能获取到未支付红包的个数信息下发给用户展现。计划一获取红包库存很不便,而计划二获取库存比拟麻烦。
另外从零碎开发复杂度和容灾状况看:计划一相对来说是一个更适合的抉择。然而计划一中的危险咱们须要解决下。咱们须要有其余的形式来爱护 DB 资源,尽量减少锁的抵触。
具体计划如下:
1)红包 redis 限流:
为尽可能少的缩小 DB 锁抵触,首先会依照红包单号进行限流,每次容许残余红包个数 *1.5 的申请量通过。
被限流返回非凡错误码,前端最多轮训 10 次,在申请量过多的状况下通过这种形式来缓缓解决。
2)内存排队:
除了 redis 限流外,为了缩小 DB 锁,咱们在支付流程中加个一个红包内存锁。
对于单个红包,只有获取到内存锁的申请能力持续去申请 DB,从而将 DB 锁的抵触迁徙到内存中提前解决,而内存资源绝对于 DB 资源来说是十分便宜的,在申请量过大时,咱们能够程度扩容。
为了实现内存锁,咱们进行了几个改变。
首先:须要保障同一个红包申请能打到同一个 tce 实例上,这里咱们对网关层路由进行了调整,在网关层调用上游服务时,会依照红包单号进行路由策略,保障同一单号的申请打到同一个实例上。
另外:咱们在红包零碎的 core 服务中基于 channel 实现了一套内存锁,在支付实现后会开释该红包对应的内存锁。
最初:为了避免锁的内存占用过大或者未及时开释,咱们起了一个定时工作去定期地解决。
3)转账异步化:
从接口耗时来看:转账是一个耗时较长的操作,自身波及和第三方领取机构交互,会有跨机房申请,响应延时较长。将转账异步化能够升高支付红包接口的时延,进步服务性能和用户体验。
从用户感知来看:用户更关注的是支付红包的点击开后是否支付胜利,至于余额是否同步到账用户其实感知没那么强烈。
另外:转账自身也是有一个转账中到转账胜利的过程,将转账异步化对于用户的感知根本没有影响。
8、稳定性容灾实际
8.1 概述
整个红包零碎的容灾,咱们次要从以下 3 个形式来进行的:
1)接口限流;
2)业务降级;
3)多重机制保障状态机的推动。
如下图所示:
上面对这几个形式别离介绍下。
8.2 接口限流
接口限流是一种常见的容灾形式,用于爱护零碎只解决接受范畴内的申请,避免内部申请过大将零碎打崩。
在进行接口限流前,咱们首先须要和上下游以及产品沟通失去一个预估的红包发放和支付量,而后依据发放和支付量进行分模块地全链路的大盘流量梳理。
上面是过后咱们梳理的一个 b2c 全链路的申请量:
有个各个模块的申请量后,汇总之后就能够失去各个接口,红包零碎各个服务以及上游依赖的各个服务的流量申请,这个时候再做限流就比拟不便了。
8.3 业务降级
8.3.1)外围依赖降级:
在春节流动期间,红包零碎整个链路依赖的服务有很多,这些上游的链路依赖能够分为外围依赖和非核心依赖。
当上游外围服务异样时,可能某一个链路就不可用,此时能够在 API 层间接降级返回一个比拟敌对的文案提醒,等上游服务复原后再放开。
比方在 C2C 的红包发送流程中,用户须要实现领取才能够发红包,如果财经的领取流程异样或者领取胜利状态长时间未实现,会造成用户领取后红包发送不胜利,也会导致前端来不停的轮训查问红包状态,导致申请量陡增,造成服务压力,甚至影响 B2C 的红包发放和查问。
此时能够通过接口降级的形式,将 C2C 的红包发放降级返回,缩小服务压力,同时升高对其余业务逻辑的影响。
8.3.2)非核心依赖降级:
除外围依赖外,红包零碎还有一些非核心的上游依赖,对于这些依赖,如果服务出现异常,咱们能够升高用户局部体验的形式来保障服务的可用。
比方在后面咱们提到的,用户在发 B2C 红包前须要先获取所有可用的红包补贴,咱们会去处分发放端查问到所有的 Token 列表,而后查问咱们本人的 DB,而后进行 merge 返回。
如果获取 Token 列表的接口异样时,咱们能够降级只返回咱们本人 DB 中的补贴数据,这样能够保障用户在这种状况下还能够进行红包的发放,只影响局部补贴的展现,而不是影响整个红包发送链路。
8.4 多重机制保障状态机的推动
在红包零碎中,如果某个订单长时间未到终态,比方用户支付红包后长时间未到账,或者用户 C2C 红包未支付长时间未给用户退款都有可能造成用户的客诉。
因而须要及时精确地保证系统中各个订单的状态能推到终态。
这里咱们有几种形式去保障。
首先是回调,在依赖方零碎订单解决完后会及时地告诉给红包零碎,这种形式也是最及时的一种形式。
然而只依赖回调可能会呈现依赖方异样或者网络抖动导致回调失落,此时咱们在红包的各个阶段都会给红包零碎发一个 mq,距离肯定的工夫去生产 mq 被动查问依赖方的订单状态进行更新。
最初,咱们对每个状态机都会有一个定时工作用于兜底,在定时工作屡次执行仍未到终态的会 lark 告诉,及时人工染指发现问题。
9、资金平安保障实际
9.1 交易幂等
在编程中,幂等指任意屡次执行一个申请所产生的影响与一次执行的影响雷同。在资金平安中,通过订单号来进行相应的幂等逻辑解决能够避免资损的产生。
具体来说:在红包零碎中,在红包的发放、支付和退款中,咱们都通过订单号惟一键来保障接口幂等。
另外:红包零碎的补贴发放接口是幂等的,内部同一个单号屡次申请发放补贴,咱们须要保障只会发一张券。
实现幂等的计划很多:包含有通过数据库或者 redis 来实现幂等的。最牢靠的就是通过数据库的惟一键抵触来实现,然而这种形式在数据库存在分片实例时会引入一些额定的问题。
这里:咱们就补贴的发放来简略介绍下,在业务零碎的设计中,咱们是依照 uid 分片的形式来建设业务的数据库表,这就导致补贴的分片键是 uid,尽管咱们也设置了红包的补贴单号作为惟一键。
然而其中存在一个危险就是如果上游的零碎调用补贴发放时,同一个内部单号更换了 uid,就可能会导致两个申请别离打到不同的数据库实例上,导致惟一索引生效,造成资损。
为了解决这个问题,咱们又额定的引入一个以补贴发放内部单号作为分片键的数据库来解决这个危险。
9.2 B2C 红包核查
除了在开发过程的零碎设计上进行相应的资金平安思考,咱们还须要通过对账的形式来校验咱们的零碎是否有资金平安问题。
在 B2C 链路中,整个链路次要是从补贴发放到红包支付,咱们对这几个链路的上下游的数据都进行相应的小时计 hive 对账。
9.3 C2C 红包核查
在 C2C 链路中,整个次要从用户发动领取,到用户支付转账以及最初红包过期退款。
在领取、转账、退款这三个流程都须要进行相应的核查。
同时:还须要保障用户的红包发放金额大于等于红包转账金额 + 红包退款金额,这里大于等于是因为红包从发放胜利到退款胜利整个周期会在 24h 以上。
另外:可能存在转账在途的这种订导致会有多笔退款单,如果要求严格等于的话具体对账机会没法管制。
10、红包零碎的压测实际
10.1 概述
后面提到过,红包零碎的链路蕴含有多个接口,发领查等,须要模仿用户的实在行为来进行压测能力失去零碎的真实性能。这里咱们应用了压测平台的脚本压测形式来进行压测。
首先:须要对整个压测链路整个革新,和上下游沟通是否能够压测,不能压测的须要进行相应的 mock 解决。
另外:对于存储服务,数据库,redis 和 mq 都要确保压测标的正确传递,否则可能会影响到线上。
革新完压测链路后,须要结构相应的压测脚本,对于 B2C 和 C2C 分为两个脚本。
10.2 B2C 红包链路压测
下面是 B2C 压测的整个链路,首先是补贴的发放,而后通过查问补贴,通过补贴来发放红包,为了模仿多人来支付的状况,咱们起了多个 goroutinue 来并发的支付红包。
10.3 C2C 红包链路压测
C2C 红包因为波及到领取相干的操作,整个链路又是另外一套流程,因而对于 C2C 也须要有一个独自的脚本。
在压测流程中,因为波及到内部零碎的依赖,如果期待全链路 OK 时再一起压测可能会导致一些未知的问题呈现。
因而咱们须要本人压测没问题后再开始全链路一起压测,在图中和领取相干的蓝色模块咱们都增加了相应的 mock 开关,来管制压测的后果。
在 mock 开关关上时,会间接结构一个后果返回,在 mock 开关敞开时,会失常地去申请财经获取后果。
11、后续布局(服务 Set 化)
在后面提到的零碎容灾中,如果红包外围服务改掉,或者数据库 DB 主机房挂掉,将影响所有的用户。此时只能降级返回,整个零碎无奈疾速切换和复原。
后续思考将服务改为 set 化的架构。
行将服务 Server 和对应的存储划分为一个独自的 Set,每个 Set 只解决对应划分单元内的流量,同时多个单元之间实现流量拆分和故障隔离,以及 Set 之间数据备份。
这样后续在某个单元异样时,能够及时将对应单元的流量切到备份单元中。
12、更多材料
[1] 一套亿级用户的 IM 架构技术干货 (上篇):整体架构、服务拆分等
[2] 一套亿级用户的 IM 架构技术干货(下篇):可靠性、有序性、弱网优化等
[3] 从老手到专家:如何设计一套亿级音讯量的分布式 IM 零碎
[4] 阿里技术分享:电商 IM 音讯平台,在群聊、直播场景下的技术实际
[5] 一套高可用、易伸缩、高并发的 IM 群聊、单聊架构方案设计实际
[6] 一套海量在线用户的挪动端 IM 架构设计实际分享(含具体图文)
[7] 一套原创分布式即时通讯(IM) 零碎实践架构计划
[8] 新手入门一篇就够:从零开发挪动端 IM
(本文已同步公布于:http://www.52im.net/thread-39…)