00:05
谢谢。呃,哈喽,大家好,呃,今天分享的主题是字节跳动service的落地与实践,呃呃,前面的几位讲师的讲的非常好,压力给到我这边了。那个我先做一个简单的自我介绍,我是呃一九年作为呃早期成员加入的字节跳动的Bo那个呃团队。然后参与的架构从零到一的演进,呃,包括冷启动呃,控制面呃呃,二进制、大规模分发、异步任务等等的一些产品,而目前主要负责微服so化这个方向的落地。那今天我演讲的内容主要分为以下四个部分,呃,第一个是一个简单的背景介绍,主要是呃,简单介绍一下我们函数计算平台的产品形态,落地情况以及技术体系。
01:02
那第二部分是介绍我们在MQ消费场景的大规模落地实践啊,包括在托管消费弹性伸缩以及性能方面的一些优化。那这三部分是我们在呃,基于在MQ消费场景大规模落地的一个呃实践经验之上,我们在微服场景下的一个实践和探索。那最后一个是一个,呃,总结和展望。OK,那我们先看一下第一部分。那从产品形态上,我们首先推出了fast的这样一个标准的函数计算产品。呃,它主要包含了fast场景下的函数开发、事件驱动以及呃,弹性伸缩的一些基础能力。那第是呃,我们推出了fast native这样一个产品,它主要是为了让用户无需更改代码就能够平滑的从pass平台上迁移到fast上。那第三个是fast worker这样一个呃,基于V8和微博ZBLY的一个轻量级,运行时,它具有毫秒级的能启动的特点,就是呃这个对于一些呃对人群动非常敏感的一些服务就很有帮助。
02:11
那最后我们同时也支持了fast job这样一个异步的执行模式,它打破了常规的呃,Fast函数执行时长的一个限制,支持用户去执行长任务的一个需求。那目前的话,函数计算已经在呃自己内部大规模落地了,我们的用户覆盖了绝大部分的业务线,那目前平台上我们已经有超过10万的服务,然后呃日活跃服务数数量是超过2万个。然后呃,微服务网关这一部分,呃,我们目前的QPS是120万左右,那通过smash进来的流量超过2000万。那在MQ消费场景下的话,我们的每天的分值的QPS超过1.4亿。那单独从QBS这个角度上看,应该是业内函数计算领域最大的规模了。
03:06
那接下来我们看一下我们的一个基础体系,那因为字节它整体的一个,呃,基础设施它是基于K8S这个生态来建立的,那我们fast也不例外。那上面这一块的话是,呃,我们fast自身的一个系统组件。它是部署在我们内部的一个pass平台上面的,嗯。底下这一块的话,是我们独立的一个呃,一些K8S集群,它用于部署函数的一些运行时,那从上往下看,呃,我们呃先是托管了呃事件的接入,那包括LB消息队列事件啊,定时触发器等等一些触发事件源。那其次我们也托管了请求的一个调度。那他这里包括我们的网关,呃getway和呃请求调度的一个组件PA,他也负责我们的服务发现。那呃其次第三我们也呃托管的资源和弹性伸缩,我们呃通过一个旁路的一个自动扩缩容组件和一个相关的指标收集系统来帮助函数进行弹性伸缩。
04:10
那底下的话是我们的一个运行时部分,呃,我们提供了呃迈克VM让C进程的隔离等不同的一个隔离技术来满足用户的一些需求。那除了以上这些之外,我们也提供了服务,治理,监控,运维以及呃开发调试等等一些一站式的一些呃需要的一些呃其他的一些功能。那同时也可以看到我们是多机房容灾部署的,那常态情况下,我们的流量是在多个机房之间是均匀分布的,那如果是故机房故障的情况下,我们可以一键切油的方式啊让啊进行容灾。OK,那以上就是背景介绍的一些部分,呃,接下来我们看一下我们在MQ消费场景下的一些呃实践经验。
05:01
这是我们呃消费架构的一个主体部分,中间是我们实现的consumer啊,我们也称之为触发T,他会负责去向呃MQ那边去拉取消息,然后同时向PA组件去呃获取我们的函数的呃实例列表。那同时将消息一定的格式分装好之后,然后再发送到我们的函数实例上。同时有一个旁路的一个autoca的一个扩缩轮组件,他会去同时感知consumer和函数实例的一个状态,然后对他进行一个扩声,一个扩缩。那细化到呃,Consumer实例内部的话,我们把一些消费场景下的一些通用的和业务无关的一些逻辑啊,都集成进来,这样业务他就不需要关心consumer的一些实现细节,他只需要通过一些白屏化的一些配置,方便的使用相关的能力就可以了。同时我们也根据呃一些实际需求啊,去不断的迭代我们的一些相关的能力,满足用户的需求。
06:04
呃,首先一个呃,需求是消费限流,这个在MQ消费场景中是非常常见的。呃,主要可能还是因为用户他的下游承载不了那么多的流量,那需要给MQ的消费速度配置一个整体的一个限流阈值。那一个简单的做法是直接在触发器之间去均分这个限流阈值,但是因为MQ它的partition消费其实可能是不均匀的,同时每一个partition之间,它的生产速度也可能是不均匀的。那这样的话就很可能导致触发器的实力消费不均匀,呃,整体的限流一直打不满。那所以我们这里是支持了一个动态分配限流阈值的一个策略。呃,我们的这个consumer它会呃,定期的去上报自己当前的一个现有日值和它的一个流量的情况,然后呃,我们有一个重要的协调器来统一的按需去分配这个现有阈值。
07:01
这样的话,让呃消费速度尽可能的去贴近这个限流。那另一个常见的需求可能是呃,是呃请求的并发管理,呃消费场景中经常会有呃非常高吞吐的一些业务。那并发的消息处理是很常见的,所以我们也支持了,呃,动态的并发管理。我们将worker作为一个资源池来进行管理,那按需创建呃,复用以及去释放这些并发。这样可以兼顾执行效率和资源使用。那另外一个方面就是,呃,我们需要有一些机制去避免我们的函数实例被打垮。那我们通过反压的机制去合理控制整个work池的一个大小,那避免并发过高导致我们的函数实力被打垮,同时呃,也能避免就是说并发过低导致资源利用率上不去,同时消费吞吐也上不去的这些问题。那这些工作的话,主要都是呃,帮助业务去提高开发效率。
08:03
嗯,我们再来看一下在弹性伸缩上面的一些优化。我们先看一下弹性伸缩会带来一些什么问题。呃,大家可能如果用过MQ的可能都知道,就是我们的consumer在消费升。呃,在消费的过程中,如果升级的话,它会触发re balance的问题。那如果我们对consumer实力进行频繁的扩缩的话,那往往会加剧这个问题。比如在kafka2.4版本以前,这个re的机制是基于一个叫E协议的。它有以下两个问题,一个是会导致呃,Consumer都停止消费啊,发生全局的一个消费断流。那另外一个就是,如果我多次去触发这个re balance,那很可能就会出现我一个consumer消费的partition,他在呃,Re balance给其他的consumer之后,很快又re回来的情况。那对于这种。呃,对,这种情况的话,如果是呃,Consumer的规模非常大,那这样的话,他对消费的稳定性影响就很大。
09:04
为此,社区就支持了两个协议来解决这些问题。首先是cooperative协议。他的一个思路是,呃,就是其实它解决的是re balance过程中所有consumer和都需要参与的一个问题。他的思路是说。呃,我在re balance过程中,其实有一些partition,它是不需要去做一些呃迁移的。并且我们的每个partition,它是可以独立消费,互相不影响,那我们就可以做到,呃。这是增减的consumer的时候只会引起局部的一个partition暂停,这样的话不会造成一个全局的一个影响。我们把这个特性引入到我们的实现中,同时结合我们的横向扩缩的这样一个能力,就能呃极大的避免在呃扩缩容过程中的一个re balance影响。那这个地方我们做过一个测试,就是有600个consumer实例,200万QPS的一个场景下,如果是之前的一个协议的话,它会造成至少1亿以上的一个LA,那在cooperative协议下,我们呃能够优化95%以上,就是只有500万左右。
10:17
那另外一个优化是state membership协议,它的思路是说,呃,Res,触发条件是有consumer group有。呃呃,就是consumer group中有成员离开或者加入。那这个对于呃,横向扩缩场景是可能是没法避免的。但是对于我们,呃,一些纵向扩缩容,或者说呃,滚动重启这个过程,那其实就可以做到,比如说我给每一个consumer一个静态成员ID,那这个成员ID它退出,然后短暂重启之后,它能通过这个ID继承之前持有的partition,而不触发re balance。这个就非常适合我们纵向扩缩容的一个场景。
11:01
OK,那通过前面这些rebance的优化扩缩,带来的消费稳定性问题就基本上解决了。嗯。OK,那除了弹性伸缩上面的一些,呃,降本的话,我们其实也在性能上做了一些优化,那回顾一下前面我们讲的一个消消费架构。我们的。跟实例和函数实例其实是解耦的,它是一个分层架构,这样的好处是它是呃,隔离性比较好,能够独立进行升级和扩缩,但是这增加了额外的一层请求转发,这个肯定比,就是说呃,我在pass平台拿到消息直接本地处理,这个肯定是呃会有一层额外的开销。那实际上的话,其实很多业务他在消费MQ的时候会做一层过滤,就是大部分的消息,他可能就直接抛弃掉了,他他直接skip掉了。那对于这种场景的话。
12:00
我们把这种过滤逻辑直接上推到consumer内部来实行。呃,就是我们通过了go on的plugin机制,允许用户把一段过滤逻辑直接上推到我们的呃,Consumer内部,这样的话就能过滤掉大部分的一些请求。呃,这个在一些大流量的场景下,我们很多情况下能挡住90%以上的流量啊,那效果是很明显,不过它本身也具有一些局限性,就是刚刚也提到就是呃。我们的假设是说这个用户他本身,呃,他的流量很多是不需要具备消费的嘛。OK,那另外一个优化是说,呃,我们。做了一些亲和性调度的一些事情,就是尽可能的呃,让我们的触发器和函数实例通过亲和性调度去合并部署。尽量到调度到同一台机器上,然后在保证呃请求均衡的情况下,尽可能的本机通信。比如说嗯,还有一些其他的一些优化方案,就是说我们也呃利用了比如说呃共享内存的一些进程间通讯方式,去替代传传统的一个TCP的通讯方式去呃降低我们的请求延迟和开销。
13:15
OK,那以上就是,嗯,不好意思。OK,那以上就是我们在MQ消费场景下的一些呃,实践经验,那既然我们已经在这方面得到了大规模验证,我们自然就会想把这些能力赋能到更多的微服务上面。同时内部很多用户也说,呃,我不想改代码,但是我想降本,我想用service。那接下来我们看一下在微服场景下我们的一些尝试。呃,首先这一个标准的函数运行时它呃是长成这样的,呃,我们提供了统一的基础镜像和服务框架,呃,然后同时有一个我们的赛卡进程去对业务进程进行强管控,以及流量的接管。
14:04
那用户他只需要去自定义实现这个handle了部分的代码。但是这个和传统的微服实现是差别很大的,那传呃已有的一些微服,它要迁移上来的话,它的成本是比较高的。呃,为此我们推出了fascin这样一个方案,就是呃。主要是为了支持原生应用的平滑迁移。首先我们支持了业务,在遵守一定规范的情况下,让他自定义so的逻辑以及启动命令,让他能够平滑的跑起来。那另外一个就是呃,刚刚也提到我们在呃。Fast的这个场景下,我们是把那个镜像给统一起来,这样主要是为了优化能启动。但是在。呃,微服这个场景下有一些业务,它可能就是对镜像有一些依赖,那我们也需要在这种去支持用户,这样平滑的去迁移起来。
15:01
那我们是,但同时我们也呃不能去呃放弃,也不愿放弃我们对那个业务生命周期的一个强管控,以及流量的这样一个接管能力,所以我们通过了Co ne的init容器机制,把我们的S卡容器。呃,把我们的一个set car二进制放到了和业务容器共享的一个warning中,然后在启动业务容器的时候去启动我们的赛卡进程,这样的话它能继续接管业务容器。OK,那第三个是我们支持了多协议。呃,这这样一个能力。呃,大家可能都知道在fast上,呃,HTTP它肯定是第一公民,但是在微服场景,呃,至少在自己内部,它的一些东西上的流量,它其实大部分都是通过RPC来进行的。所以我们也支持了IPC协议,但是这个过程中我们也希望能够复用大部分的一些系统控制链路和流量的调度逻辑,所以我们把控制面和呃数据面做了一个拆分,把IPC的一些改造都完全放在了数据面的这一层。
16:13
OK,那另外的话就是呃,自己内部它有一套非常成熟的bama体系。而且他目前已经覆盖了大部分的微服务,所以我们也需要让跑在fast上的这些微服务能够融入到这个体系当中。呃,首先就是嗯上游它需要通过mash把流量导进到fast,在上游的C侧一般都会有一个mash出流量代理,那我们和mash之间进行了一个呃服务发现的一个协商,那如果是在流量很小的情况下,他会把请求打到我们的网关。呃,如果在流量很多的情况下,它是直连我们的函数实例,这样能兼兼顾小规模和大规模流量的一些优势。呃,如果是出流量的话,其实这个直接,呃这个支持就非常直接了,只需要在我们的环境中直接去,呃集成fast的出流量代理就可以了。
17:08
那这样的话,我们呃,微服就可以跑在我们的fast上了,呃流量也能接入到现有的微服生态之中。不过这只回答了业务能不能上fast的问题,那我们的优势其实还是呃,弹性伸缩,能启动等等的一些能力,呃,还有就是说帮助业务去免运维事件驱动这些能力。所以呃,同时在微服务场景下业务它对延迟和稳定性是非常敏感的,他对弹性伸缩和请求调度能力是要求更高的。呃,比如说在fast中一个非常典型的问题,对请求延迟非常大的一个问题就是能启动。嗯,特别是在没有流量的情况下,我们会把实例直接缩零,那如果用户这时候有流量过来的话,它就会触发零到一的冷启动。那一个最简单的思路,其实就是给用户去预留一个最小实例数,来避免零到一的一个能启动,但是如果是用户他有一些突增流量,那我们的弹性扩缩如果跟不上的话,就会还是会触发冷启动。
18:14
所以我们又支持了预留实例的模式,呃,配合不同流量的调度测远,让用户用户进行选择。如果说他对于呃能启动就非常敏感的话,他可以选择优先弹性实力的这种模式,那预留实力在这种情况,它在常态情况下是闲置的。那如果有突增流量的话,它就能作为buffer来填补弹性扩缩跟不上的情况,从而避免冷启动。不过预留模式只是解决了呃,就是说我们弹性扩缩速度可能不够快的问题,但是他还是会有一些资源浪费,所以我们最终还是要把弹性能力给提上去,做到更快捷的一个呃,感知和扩容。OK,那那说呃,说到这个感知扩容的这个,呃,事情之前我先讲一个相关的背景。
19:08
就是我们发现在呃可能很多函数的一些实现,呃,就是它是基于一个呃并发这样一个值去做一个实例保护,或者说让用户配置一个并发需要多少资源,但是我们发现在微服务场景中啊,这种基于单实例固定并发值,或者说单并发固定资源量的一个方案,是很难做到实力的保护和资源的充分利用的。原因是呃,单实例相同CPU memory的情况下,他能承受的并发值其实是变化的,比如说用户的代码,它很可能呃做了一些变更,或者它的下游有一些呃劣化,呃,又或者说。呃,我这个机器上我不同的实例,它的CPU机型其实是不一样的。啊,同时呃,比如说呃,很多情况下,上游它也是去均匀的打流量的,那这种就会出现,比如说我们平均CPU还在百分之四十五十的情况下,就有一部分实例出现过载,出现一些SSA的一些呃,Drop。
20:07
所以我们在函数实例内部的这个set看内,我们做了一个呃过载这样一个方案。他会实时的去检测CPU的使用率,然后同时去收集请求的啊,排队时间,完成率,超时率等等,来监测我这个实力是否处于一个过载状态。那如果。已经开始出现过载,那我会选择拒绝一部分请求来保护我,这个实例就是避免被打崩掉。同时,我们会等待函数的扩容和实例的恢复。那另外我们一旦检测到这个实例的过载,我们会迅速的把这个信号呃反馈到我们的autoca组件,它会在秒级去扩起相应的一些实例。然后然后呃,刚刚其实也提到,因为你即使呃这个负呃请求是均衡的,但是你不同实例上它的负载可能不均衡。
21:02
那在一些提前扩载的实例上,我们就能把这些请求呃,过载呃,通过转发的方式转发到横向,转发到其他的实例上。呃,这个对于比如说我们新扩起来的一些实例,还有刚刚提到的预留实例,那他通常情况下,它的那些负载是相对比较低的,这样就能够,呃。比较大的去提高用户的成功几率。OK,那即使我们呃,前面也就是做了预留实例,快速扩容过载转发啊等等这些事情之后,那如果遇到一个比较大的一个突增流量。那还是避免不了要触发请求的冷启动。呃,请求能启动的耗时,嗯。主要是包括就是说实例的拉起时间,然后代码下载时间,或者拉进项的时间,还有用户的进程启动时间,那用户进程的启动时间的话,一般是交由业务来进行优化,那我们先看一下前面两部分我们是怎么进行优化的。
22:03
呃首首先我们呃通过预热实例的方式将函数实例给预先启动好,那发生能启动的时候,我们就直接从这个呃预启动的池子里面去呃按需启动一个已经启动好的实例,从而跳过K8S的调度,然后本机的实例拉取时间呃镜像下载时间等等这些呃延迟。在下载代码呃这方面的话,我们呃采用了热点代码的一个多级缓存机制。呃一级缓存是我们的本地磁盘,那如果本地命中的话,呃就不用从远端去下载了,这样就呃能够呃省去远程下载的这样一个呃下载和解压的开销,如果没有命中的话,就会回原到我们的n g cash以及呃对象存储系统,那这样的话它的下载延迟就会比较高。所以我们又引入了代码lazy load的这样一个方案,这个其实是参考了呃社区呃NAS镜像呃按需加载的这样一个方案,我们在构建过程中会先把用户的代码和依赖把它打成一个可寻址的一个ne格式。
23:15
呃,在能启动过程中,我们其实只是先把这个代码包,把它的原信息给下载下来,然后。同时net进程去创建一个挂载点,然后把我们的代码目录和这个挂载点给办了起来。然后就可以同步去拉起进程了。那这样的话,呃,我们不需要在能启动的时候把全量的代码给下载下来,然后它是一个按需去下载的一个方式,就是这个进程它启动时需要哪些代码的部分,它就按需去下载。那这样的话,在一些场景下能够显著的降低下载代码的延迟。OK,那目前这种预热实例的方式其实已经能解决很大一部分的问题,但是它本身有一些局限性。呃,首先它的镜像和资源规规格是预先指定的,这个跟函数的实际需求他可能对不起。
24:06
那第二个就是我们需要根据我们的函数的run,它的协议以及它的呃。呃维度就是它的资源规格等等,都要做一个资源池的一个提前启动,那这样的话,它容易会导致呃色变化非常严重。而同时它预先启动的容器,我们是比较难以去做一些亲和性的代码分发的一些调度优化。所以近期我们在做一个能启动深度优化的一个事情。呃,对能启动电路我们进行重构,然后呃通过更细力度的通用的资源池化来解决呃刚刚提到的一些碎片化的一些问题。那这个思路是说,呃,我们在进行缩容的时候,我们不是把这个函数镜像给直接删除,我们是通过把这个容器内部它函数使用过的一些痕迹给清理掉,然后给其他的函数来进行复用的方式,这种方式。
25:05
比如说容器内部的呃,C group namespace进程等等这些,其实它很多场景下是可以复用的。那这样的话,它从上层四角,它其实还是在同步的去拉起一个实例,呃,但是实际上它的开销会小很多,呃,再加上我们在呃容器调度以及单机管控电路去进行一些深度的优化,这个会替代K8S的一些调度的流程,那这样的话来进一步降低调度的一些开销。那当然,这个事情我们现在还在进行中,呃,从POC的结果来看,我们是有希望能够把这个容器的调度时间呃,降低到十毫秒这个量子。OK,那就前面就是今天分享的主要内容,接下来我来做一个简单的总结和展望。首先我们会持续推进业务的so化,那MQ消费这一块的话,其实有呃,相当大规模业务已经在使用了,而且目前在持续上量。那微服这块的话我们会呃,就是接下来的方向主要是让fast和fast pass互相融合,我们会提供一个更广义的service计算引擎,让用户无需感知它是在哪两个平台上进行跑。
26:22
然后在技术能力上,我们会进一步提升弹性伸缩能力,降低冷启动的延迟。那第二部分的话,我们会呃。进一步提高呃系统的拓展性和呃容灾能力。一方面,我们会通过单元化的部署,对系统和业务进行一些分片的管理,来减少爆炸半径,控制这个单元规模。那另外一个我们会在单单机房内进行多集群的容灾部署,那提供自动容灾的这些切换能力。那最后一个就是我们会,呃,资源共识内外一起。
27:00
啊,目前我们在弹性伸缩上面,其实已经帮业务节省了非常多的资源啊,对于流量的错峰场景的话,呃,是非常有效的,但是我们整体上集群的实际资源使用率还是有一个波峰和波。那低分期的这个资源目前还没有被很好的使用起来。啊,所以这块我们会接入统一的资源池啊,通过在在离线混部的方式能够啊提升资源的售卖率。呃,同时呃,我们也会推荐呃业务内部的业务去上云,然后通过火山引擎内外一体的方式去做技术复用啊,打通两者之间的资源池,来进一步提升资源的利用率。
我来说两句