掘金 阅读 ( ) • 2021-11-30 13:25

本文是字节跳动发布工程团队的高磊讲师在2021 GOPS 全球运维大会中「字节跳动亿级DAU客户端发布最佳实践」的分享全文。

首先做一下自我介绍:我是字节跳动发布工程团队的工程师高磊。从事软件开发工作有10多年的时间,在传统软件公司还有一些创业公司都做过,最近大概6,7年时间基本专注在DevOps方向,也积累了一些自己的理解和经验。 file 今天我分享的主题是【字节跳动亿级DAU客户端发布最佳实践】,通过今天的分享,希望大家可以了解字节在客户端发布方面做的一些实践。本次我将从四个方面来进行分享,分别是:1.移动端发布的特点难点,2.字节移动发布体系的介绍,3.移动发布的实践总结,4.未来的展望。

1. 移动发布特点&难点

我们先从移动发布特点难点开始。 发布这个概念应该属于CD(持续部署)的范畴,也就是Devops的一部分。平时我们接触的比较多的是服务端Devops,今天的分享主题看似会偏客户端一些,但大部分的内容和理念应该是相通的;本来我之前想的是,如果能到场的话,我会做一个现场的调研,看下到场的做客户端的同学比例;不过既然现在没有这个机会,我先简单介绍服务端和客户端的发布流程的区别: file 其实这里的内容并不复杂,但作为我们后续内容的一个背景铺垫还是有必要讲的。 从构建打包到包出来的这个阶段,两者应该区别不大,无非底层依赖的打包工具略有差异,最本质的区别在于出包后的流程;服务端的更新流程,是把打出来的二进制包发到我们自己的服务器上,所以他的整个过程是可控的,一方面你可以随意更新新版本;另一方面呢,万一上线的版本有问题,也能做到一键回滚;但客户端是不行的,我们的包打出来以后,一般我们会把新版的包放到服务器上,如果对于正式包,我们会上传到商店来托管,但用户什么时候过来更新,我们是不知道的;如果这个版本有问题,那你也没有办法很快会退到上一个版本,你还得规规矩矩的再走一遍这个发版的流程,这个止损的成本是比较高的。 这就是两者最大的区别:客户端升级依赖终端,不完全取决于平台,这个特性也决定了两者在版本发布全周期其他方面的不同。 file 具体来说,首先是部署介质,这个上面提到了,一个是可控的服务器环境;另外一个是复杂变化的终端设备,操作系统有Android/iOS, Android有很多不同的厂商,比如小米,华为, vivo等等; 第二,服务端的版本概念比较弱,一般来说,我们不用版本号去定位某次服务端的发布,但客户端不同,我们的交流语言就是某某版本,版本号是客户端发布里面非常重要的一个信息; 第三个差异点是发版周期,服务端的发版一般没有明确约定,有新功能的话可以每天都更新,一天发多次都是常态;这个也符合Devops持续交付的理念; 移动端,一般准备周期比较长,目前主流的节奏可能是1到2周一个大版本,也有一些不常更新的应用甚至可以一个月才发一次版; 第四个差异点是参与人群,服务端一般情况下只要测试阶段通过,到了发布阶段基本上就是RD来主导,但客户端因为版本比较珍惜,一个是一个,所以在发版阶段设计了很长的链路,每个阶段都会配不同的角色去完成某项工作,现在字节不少团队还有QABM这个角色,就是QA里面专门负责发版管理的同学;整体分工可以说越来越精细和复杂了; 第五点是止损效率,这个之前已经提到过了,不能很快的回滚止损这个“硬伤”可以说是造成客户端和服务端发版差异主要的原因; 最后一点是多版本并行,服务端显然常态下只会保留一个最新的版本,在上线或者灰度阶段会短暂的多版本并行一段时间,但是客户端历史上累积的这些版本会永久的保留下来,不再变更; 那么说了这么多,相信大家对两者差异应该有了一个更清晰的认识,大家可能都会想,“客户端发版”真麻烦,这个是对的,而且因为麻烦,一旦发出去以后,就要承担一些可能的风险。 file 下面我给大家看几个典型线上事故的例子,大家可以看下这张PPT,这是我们线上曾经出现过的真实事故,有因为错误下发64位机型安装包到32位机型导致升级失败的,有为安装包配置错误下载链接导致安装失败的,还有使用不恰当物料导致应用商店拒绝上架的...真是什么事故都有。不难看出,移动场景下发布面临的风险很多,有安全的问题,有数据的问题,有测试的问题,稍有遗漏就会给公司造成很大的损失,或者是财务损失,或者是用户流失,这些都是我们在设计发布系统时要解决的问题。 file 那么看了以上这些事故,再结合客户端发版的特点,就会引出几个问题:

  1. 如何打造一个高效的发布流水线?
  2. 如何保证流程的安全性?
  3. 如何达到更好更快的放量效果?
  4. 如何保证发布数据的可靠性?

