37手游安卓打包系统演变以及在容器化和函数计算FC方面的实践

前言


对于安卓端的游戏发行而言,在对应渠道上的投放归因效果统计,可称作为它的命脉也为过。通过不同渠道引流情况的判断,可用来评估渠道效果或进行渠道营销的优化。与一般的app业务投放的渠道不同,游戏投放的渠道一般来说会更加繁多复杂。

而对于这种归因统计上报,一般采用的方式都是通过在客户端包体中,在某个位置或者某个文件中,写入对应的渠道的信息,包体在游戏激活启动时,客户端便可通过该位置进行读取对应的渠道信息,并进行埋点上报。这一套流程下来,投放便可通过数据平台/后台知道,在哪个渠道下的登录/激活/支付的提现效果,包括设备转化率等数据,从而能分析出投放的效果。

而对于众多渠道而言,如果单靠客户端手动反复进行打包这一操作并进行上传到对应渠道,这一效率无疑是十分低下的,因此,这种出包便需要一个系统去承载,从而优化打包的效率。

这里,就引出了我们今天的主题——关于37手游打包系统分包策略在函数FC和容器化实践,37手游具体的分包策略是怎么样的?我们是如何通过一系列的优化,使出包效率达到秒级的?

一,手游打包系统的前世今身

“前世”——打包系统存在的意义

作为一家大体量的手游发行公司,我们的渠道,游戏类别,广告位置的信息是繁多的;我们往往一款游戏,对应需要出的游戏包体以及投放的链接就有十几万条,而目前我们发行的游戏累计运营近2000余款,在这么一个那么大数量的游戏渠道组合下,依靠客户端人员手动去打包,无疑是一件不现实的事情。

在这么一个背景下,我们的第一代打包系统便随之诞生了,此时的打包系统,还是较大程度上满足了业务出包的需求,加快了投放的周期效率。此外,打包系统还除了出包投放以外,还承接了我们安卓SDK端游戏出包本身的一些文件合并和资源载入功能MTP安全加固功能,探针检测功能等等

初期的打包上传流程比较简单,大体流程可以概括为以下

1、研发母包上传和jar包构建: 运营/客户端同学负责将游戏研发的原始母包通过后台进行上传,上传后的母包存放在服务器的包体目录下,并根据客户端代码版本选择构建出对应的jar包

2、SDK资源整合——第一次打包: 运营/客户端同学选择所需要的jar包进行打包操作,产出带有手游平台SDK的包体,我们内部称之为广告母包。

3、广告母包二次确认: 支撑同学对产出的广告母包功能,版本号等信息进行二次确认。

4、渠道分包上传到CDN——第二次打包: 支撑确认完之后,打包系统便根据所配置的渠道信息,打入广告母包中,由于每款游戏承接着手游平台的发行运营功能理应都是一致的,因此同一款游戏在这里的打包,可反复复用同一个广告母包去进行渠道分包

信息打入完成后,打包系统便将包体上传到CDN中。此时投放同学便可拿到对应包体的下载链接到渠道中进行广告投放。

渠道信息应如何存放在包体中?——从分包策略上所做的效率优化

第一代打包系统“出生”后,在很长一段时间内,很好满足了我们自身的出包需求,对于分包策略而言,我们一开始是选择放在包体asset目录的某个xml文件下,让客户端读取,具体格式大概如下

这种方式虽然能满足需求,但存在一个弊端——每次改写写入渠道信息,打包脚本都需要进行一次重签名,而apk重签名所需要的时间相对而言是比较长的。整个打包上传时间,少则30秒,多则可达几分钟。

在游戏和渠道数量小的情况下,这种“慢条斯理”的打包方式,业务还能忍受,但随着时间的演进和游戏的逐渐增多,当一款游戏的更包打包的时间需要以“周”来计算时,我们的支撑,广告同学终于也“忍无可忍”了。这时候,在资源有限的情况下,当务之急就是取优化整个打包分包的效率。

