作者 | 交易中台团队
导读
随着公司内容生态的蓬勃发展,内容产出方和流量提供方最关注的“收益结算”的工作,也就成为重中之重。本文基于内容分润结算业务为入口,介绍了实现过程中的重难点,比如千万级和百万级数据量下的技术选型和最终实现,满足了业务需求的同时,最终实现了高效,准确的资金结算,文章旨在抛砖引玉,希望能给读者带来思考和帮助。
全文5185字,预计阅读时间13分钟。
什么是内容分润平台呢?简单来说,百家号等平台负责内容的生产和引入,手百等渠道方负责内容的分发,凤巢等广告平台负责在此流量上进行变现。而分润平台,则是根据上述各方提供的数据,通过核心策略模型,赋予作者、媒体、小程序主和用户,合理的、差异化的、有竞争力的分润收益,以吸引更加优质的内容和流量的入驻和合作。通过这种多方相互协作模式,实现互惠共赢的目的。
针对上述的业务特点,结算系统需要包含三大功能,用于支撑内容分润业务的准确性、合规性、及时性。
功能一:结算模型
这是我们最关键的功能,它负责将出色的文章转化为作者的分润收益。该模型的输入数据包括数据中台生成的用户维度的日分润明细和日补贴明细,而输出则是每月的结算账单,这些账单会被发送到统一业务平台用于付款。在这个过程中,我们经历了一系列步骤,包括每日的计算、每月的总结、预提、计提和账单生成等,所有这些步骤都是按照不同的维度逐层计算和聚合的,最终实现了账单的付款。
功能二:C端内容交易平台
这个功能主要面向用户,旨在帮助作者及时查看他们的收益,并进一步激励他们的创作动力。作者只需登录平台,即可查看每日的预估收益、文章的分发情况、浏览量等数据,还可以查看每月实际的付款账单,提供发票等相关数据。
功能三:O端管理端平台
为了确保资金结算更加合规和准确,整个结算体系引入了运营管理和反作弊等不同角色。这些角色在管理端负责资金管控、发票审核、黑名单管理等各种操作,以确保整个过程的合规性。
PALO:百度数据仓库,是基于开源ApacheDoris构建的企业级MPP云数据仓库,可有效地支持在线实时数据分析。
BNS(Baidu Naming Service):是指百度名字服务。BNS提供服务名称或服务组名称到服务所有运行实例的映射,你可以根据一个名字(服务名或服务组) 获取服务的信息,包括实例的主机名和IP、实例的运行状态、端口、负载、实例自定义配置标签以及其他实例自定义信息。用于满足服务交互中常见的资源定位、IP白名单维护、查询服务下的机器列表、负载均衡以及其他任何依赖于这些信息的开发、测试和运维需求。目前BNS已经在全百度各业务线中广泛使用,UB、RAL等框架的支持和各语言SDK也已经发布。
图1是整个内容分润的业务架构。内容分润结算面向数据中台,业务方,用户(作者)和运营管理提供服务。
△图1.内容分润结算平台系统架构
对于数据中台,我们是直接下游,同时在整个内容分润流程的流程中,我们扮演的是最末端的角色。百家号、问一问、百度文库等业务会将作者的内容分发数据、广告贡献等给到数据中台,数据中台按照各种分润计算模型归一化数据结构,产出三份较为详细的明细文件,包括日分润明细,日内容分发明细,日补贴明细。
日分润明细:作者内容分发或流量贡献所获得的分润详情,明细中包括分润金额,文章分发渠道,父子账号等字段。
日补贴明细:基于运管管理的二次资金分配详情。
日内容分发明细:作者的内容分发贡献报表。
数据中台会将这些数据以离线文件的形式提供给我们,结算系统每日基于配置规则,进行离线计算,最终将数据进行降维汇总。后续每月月初,基于这些汇总数据,做二次汇聚产出用户收益账单。
结算系统根据外部需求,提供多种接入方式。面对业务方,结算系统提供API、网页嵌入模式接入方式。若业务有其自建平台,可将结算系统提供的网页嵌入其平台内部,用于展示用户的收入信息或上传发票等。若无自建平台,也可API形式接入。新用户在业务侧申请入驻作者后,业务调用结算系统API完成用户注册,开通计费单元,维护财务信息等。后续作者在内容分润平台查看其收入,文章分发报表,重新维护财务信息等。若有重要变更或通知,系统通过站内信方式通知作者。
系统整体支持三种账号体系,面向作者提供两类百度常用账号登录方式,面向管理端提供内网账号登录方式,基于此账户体系做了灵活权限控制,不同用户登录管理端,看到的可操作菜单栏各不相同,避免出现越权操作。同时基于此账号体系,能灵活获取上下级,构建了自动化的审批流程。
结算系统的平稳、合规、高效运行离不开各类协同生态的合力支持。反作弊能力贯穿整个内容分润的始终,着力于打击黑产,识别作弊用户。OCR、发票平台为发票识别,发票鉴定提供了通用服务。财务的各类审核,业务的多维度监管则进一步为资金结算的合规安全保驾护航。各类角色、各个系统协同合作,促成了目前内容分润结算系统。
上文以整体的视角介绍了内容分润结算系统的架构设计,下面我们将枚举几种业务场景构建过程中的技术选型,来详细介绍该系统的技术落地。
场景:每日上游会给我们产出明细数据,数据为细粒度,量级为大几千万级别,格式为AFS文件(离线文件),需要基于某些过滤规则和计算规则做二次汇聚,后续支持多维度查询,作者端展示报表。
最初任务是在物理机上通过sql批处理,任务串行执行,简单明了,同时成功同时失败。但随着数据量持续递增,串行执行可能面临着实效性问题。基于原始的DB思路,我们构建了基于DDBS(关系型分布式数据库系统)的解决方案,全部依赖于DB,因汇聚是基于用户维度,所以基于子账号uid计算shardingKey分表,过滤规则也落入库中,后续使用表之间连接过滤,相同分表中的同子用户数据汇聚。使用在线服务,按照分表规则,启动多线程执行任务,实时写入日汇总数据表。具体方案如图2。
△图2.基于DDBS的解决方案
利用SPARK天然的分布式计算能力,采用离线计算方案,汇聚时使用SPARK计算。基于上游提供的离线文件,构建RDD1文件,后续基于一些过滤规则过滤数据和然后基于集合规则,使用reduceBykey聚合,产出新的RDD2文件。这个RDD2文件就是我们后续使用的日表数据。因有各类在线查询需求,需持久化到数据库中,又因产出的日表需支持各角色多维度查询,调研后采用PALO数据仓库,具体方案如图3所示。
△图3.基于SPARK+PALO+DB解决方案
对比两种方案后,我们最终选择方案二实施。方案二的优点比较突出:1.SPARK集群自带分布式计算能力,无需我们按照方案一方式自行实现分布式计算;2.数据存储于PALO,相比于传统的MYSQL,在大批量数据和多维度报表场景,PALO性能优势更加明显。3.方案一有一个最大也是我们最踩坑的性能问题,实时大批量写入DDBS数据库导致较高的主从延迟,影响了其他业务场景。
场景:基于上述场景会产出月表,数据量大约在百万级别,遵循月度出账计算模型,产出最终的预提数据。日度任务和月度任务的最主要区别在于日度任务计算过程密集,月度任务过滤过程密集。
月度产出计提任务实际就是计算用户本月收入以及本月可结算的收入,可结算收入=以前累积未结算金额+本月收入。目前该任务输入的数据量相对较少,且以过滤为核心,因此此类任务未采用SPARK计算。而各类过滤规则与当前用户各种属性息息相关,因此任务围绕用户uid展开,采用以用户uid为底表,先通过各类策略过滤uid,后置再计算的方案。数据量虽然相对日度任务较少,但毕竟在百万级别,如果使用单一线程处理所有用户,速度会极其缓慢,所以必须拆分任务,使用并行计算的方式提升效率,而如何拆分任务,如何保障任务全部执行是月度任务模型需要考虑的核心问题。
我们设计了主从任务模型,用于支持上述任务拆分执行,主结点先置启动,用于数据备份、初始化出账任务,以及调度从节点。从结点则等待主结点启动子任务指令,启动后获取子任务执行。具体模型如下图4,5所示。
△图4.主节点生命周期
图5描述了主节点的生命周期,主节点收到出账指令后,优先做的是账户余额类表的数据备份,这个动作归因于我们月度任务的特殊性,月度任务产出的数据表在其他时间不会更新,即上个月出账结束后,账户余额类的相关表会在下一次出账完毕才更新。
备份表的环节非常重要:
1.是可以在月度任务结束后做数据总额验证工作;
2.是可以用于兜底,一旦月度任务产出数据异常,也可回退到备份数据,重新启动任务。
主节点任务的第二步则是确认出账任务的用户uid范围,我们系统为了既支持C端用户体系,也支持商家账号体系,重新设计了一套内部用户id,不论是用户账号还是商家账号的id均会唯一映射成一个内部uid,后文提到的该任务的uid均为内部uid。内部uid为自增id,因此查询数据库,即可获取到最大uid和最小uid,也就确定了我们本次任务的uid范围。在redis中设置两个key代表uid的最值。至此,出账任务的前置准备工作就完成了。主节点获取执行子任务配置的BNS,基于BNS解析出所有实例,发送子出账任务指令,子实例获取到指令后,启动N个线程执行任务,即假设有M个子实例,那最终就是M*N个线程同时执行任务。从主节点的任务可看出,该任务无其特殊性,即主节点实际和从结点是平等关系,任何实例都可成为主,也可成为从,这就为调度任务进一步提高了灵活性。
△图5.从节点生命周期
图5以上述实例中的一个线程作为示例,详细描述了线程启动后,执行的子任务的过程。首先获取目前的最大uid和最小uid,最大uid为主节点固定值,最小uid则是一个游标。若最小uid已经大于最大uid,则代表所有uid已经处理完毕,线程结束。若不满足上述条件,则继续执行任务,利用redis的incryBy指令,将最小uid向前移动N个数值,这N个uid就是本次子任务的执行范围。拿到uid后,先将uid变为N条任务批量落入Job表,并设置初始化状态。落库失败,引入报警机制。落库成功后,按照出账模型,启动过滤规则。所有被过滤的用户uid均批量写入job表,设置任务结束状态,并且标记过滤原因,便于后续运营查询。过滤规则执行完毕,剩余uid十不存一,此时我们利用sql计算本月用户结算金额。计算完毕,写入jobDB的临时产出表,设置job任务完结态,此时一轮子任务就执行完毕。线程继续重复执行上述过程,直至所有线程均结束,代表出账任务执行完毕。
所有任务执行完毕后,主节点会收到出账任务确认指令。
△图6.出账确认任务
该任务的主要目的就是确认所有uid均执行完毕,无疏漏,具体如图6所示。上文提到,子任务执行时,都是先置落库job表的,确认任务的第一步:扫描job表,看是否有非完结态的任务,若有,则启动子任务,重新执行这批数据。确认任务第二步:获取job表中所有执行的uid数量和需要执行任务的uid数量,确认数量是否一致,若不一致,重新执行出账任务,任务基于uid和业务期间重入,已经被执行的任务会被跳过。多次兜底策略执行完毕后,数据总量校验一致后,会将临时月度产出数据写入正式DB,清理临时数据。之所以设置临时表:1.是为了数据校验工作,若数据校验异常,可快速清理该表,重新启动任务;2.若直接写入正式线上库,大量数据的并发写入会导致数据库的主从延迟,会影响其他线上实时业务场景。后置写入实现了另类的『读写分离』,任务过程中仅读正式表,任务完毕临时表往正式表写入数据。
本文主要介绍了在构建结算系统过程中的几个技术重点和难点,而要维护整套系统的平稳运行,不仅有这些技术重点,也有看似微不足道但却环环相扣的细枝末节,保障每个环节不掉链子是运维工作的重要一环,后续我们将着力于提升运维效率,节省人力成本,向着运维自动化、智能化改造。另外目前的技术方案取决于我们的数据量级,未来业务蓬勃发展,业务架构也会持续迭代,期待我们向着更加完备的架构前进。
——END——
推荐阅读
小程序编译器性能优化之路
百度APP iOS端包体积50M优化实践(六)无用方法清理
基于异常上线场景的实时拦截与问题分发策略
极致优化 SSD 并行读调度
AI文本创作在百度App发文的实践