2. 字节跳动移动发布体系

这些问题在字节是怎么解决的?接下来我将为大家介绍字节的移动发布体系,并尝试回答以上四个问题。 file 首先先介绍下字节的移动发布中台发展史,大致可以分为几个阶段: 第一阶段是2017年以前,因为业务发展比较快,公司级的中台发展相对滞后,更多的是业务从自身发版的需求出发,简单搭建了一些小的jenkins集群,用来串联各种打包和测试任务;那这个状态持续了一段时间后,问题就出现了,大量分散的jenkins集群维护起来很麻烦,业务需要自己去维护这样一个平台,投入太大,很多任务编排都重复建设,没有很好的共享资源,随着业务越来越多,发版问题越来越突出,所以一个中台的出现就变得迫在眉睫了; 于是我们进入了第二阶段,从2017开始,第一代发布平台1.0就出现了,我们内部叫“Rocket”,字面意思是火箭,显然它的寓意是快,这个阶段相之前有了一些进步,最大的改变是通过jenkins提供了平台级的流水线,不同的团队可以自己定制,同时也有了专门的编译团队来解决打包的环境和部署问题,情况比之前要好了很多,jenkins出了问题有人维护,有了流水线,也可以重复执行。 这个过程大概持续到2019,一些隐患又暴露出来了,首先是jenkins打包集群,受限于jenkins本身的单master架构问题,海量的jenkins任务对于构建团队而言同样苦不堪言,多的时候一天发生10多次重启机器也不罕见,另外呢,这一期的流水线也有一些问题,过度追求灵活的配置,那会的原子能力其实不能真正算原子能力,更像是一些脚本的零散组合,缺乏系统的协议约定,带来的问题就是用户的配置成本很高,因为需要关心的细节太多了,平台和平台之间的能力联动不透明,导致各工具之间比较割裂,用户配置流水线成本很高;同时在安全等方面的流程设计不合理,导致一些卡点形同虚设;所以从2019年开始,我们开始推进第二代发布系统的建设,这一期我们做了这样几个事情:

  • 首先在构建层面,我们抛弃了基于jenkins的二次开发模式,完全采用自研的分布式调度集群,实现多主多活,自动恢复,支持任务优先级调度等方案,整个可用性也有了大幅提升;
  • 在流水线设计方面,我们放弃了之前完全脚本化的方式,提炼出了很多通用的系统原子能力,大幅降低了用户的使用成本,交互体验有了大幅改善;
  • 除此之外,我们在安全方面做了一些建设,整体从需求开始就有渗透,目前来看基本覆盖了CI/CD全流程;
  • 在数据这块,我们确定了以制品库作为发布的数据基座,从之前关注版本的宏观视角落到关注产物的微观视角,整个平台的架构更加清晰;
  • 最后,我们围绕灰度放量这个环节进行了多种方式的探索,目标是提高我们的问题反馈率,尽可能把一些潜在的严重问题前置暴露; file 大家可以看看这个图,这是我们当前版本的一个主要架构体系; 首先平台目前较好支持了头条,抖音,西瓜,小说,飞书等业务的发版需求,支持的终端场景也从Android, iOS 应用扩展到,Mac,Windows等更多场景;在此过程中,我们也沉淀出了很多的平台和能力,从需求规范到研发打包,测试,发布,以及上线后的监控和反馈;我们的最终目标就是打造一个一站式的通用移动研发平台; file 那回过头来,我们再看下之前提到的几个问题,我会分4个小节,分别从流水线,安全,测评,制品库这几个方向介绍平台的一些特点,一起来看下字节对这些痛点给出了怎样的解决方案。 file 首先,针对如何构造一个高效流水线这个问题,我们搭建了字节发布流水线。流水线对于Devops来说基本算是一个既定的事实标准了,我们也不例外,在字节的场景下,他主要解决了两个问题: 第一:多场景自定义任务编排; 曾经我们尝试想从一个最佳实践出发满足所有业务的场景,后来发现这条路是行不通的,因为字节不同业务的发展阶段不同,成熟的业务比如头条,抖音,他的团队分工和成熟度和一些新兴业务肯定不同,在权限控制,测试流程,准入准出规范上差别很大,举个例子,一些小业务在上线之初,可能都没有灰度阶段,你如果非得拿一套发布的全家桶模板往给他套上,逼着他必须走一个复杂的灰度流程,对于业务方来说其实是一个拖累,而不是提效; 第二:解构平台的复杂度,便于度量; 整个移动devops的流程很长,如果不依赖流水线,那你需要自己单独管理这十几个甚至几十个原子能力的相互关系;流水线的设计可以让这些原子能力用一个编排的方式管理起来;对于平台来说,我们也更容易去度量整个平台的质量瓶颈,是出在哪一个具体的能力上,比如某个业务的发版速度一直很慢,我们通过分析流水线上每个原子的执行时间,就很容易的定位到是具体哪个阶段有问题,是自动化测试效率低,还是灰度阶段放量慢,都可以用自动化的方式来完成,甚至可以做成报表,给出一些定量的分析; 从我们自己的实践来看呢,我觉得其中的关键点在于:原子能力的粒度;
  • 粒度过大,那内部结构就过于复杂,不利于直接深入到本质,这说明还需要往下拆解;
  • 粒度过小,没有太多度量的价值,那他就可以合并到其他原子能力里面; 这里总结了我们的两个原则,大家可以参考下:
  1. 有独立的功能定位:
  • 这里面独立指的是执行独立,权限独立,数据独立这三个维度;
  • 执行独立指的是原子能力在给定了基本的依赖数据后就可以独立运行,可以实现特定的功能;
  • 权限独立指的是原子能力需要具备独立的权限,不受其他原子能力的直接控制;
  • 数据独立指的是原子能力应该享有独立的数据通路,可以直接和流水线框架做一些数据的沟通;
  1. 可独立度量改进:整个发版流水线的度量最终会落到单个原子能力上,如果你的原子能力本身无法被某个指标度量,那么说明这是个无效的原子能力,他不具备独立存在的条件,需要通过兼并或者拆解来达到度量的要求; file 流水线问题讲完了,我们再来看看如何保证发布安全。前几年大家会把安全问题当做一个不得已而为之的事情,也就是安全问题是彻底暴露了才想去解决的事情,现在呢,业界逐步形成了一些共识,就是安全问题不应该作为一个救火队员,或者作为兜底手段存在,而应该作为一个devops必需的参与者,渗透到devops的全流程;那在字节的场景下,我们从前两年开始也逐步把安全的理念贯彻到整体移动发布的全流程,从需求阶段开始,我们就开始做相关的安全合规评估,在CI和CD阶段,我们也会分别做静态以及动态扫描,在最终release到商店前,我们还会基于我们累计的一些案例库做一个审核,避免触碰一些安全方面的红线。 平台能力刚上线的时候,我们确实能发现很多的安全漏洞,有网络方面的问题,有隐私合规的问题,看上去效果不错,但是发现漏洞仅仅是起点,更重要的是我们要去消费漏洞,去修复问题;从平台的角度看,一旦发生了高危漏洞,应该禁止业务方发版; 但从业务方的角度呢,版本的按时上线才是他们最关心的,最后的结果就是平台临时给开绿灯(可能是hardcode),然后双方拉老大开群讨论,最后定一个限期整改的方案出来。 这种事情多了以后,所谓“安全很重要”就变成了一句口号,所以我们需要一个机制去解决这个问题。这个机制呢,不是技术层面的,更重要的是在公司内部形成自上而下的共识。 一般来说,这里面有三方参与的,安全团队,平台团队和业务团队;我们需要明确每个团队在安全问题当中的定位:
  • 首先安全团队的职责是负责对安全问题定级,提供安全问题的整改方案,并且辅助落地;比如在我们现在的平台上,安全团队就提供了黑盒和白盒的扫描能力,以及相应的规则库;
  • 其次是平台团队,平台团队对系统的流程负责,平台不能不管,也不能管的太死,简单粗暴的规则“一刀切”肯定是不行的,更合理的做法是提供灵活的卡点能力和配置能力,业务可以根据自己的实际情况配置
  • 卡口级别;在具体的问题上,我们采取的还是“增量问题动态修复,存量问题限期整改”这样的原则;
  • 最后是业务团队,需要提高对安全问题的重视和反馈,积极配合整改意见的实施,分阶段按优先级来推动安全问题的整改落实; file 接下来我们想解决的痛点是:如何提高放量的效果? 在一开始我们介绍服务端和客户端差异的时候提到了,放量这个阶段的差异是移动端和服务端最显著的区别,我们希望能通过正式发版前的灰度环节发现更多的问题;显然我们安装的新版人越多,反馈的问题越多,那么我们的灰度效果也越好;于是我们尝试了两个思路: 第一个思路是对内: 我们考虑到公司内部有好几万人,里面有RD,PM,QA,他们的专业和对故障的敏感程度是远超过普通用户的,如果可以很好的利用这部分资源,我们相当于有一个几万人的后备资源池,这个是非常厉害的;所以我们在18年开始在公司内部做了这样的尝试,上线了一款小程序,叫“字节内测”,我们自己的运营团队会定期和业务方做一些合作,吸引他们在我们的平台设置一些活动,引导大家去下载他们的新版,同时反馈试用过程中的问题,并且给予一定的激励;那从目前来看呢,我们的ROI还是很正向的;每周大概参与到活动的人数在7000人以上,平均反馈的问题数量都在几十个上下,其中P0到P2的问题能占到四分之一以上,如果严格去计算的话,我们为此付出的成本,主要是运营人力和激励,还是明显少于这些问题外泄到线上后带来的损失;所以呢,内测活动还是很划得来的;但话说回来,这个对我们内部运营的要求是比较高的,我们需要持续进行一些运营的引导,降低用户参与的门槛,保证活动-反馈这个通路的畅通。 file 我们尝试的第二个思路是对外: 公司用户虽然不少,但相对于外部的几亿用户而言,还是很小的一部分,所以我们主要精力还是要这些数以亿级的普通用户上,他们才是我们要挖掘的重点;问题就变成了我们怎样在这几亿用户里通过算法精准的去找到这样一些目标人群。 file 要使用模型算法,那前提得准备数据;这里面能利用的数据维度还是非常多的: 首先是用户app信息:用户使用app的习惯,浏览内容偏好,活跃时间段,老用户还是新用户等等; 其次是用户基础信息:性别,年龄,城市等等这些也可以辅助我们做一些判断; 除了用户相关的这些信息外,我们也可以结合版本本身的一些属性,比如我这次上线的是一个直播相关的功能,那我应该优先去覆盖平时玩直播比较活跃的这部分用户;这样的话,可以实现一个版本信息和用户的深度双向匹配;提高我们算法的准确性; file 那么在实际落地阶段,我们还会根据不同业务的情况来对模型做针对性的优化;比如对于头条,抖音这种亿级APP而言,我们可以拿到足够的数据来训练个性化的模型;但对于一些小业务而言,因为数据规模达不到可以单独训练的量级,所以我们会提供一套通用的模型。 总而言之,目标还是非常明确的,就是提供CTR和CVR这两个数据的转化率。 这些就是我们在放量这个事情上做的一些尝试工作,目前我们也取得了一定成绩,通过我们的算法模型比手动盲选平均有10个点左右的提升,但这个事情的天花板很高,我们后面的提升空间依然很大,需要继续努力; file 再讲最后一个痛点:如何保证发布数据的有效性? 大家应该还记得我们分享开始时提到的因为链接配置出错导致的线上事故,根本原因在于我们的升级体系没有一个可信的数据源。 为什么我们不能直接用链接呢,因为链接仅仅代表了这个产物的获取方式,并不是唯一代表这个升级包,如果这个链接被篡改或者覆盖,那么意味着你的发布就会出错,而制品库的存在就可以避免这一点,他保证给到下游使用的数据是可信的,是经过完整测试的。 所以在我看来,制品库他是作为整个devops的核心数据基座存在;如果说CI(持续集成)是对代码负责;那么CD(持续发布)就是对制品负责。 发布过程中所有在出包后的数据,基本上可以认为都在针对制品来做的,不管是安全检测,还是功能测试,或者是用户故事,都代表这个制品存在的某些特性;如果我们发现某个制品有问题,我们可以把他放到黑名单里面,这样就不会影响后续的放量。 有一个案例可以分享下:我们内部有一个直播中台团队,因为他们是跨业务部门,头条/抖音/西瓜等多个业务都要用到他们的直播插件;之前在制品库出现之前,他们的做法是用飞书在线文档来存储这个包,然后还会非常详细标记这个包的状态,谁,什么时间,什么原因,备注信息是什么,里面有什么功能?这个过程是非常繁琐和低效的,那在制品库出现之后,我们可以把整个出包前的配置信息和之后经历的事件动作全部集中在一处,这样用户就可以很方便的根据各种标签条件去找到他需要的包,同时我们还支持订阅模式,比如我只关注正式类型的包,那我可以订阅下正式包这个标签,当出现此类包的时候,就会主动通知到你;当然前提是你得有这个产品的访问权限。 那么现在,我们可以回答这个问题了,发布数据的有效性是通过我们对制品库的质量品控来保证的,他作为我们平台的一个数据基座,需要做到和流程解耦,保持一定的独立性,这样才能适配各种复杂的场景。