对于这个的解决,美团刚好有一套完整的方案,因此我们很“幸运”的可以借鉴过来,详情可看tech.meituan.com/2014/06/13/…

这里对他们的策略简单做个描述——在APK内一个名为META-INF目录内添加空文件,可以不用重新签名应用,因此,通过为不同渠道的应用添加不同的空文件,可以唯一标识一个渠道。 最终在我们的目录的呈现,为以下。

通过这样的形式,我们的一个包的出包速度,已经是秒级的级别了。

“今身”——从“快速迭代”的泥潭中脱身所做的架构优化

早期出于业务的快速迭代搭建和成本节约,打包系统本身的系统架构是十分简单的,而且部署的方式也比较野蛮,原始架构图可总结为如下

这里做一个大概描述

  • 后台做打包参数的配置,并入打包队列

  • 采用redis做打包队列削峰,打包脚本消费队列去消费打包

  • 采用mysql存放打包数据表。后台读表去做各种修改。

  • 利用机器挂盘存放打出来的母包,打包服务负责将母包上传到网宿云CDN。

它的缺点也十分明显:

  • 容灾差:全后台,全服务大部分全部署在IDC集群中,且采用服务器发布,若大厦断电,打包系统直接不可用。

  • 部署混乱:mysql,redis,后台服务等组件都在同一台机器上,数据库组件导致的CPU,内存问题直接影响后台服务使用;磁盘只采用某台机器的挂盘,实际产生了机器之间的互相依赖。

  • 可观测性差:

  • log:基本依赖服务器上的日志,且日志目录靠开发“口口相传”,日志格式不统一,日志系统无法收集。

  • trace:无接入

  • metric:只有机器CPU,内存的基础监控,对于实际业务的监控较差

  • 运维成本和维护成本高: 大部分服务和脚本部署在IDC集群中,运维同学需要手动搭监控,且在扩容的时候,无法做到云上机器分钟级的快速扩容。

  • 打包效率无法提升: 由于打包脚本都采用服务器部署,在一些大批量打包的情况下,无法分钟级扩容;若常态下准备较多的资源,又会造成实际成本的浪费

由于上面的主体框架维持了两三年的时间,且这期间对于打包系统后台本身也有一些零零散散的迭代需求,在主体架构没变的情况下,进行迭代无疑对系统稳定性是“雪上加霜”。

最终导致了其缺点不断被放大,像一座不断被堆积的“屎山”一样,到了被运营、支撑同学甚至维护其系统的开发同学都“口讨声伐”的地步,

正如上篇文章——37手游云原生落地实践所提到的一样“最初的基础架构是在自建的IDC机房中运行的。但是,这种基础架构的管理和维护成本很高,而且扩展性有限。“

因此,这种架构,我们最终还是要去演变优化掉的,为此,我们内部做了一系列的架构优化,包括后台上云,打包脚本容器化, 函数计算FC打包的应用尝试,其中效果最显著,为打包脚本的容器化实践,和函数计算FC打包的应用尝试

二,容器化实践变革

为什么要去做容器化?

由于我们打包脚本是服务器部署,它带来的缺点是十分明显的。

  • 配置无法自主调配: 无法自由的按所设定的配置去调控资源。

  • 扩容慢:无法做到秒级的扩容,需要新购买服务器,部署脚本和配置对应的打包环境。从而导致打包效率无法提升

  • 资源利用率不足:打包时所需要的资源配置高,但实际常态的使用的利用率较差,往往可能是大批量打包任务过来的时候,CPU利用率可达到90%,磁盘IO也是拉满的,但在大部分的常态时间下,CPU,内存利用率接近于0;若采用服务混布,真正打包实际所用到的资源又会影响对应的业务。

如上图所看,常态资源利用率接近于0,资源实际空闲浪费

