日活2亿+,百度交易中台系统架构是怎么撑下去的?
百度APP内含有现金、活动、虚拟等多类资产信息,分布于百度APP内各个业务线中,用户回访信息难度较高,且用户对百度资产认知度不高。
我的钱包建立后,汇聚百度APP内所有用户资产信息,解决了用户回访难的问题,建立用户百度APP资产认知。
本文主要介绍了钱包从0到1的搭建过程、遇到的各种问题以及相应的解决方案,旨在抛砖引玉,希望能给读者带来思考和帮助。
一、背景
在钱包入口点击后,可进入钱包首页。首页中主要外露用户的现金、提现活动、返现、卡券、虚拟币、快捷入口等信息,同时支持运营配置营销、活动。对于现金余额信息,我们需要用户授权,用户授权后查询用户在度小满、百度闪付的余额,进入明细页面后,可分别进行授权、查看明细。针对活动提现金额,钱包推动百度APP内所有涉及提现金额的业务接入钱包,统一进行管理,支持用户查看金额明细,其中金额明细中包含两个部分,一部分是明确返回用户余额信息的业务,支持用户点击跳转至具体业务方查看明细,另一部分是汇总百度提现中心全部业务,支持用户点击跳转,建立用户现金余额统一管理、访问的认知。对于虚拟币数据,可根据用户各个虚拟币余额数量进行动态展示,便于用户快速知晓自己的虚拟币信息,点击具体虚拟币时,支持跳转至业务方明细页面进行查看明细、充值等操作,也支持跳转至钱包统一虚拟币明细列表,查看用户明细、月度汇总等信息。对于业务方接入,钱包提供多种接入方式:API数据同步、实时查询、配置跳转,业务方可根据自己实际情况进行选择。
图2-1 钱包展示
图3-1 钱包业务架构图
钱包的整体系统业务架构如上图3-1所示,钱包服务主要面向C端用户、业务接入方、运营人员。针对C端用户,钱包提供百度APP个人中心一级入口和钱包首页两个核心入口,前端使用talos框架实现。用户通过个人中心入口访问到达逻辑层后,首先判断用户是否命中用户缓存,该缓存用来标识用户是否有资产,如果命中缓存且有资产,则读取数据缓存返回数据。用户通过钱包首页进入钱包,根据访问的模块,路由适配器根据配置规则,选择不同数据规则进行数据加载、获取,数据获取后按规则进行聚合返回。为了防止服务异常时影响页面渲染,提供异常兜底方案,业务方接口异常时使用缓存数据兜底,内部异常时统一返回兜底文案。
针对不同的业务方,我们提供多种接入方式,对一个人中心一级入口外露业务,我们提供推送、拉取机制,即业务方在用户数据变更时同步钱包,钱包准时拉取用户最新数据;对于钱包首页、列表页业务,我们提供实时查询方式,对于利用钱包给业务内导流的业务,我们支持H5、scheme、talos、端内等多种跳转能力。配置主要包括基础配置、扩展配置、流控配置, 实现不同的适配器供上游调用。
运营人员可使用B端运营工具,配置、修改接入的业务方信息,也可以管理钱包首页展示、运营活动、营销等,同时亦可配置相关的流控、监控等信息。底层服务,我们使用了百度内通用的数据库、Redis、消息队列、CDN等基础服务,该基础服务由统一的部门运维,稳定性比较可靠。
针对如上一系列问题,我们确定了接入钱包的原则:①在个人中心破壳展示位外露的业务方,必须将资产数据同步至钱包,钱包服务内部确保接口可用性和降级方案;②钱包首页展示数据,支持业务方选择是否将数据同步至钱包,如果不同步数据,需要提供满足QPS、平响等要求的API供钱包调用;③钱包二级页面、三级页面数据,需业务方提供查询API或者落地页,实时查询业务方数据进行展示,确保明细数据准确无误。
1)数据同步
对于业务方同步的用户资产数据,我们只存储用户的最新数据,考虑数据量级问题,钱包不存储历史快照。钱包定义业务方数据推送的接口规范,业务方在数据更新时,需及时通知钱包拉取最新数据。为了防止业务方推送数据时流量突增对钱包sever的影响以及提高钱包server的吞吐,引入消息队列进行削锋,即接到业务方资产数据变更请求时,放入消息队列后返回业务方通知成功,钱包server内消费队列消息,根据配置信息向业务方拉取最新数据进行更新。对于用户资产明细数据,钱包定义接口规范,业务方实现后将调用地址提供给钱包,钱包进行配置,用户访问时实时调用。
于是,我们设计了如下的数据同步方案:
图4-1 业务方数据同步
- 用户资产变更时,业务方调用钱包接口通知信息变更;
-
钱 包接到通知后,放入消息队列;
- 消费任务获取消息队列中消息进行消费处理;
- 根据配置,拉取业务方最新信息,更新至钱包存储;
在消费消息时,钱包server进行批量处理,针对一定时间内同一业务方同一用户信息,只拉取一次数据,减少对业务方服务压力。遇到异常时,会进行3次重试拉取。钱包server控制是否拉取、流控,在下游业务方服务异常时,可及时停止拉取、降低拉取流量,减少或者去除下游系统对钱包的影响。
2)实时查询
针对部分业务方不推送数据,我们定义接口规范,业务方进行实现,实现后上报钱包配置调用方式。对于此类业务方,不允许配置到个人中心展示,防止业务方接口性能不达标拖累整体用户体验。实时查询接口主要分为余额、分页明细接口,钱包统一format后进行展示。余额接口查询返回后,异步写入Redis进行缓存,用于下次访问业务接口异常时进行兜底展示,如果后续访问业务接口正常,则使用最新数据更新Redis数据。
用户明细分页数据包含分页明细数据和月度汇总数据(月度总收入与总支出),钱包调用下游拆分成两个接口:分页明细接口和月度汇总接口,提供给前端展示页面封装成一个接口,降低前端交互复杂度。调用下游分页接口实时返回用户明细分页数据,月度汇总接口返回用户当前明细所属月份的汇总数据。钱包服务内部根据业务方明细分页接口数据进行汇总,为了减少请求业务方次数,钱包内部判断分页数据,如果返回数据都是同一月份,则只请求业务方一次月度汇总接口,后续分页不再请求,如果返回数据分散在两个月份,则请求业务方月度接口两次,如果返回数据分散在三个月份(含)以上,则只请求起止两个月份的月度数据,中间月份数据由钱包根据明细数据内部计算。
针对存在资产的用户量分析,进入个人中心的用户存在一个特点:大部分用户没有个人中心外露的资产数据,抽象成一个稀疏矩阵,这就使我们在设计的时候,可以考虑两层缓存:第一层判断用户是否拥有资产信息,第二层缓存存储用户资产数据。故我们设计了如下的两层缓存方案。
图4-2 钱包两层缓存方案时序图
大部分用户流量会被第一层缓存拦截,设计合理的第一层缓存数据结构,成为了系统的一个关键。我们调研了hyperLogLog、布隆过滤器、roaringBitmap等方案,分析和实验后发现,hyperLogLog中pfadd操作本身可以满足性能要求,key的大小在12k也满足,但是由于pfadd本身针对于一个uid,只能操作一次,所以不适合这个场景hyperLogLog;布隆过滤器在试验20亿的数据量下,内存占用来量大约占比3G,内存占用比较大,基于redis的布隆过滤器没有分布式的数据能力,本质上还是对redis的强依赖,存在风险。
roaringBitmap在存储空间上满足要求,我们根据钱包的实际场景,改进了分桶与计算规则,根据用户id进行sharding分桶,桶内使用uid的hash值对应的bitmap位点标记状态。实验数据验证,3000分片,8个计算单元,1000W实验数据,存储空间占用500M+,误判率2.14%,即存在2.14%的用户没有资产信息会打到第二层缓存,整体对第二层缓存增加压力可控。
图4-3 读写分离
对于钱包内展示的业务方,也会存在服务异常、网络抖动导致的无法获取用户最新信息的问题。我们采用的方案,对于正常请求业务方结果数据,将结果信息写入Redis,如果下次访问业务方接口异常时,使用Redis中数据作为兜底,优先确保数据正常展示。如果后续请求下游接口正常,则使用成功的数据更新Redis数据,确保Redis数据的准实时性。
图4-4 钱包数据一致方案时序图
配置化主要分为两部分:接入配置化和展示配置化。通过分析发现,不同业务方的接入、展示需求不同,于是我们抽象共同的信息,生成通用基础信息,对于业务个性化信息,采用扩展模型记录业务特殊配置。汇总业务方基础配置+扩展配置后,放入Redis缓存中。对于钱包展示配置,底层设计展示通用配置,比如展示名称、文案、跳转链接、图片Url、背景图Url,对于特殊展示需求,可配置到扩展信息,比如外露Icon、展示金额,配置信息同样放入Redis缓存中。
项目上线初期,配置信息新增与更改的频次都很高,容易发生修改错误的风险,导致C端用户体验受损。于是我们设计版本号+白名单方案进行放量控制,新版本发布需与白名单用户相关联,配置生效后先使用白名单用户线上进行验证,验证通过后逐步全流量。修改配置时,保存历史更改全量数据,用于配置信息异常时回滚。
由于钱包对业务方配置信息依赖较强,如果Redis异常时会影响钱包的稳定性,所以我们将配置信息同步配置到百度云控平台GCP中,作为Redis异常时的兜底。缓存有效期可以设置永久有效,在变更配置时同步更新Redis,同时需要同步更新GCP配置,及时下发。