3. 字节跳动移动发布的实践总结

那第三块我们整体介绍一下字节在发布体系里面的一些实践和总结。 file 先看这组数据:我们现在大概每周会有 10 万次的构建,每周超过 700 次的灰度,每周超过千万量级的灰度实际的人群。 file 我也总结出了几点关于迭代的经验,大家可以参考一下: 首先是最佳实践的问题,我们整个平台的迭代史都是以一个典型业务作为的一个模板进行持续优化的,不追求一步到位,持续迭代是最好的状态; 第二是珍惜事故,事故都是很宝贵的经验,所以我们对于每次事故就尽量要做到极致,我们要做非常完善的这个 case study,也就是“5W” 原则;这个地方需要提一下,作为平台方千万不要去甩锅,业务方没有遵守规范,那可以问下自己是不是给了业务方犯错的空间了?我是不是给到足够的引导跟支持了?我能不能让他没有犯错的空间,我能不能杜绝一切犯错的可能性? 第三个是需求,因为需求是做不完的,那怎么办呢?我觉得作为平台方,心态要开放一些,我们可以做通用的需求,平台可以尽量做到闭环;但是对一些个性化的需求,不要去强求,不把所有东西都揽下;我们可以制定规则,给其他人参与的机会,一起合作共建,把生态做大。 最后一点是平台价值是需要被度量的,还是老生常谈的那句话,不能度量的不能被改进;你如果想改进,就必须可以被度量。 file 其次分享一下移动发布版的发展趋势,这个趋势是我们内部这几年做平台感知到的一些变化。 首先是版本发布逐步出现高频化的趋势;从之前的单月到双周,而现在主流的业务可能是一个单周的节奏在迭代,将来可能用不了多久,可能会呈现半周/天级别的演进,这个趋势还是比较明显的; 第二是安全越来越受到重视;这应该是一个常态了,上面其实讲的比较多了,这里不再赘述; 第三是精准测试场景;传统的都是基于已经写好的用例,这些用例最大的问题在于后期没有人维护;当UI只要有一点变化,测试用例就基本上不可用,必须持续的对这个用例进行更新,这样后期的维护成本是很高的。现在基于 AI 技术的精准测试,后面可能成为一种主流,我们不需要去维护大规模的case ,它会自动根据我当前的这种场景去实时生成测试场景,我们内部也在做这方面的一些尝试。 最后一点是持续灰度的理念;随着版本的日益高频这个是必然的,也就是灰度版本和正式版本的界限会越来越模糊;你不知道哪天是在灰度,明天就正式了,过两天的又进入下一个灰度。持续灰度这个理念也是我目前感知到的一个点。 file 从平台视角来看,还有几个点可以稍微提一下;速度、效率、安全、成本。具体的每一点大家可以参考一下,我就不再细讲。 我稍微提一下降本这一点:为什么我们有降本呢?因为我们要存储大量的包,所以对存储是有一定的要求的。 带宽这一块可能是应用场景下比较明显的一个特点,因为如果你是一款拥有亿级用户APP的话,随着迭代周期的缩短,包的更新频率越来越高,那由此产生的带宽成本的费用也是比较高。每年的花在 CD 成本上的这个钱估计可能会上亿。钱其实还是挺贵的,我们还是想办法降一下成本。具体怎么降呢?其实有很多方法的,但是不是今天的主题,所以先不一一展开了。 file 最后做一下总结:今天我们讲了很多的点,有流水线、安全能力还有放量。仔细去看这些点,它们其实都在追求 balance :比如说流水线,在原子能力上太大或太小都不行,要追求它的 balance;安全和业务的 ROI 的中间的取舍,我们也要做 balance;放量这一块,我们要在速度、效果、用户体验之间追求balance。 所以总结起来,整个发布平台的迭代过程,我们都在不断追求 balance 。在特定的时间段内,想要追求平台的最大业务收益,就必须在某一方面进行取舍,不能什么都想要。 file 在这里分享一下我们平台的未来的发展方向: 第一,我们会对发布概念做一些延展。从目前的小发布体系,逐步的把它向大发布体系去引进。那何谓大发布体系呢?除了现在正常的升级包、热修包以外,我们可能还会把配置资源或者静态资源给也纳入整个的发布体系里面来,形成面向业务统一的一个大体系。 第二,我们会持续优化放量,对放量做一些算法模型化的优化,也尝试引入更多的一些数据维度增加它的丰富性。 第三,我们会更加精细的度量,把它形成消费的闭环,而不仅仅是去搭一个数据看板,数据很好看但并没什么用;最终还是要去消费、去改进。

image.png 最后介绍一下火山引擎应用开发套件MARS,今天分享中所提到的能力未来会通过MARS开放给大家,感兴趣的朋友可以扫描右下角的二维码,申请免费试。如果有私有化部署的需求,可以直接在公众号留言,我们会有专人去提供帮助。

今天我的分享内容就全部结束了,期待将来与大家能有更多的交流,谢谢大家。