除此之外,APK的热更在一定程度上能解决一些客户端的小bug和小问题,但是若是游戏研发母包出现重大bug的情况下,此时一般只能通过强更去解决。

在这种比较紧急的更包场景下,出包的速度约等于修复线上问题的速度,这时候若想要提高出包速度,就需要说利用扩容资源解决问题,采用服务器部署,在这种需要临时扩容的场景下,就显得十分鸡肋。

在以上前提下,打包脚本的容器化,便是一个迫在眉睫的事了。

如何去做容器化?————37手游打包脚本在容器化中遇到的问题

由于历史包袱重,打包脚本在容器化中,还是存在一些问题的,问题主要有如下:

  • 打包服务和脚本耦合成一个进程,导致单服务器只能部署一个进程(否则服务端口会被占用)

  • 打包CDN由于成本原因选择了网宿云,但我们自身的容器资源集合在腾讯云以及阿里云。

  • 若采用阿里云或腾讯云实现容器化,在容器上传包体到CDN的场景中需要拉专线或者说走外网,带来的高额的流量传输费用是不可估量的。整体手游架构采用的容器服务也是腾讯云的TKE与阿里云的ACK,就算做到容器化,也是不完全的Serverless,还是会有资源的损耗

  • 若转换CDN到阿里云/腾讯云,转移的包体量大,且转移期间依然会存在一部分的流量传输费用。还需要观察实际业务影响,采用这种方案无疑会“作茧自缚”

针对上面所说的两个问题,我们采取了分步走的策略。

第一个问题还是比较好改进的,我们通过打包脚本和服务解藕,服务只负责对打包信息分析做统一收口,脚本只负责消费队列信息,并根据队列中的打包信息进行调用打包

同时通过我们内部的任务调度系统,实现了对脚本调度的无缝对接——即实现对服务单部署,但打包脚本可无限在单服务器上水平扩展的程度,

1,就算有新的服务器需要部署,也不需要发布,只需由调度系统进行调度,这样子在扩容的环境部署方面的速度就得以有效提高。

2,需要在同一台机器上新加进程脚本消费,也无需重新发布,只需要在任务调度系统上,调整进程数量即可,在某台服务器出问题的情况下,也可快速删除迁移。

解决完第一步问题之后,剩下的就只有容器化的问题。正当我们一筹莫展的时候,我们了解到网宿云自身,还有一个名为边缘云容器的产品,对于我们来说,是十分合适的。

  • 容器自身部署在网宿内部环境中,到网宿云CDN的上传走的是内网通讯,费用不收费

  • 由于是Serverless全托管类型的产品,真正做到了用多少,则消费多少,在常态不需要打包的时候,CPU,内存最低仅消费1核2G,而在大批量打包任务过来时,则可通过我们内部的发布系统根据基础监控自动快速扩容(顶峰可使用到128G),提高打包效率,全程业务无感知。

容器化后的系统稳定性

对于容器化之后的系统稳定性,可以从这两方面去阐述

  • 可观测性:

  • log:由网宿云三方厂商搜集提供,无需自行上报,只需提供日志格式规格。

  • metric:基础监控通过metric-server实现,业务监控上报到手游自行搭建的prometheus,并以grafana面板做呈现。

  • trace:目前尚未接入,从业务形态上来看效用不高

  • 打包稳定性:打包上传成功率从监控上看达到基本维持在100%。并附有重试机制

至此,打包上传CDN这一流程的监控和稳定性已经比起第一代打包系统来说,有了很大的提升,对于效率而言,我们也用实际业务运用过对应的扩容策略,以下为对应数据

条数:2000条
包体大小:128MB

旧有服务器打包:
打包开始时间:2023-02-07 21:00:56
总体打包结束时间:2023-02-07 21:32:19
总耗时:32分钟

容器化后同样规格打包
打包开始时间:2023-02-08 11:46:09
总体打包结束时间:2023-02-08 12:14:00
总耗时:28分钟

容器化后扩容规格拉满打包
打包开始时间:2023-02-08 12:22:17
总体打包结束时间:2023-02-08 12:32:46
总耗时:10分钟

当然, 这一套打包模式也不是说没有缺点的,

首先,网宿云的serverless集群资源也是有限的,我们用的资源量其实也没达到说无限扩容的地步

其次,我们发现网宿云内部带宽也限制了实际进程上传的包体的速率,导致实际打出来的包体的效率,一部分卡在了上传CDN这一流程上。

最后,我们的打包脚本实际还有性能提升的空间,在内存空间的使用上还可以采用另外的方式去实现内存的拷贝。

软件工程没有银弹,这一法则我们还是需要牢记的。

容器化后的整体模块

从架构上来看,我们容器化后的打包架构并没有多复杂,但却很大程度上解决了老一代打包系统可观测难等一系列问题。我个人认为,最关键的点还是在于上云以及监控添加这一方面,在基础组件搭建充足的情况下,IDC迁移上云并不会是一件比较困难,不能甚至说不敢解决的问题。

从上图看到,我们还有叫做FC打包的模块,这就引下来需要介绍的话题——对于函数计算FC打包的应用尝试。

三,函数计算FC打包应用尝试

近几年,随着微服务的架构的演变,出现了许许多多新的理念,Fass以及Serverless便是其中的一个炒的比较火的概念。

对于Faas和Serverless的具体关系,目前并没有一个权威的定义,但普遍的看法是,Faas是Serverless的一种比较好的实践方式,从外延来看,Serverless 比 FaaS 的外延要广,FaaS 主要解决的是用户自定义的代码逻辑如何做到 Serverless,可以叫做 Serverless Compute,同时它也是事件驱动架构的一种,即Serverless是一个比较宽泛的定义,Faas能实现Serverless。
复制代码

在这一类的商业化产品中,做的比较好的,有

  • Amazon 的 AWS Lambda

  • 微软的azure等等

由于篇幅原因,在这里,我们先不做Fass和Serverless的过多介绍,只做引子,来引入阿里云的函数计算FC服务

阿里云函数计算FC介绍:

阿里云官方对FC(函数计算)的介绍是这样的:

阿里云函数计算是事件驱动的全托管计算服务。通过函数计算,您无需管理服务器等基础设施,只需编写代码并上传。函数计算会为您准备好计算资源,以弹性、可靠的方式运行您的代码,并提供日志查询、性能监控、报警等功能。

从概念上看,阿里云对于函数计算FC这一定义是非常通用的,它将重点更偏向于事件驱动下的Serverless服务,而非简单的以Fass作为它的产品定义标准。在这一个大前提下,我们实际在渠道分包打包这一场景中,是能很巧妙的运用到函数计算FC的优点的。

我们只需要将我们的打包脚本的二进制文件上传到阿里云控制台中,通过事件触发(可以是CDN回源请求,也可以是队列的消费),便可以将打包所用的逻辑全权托管到阿里云中。

函数计算FC用于打包的原理架构以及优点:

正如上文我们所提到的那样,渠道分包无非是在包体的META—INF下添加渠道信息文件,因此,我们在和阿里云官方人员沟通和推荐下,演变出了以下的架构

  • 打包服务上传: 这一部分还是承接了之前的上传包体功能,只不过从网宿云的对象存储转变为了阿里云的OSS(也是阿里云的对象存储产品)

  • 阿里云FC: 由阿里云自身全权掌控,可在“资金充足”的情况下,自动扩容,并接近于无限调配打包资源。

  • FC打包效果验证预热脚本: 出于第三方依赖不可完全信任的原则,由我们自行编写的一个预热脚本,负责预热包体,节省用户点击时的打包延时,并对包体的下载,渠道信息校验,并将其成功率上报我们自身的业务监控

整个运用阿里云的函数FC打包的流程,可总结为如下

  • 支撑负责将广告母包确认上传: 包体直接传输到阿里云OSS中,此时打包服务根据包体信息以及渠道信息拼接出一条下载链接(以http请求的query参数形式),并将其投入队列

示例:

https://{CDN域名}/origin={OSS包体地址}&channel={需要打入进行的渠道信息的值}&sign={签名}

  • FC打包效果验证预热脚本负责请求下载链接: 此时会触发CDN的回源,到打包FC函数域名中,打包FC接受到请求并读取到请求中的具体参数后,将渠道信息打入到包体之中,在下载链接这里为了保障我们的函数不会被疯狂调用,特意加了签名机制去处理,以保证我们的包体生成数量是可控的。

  • 确认包体信息监控无误后,广告拿到下载链接进行投放

  • 用户点击: 用户点击下载链接后,实际上是已经请求去下载到了CDN上具体的包体,我们还特意为FC包体的CDN单独了一套域名,因此能很好的监控到,采用这种方式下载的404情况

实际个人认为采用FC打包这套架构的优点主要体现为以下

  • 架构精简: 无需运维,开发观察打包实际运用到的资源情况,日志,监控全程由阿里云官方自身采集。

  • 实时打包: 无需预先准备CDN上的包体,有用户点击过来便可触发打包,在一些实时打包,如通过包体进行分享裂变的业务场景下,能够很好的运用上。

  • CDN资源成本节省:在删除一些不确定是否还有存量用户的游戏项目包体时,无需担心用户点击下载不成功,删除后若有用户点击,可立马回源,节省CDN存储资源;同时在投放一些渠道时,可能会出现某个小渠道压根儿没用户点击下载的情况,采用函数计算FC后,这一部分的CDN资源成本损耗可化为0。

  • 按需打包,按需付费: 函数计算FC单实例的配置可调整,且用完即毁,在广告大批量引流造成的大批量打包时可接近无限扩容,而打包后常态的资源占用率可维持在一个较低的水平

  • 出包效率提高: 支撑投放无需等包体一个个更新到CDN上,只需要负责通过后台生成渠道信息对应的下载链接即可。

函数计算FC在37手游中的运用效果和监控:

目前来看,函数计算FC在37手游还处于一个逐步放量灰度的阶段,对于增量的新游戏的包体,我们内部会推崇采用这种方式进行打包,对于存量的旧有包体,出于CDN厂商回源流量以及桶迁移的成本问题,还是采用了旧有的容器化打包模式。

由于函数计算FC打包这一类别的打包实践我们还在灰度中,对于旧的出包的包体,总体打出来的包体还是不算多的,目前累计有2000多条链接是采用函数计算FC进行打包,但之后我们也会逐步进行放量。

从监控上来看,整体效果还是十分平稳的。

从总体来说,函数计算FC的在37手游中的投产效果还是可以的,但在这一方面的应用我们还在摸索,也欢迎大家可以提出自己的意见。

经过一系列的优化尝试,我们的目前整体的打包系统架构,已经逐渐演变成如下,比起之前而言,可观测性和效率都有很好的提升。

四,后续优化探讨

对于37手游安卓打包系统的效率演进,在这里还是可以和大家做一番探讨的,

分布式云编译

分布式云编译对于打包来说,应该算是一件老生常谈的事情,比较典型的即为哔哩哔哩的云编译实践:www.bilibili.com/read/cv2044…

但目前而言,对于37手游来说目前整个打包的卡点还是在于发行的渠道分包速度

这块的优化,其实可以考虑斟酌

可扩展的APK Signature Scheme v2 Block

对于分包策略来说,出于业务稳定性的考虑,,我们其实也是稍微有点落后的,美团也是率先早一步提出了新一代的打包策略:tech.meituan.com/2017/01/13/…

在这一步骤上优化,其实确实是可以更进一步的优化打包的效率

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值