Golang在阿里巴巴调度系统Sigma中的实践

640?wx_fmt=png&wxfrom=5&wx_lazy=1

作者简介

640?wx_fmt=png&wxfrom=5&wx_lazy=1&retryload=1

李 雨 前 

花名叫鹰缘

系统软件事业部调度系统。


关键词


  1. Golang

  2. 调度系统

  3. Sigma,阿里巴巴自有的内容

  4. 实践交流

  5. 工程


 


1.取材


资源调度领域Sigma

 主要思路是说资源调度领域的Sigma,共性的借鉴性的东西,阿里特有的就不讲,更多在Q&A里面。因为涉及到实践会聚焦工程的问题,所以我会讲一些架构设计与语言的选择,并发模式下面任务粒度怎么样去选择,还有一些比较大型、综合的解决方案。


2. 工程问题

  所有的工程问题,突出背后的故事有几个线索,第一个线索就是跟规模化相关,阿里的规模很大,背后就要支撑很大的规模。另外就是阿里还有很大一块就是上云,包括双十一,很多东西跟云相关。

   另外我们踩过的坑针对这些坑的解决方案跟大家分享一下

  最后就是在Golang当中bug,最怕低级错误引起的。



640?wx_fmt=png

   Sigma的业务有两块,一块是对内,一块是对外,对内是所有的bu都接入了Sigma的系统,我们的规模会有100万级别Sigma的内部有一个logo,很容易理解,就是数学求和,Sigma是很大的生态系统,需要很多人和系统配合完成。


   这个业务分了几个层次,上层业务偏运维,下层业务偏系统,从接入层,到中心的Master,到Slave,到会有一个Pouch


这是sigma的架构

640?wx_fmt=png

可以看出来,颜色有三块,一块是Sigma的,一个是0层,还有一块是关于Fuxi的,通俗的理解就是Sigma管在线,然后是Fuxi线的,中间是一个协调层,这样理解起来就跟Mesos比较接近一点。这个架构肯定不是最优的,但是它存在有它客观的原因。

我会从Sigma这边抽象的四个案例。我会给大家展示是什么的同时,讲背后我们的故事。


案例1

   首先看一下APIServer

640?wx_fmt=png

  我们平常写代码经常会接触到这些事情,在业务领域、调度领域稍稍不同,背后要做发布、扩容、销毁、启停、升级,还有云化,特别是双十一到阿里云买服务器,所以会有混合云需求。我们规模很大,就会要求所有简单的事情怎么样让它规模化的场景下面依然能够工作我们调度系统跟运维有关,核心的内容就是怎么样做到运维友好。还有就是我们做在线的容器服务,肯定要做到高可用一致性。


解决思路

1. 数据一致性

数据的一致性上面我们是用etcd/redis,我们会用一个实时+全量的方式做到数据的一致性

2. 状态的一致性

想做到很好是很难的,但是我们把状态的一致性转为存储一致性来做,就会降低处理问题的难度。

3. 简单的

我们没有追求技术看起来非常完美的方案,先把业务推起来能够用就了。

4. 高可用-无状态

我们要做到前面说的高可用,有几种方案,一个是多Master结构,还有无状态,还有就是快速的failover,我们希望做到无状态。

5. 降级-抢占

规模大了以后就会有一个问题,很多人都要资源,这个时候肯定会有一些稀缺的资源,系统要支持降级抢占。

6. 内外兼容:一个团队两块牌子

要做到上云,所有的思考都要考虑到对内对外是一班人马,一个团队会有两个牌子,这是我们整个出发点的思路。


   思路之后我们选择由APIServer把前面的发布、扩容等都放在task,丢到Redis里,底层的Worker消费一些任务,做到无状态,得出来的结论就是架构整体的设计大于语言的选择。

   数据一致性里面,如果能做到了实时,数据应该就是一致性的,为什么还要加全量来弥补这个过程?背后一个重要的原因就是物理机的硬件或者是软件会经常出故障,系统多了之后,很难保证故障的事件跟整个链路100闭环起来,就导致总有一些地方的数据不知道为什么不一致,有很多硬件、软件的故障导致实践的层面做不到一致性,就只好加一个全量弥补。还有另外一种方案不要全量,我们定时同步,但是有一个问题,时间窗口怎么定,定几分钟。同步的一瞬间,数据会有一些比对,这个时候加锁,成本维护会多一点。

  状态一致性问题转为存储一致性的问题最好的就是转为存储一致性,我们为什么说要面向etcd,在很大的系统里面有很设备系统,没有办法面向事务去做,最好是分布式的存储把这些问题兜掉。

  为什么说简单够用,后面会有数据给大家看一下,要承载的量很大,没有办法做到95%以上才上线,大家等不及,可能做到85%就要上线了。

   还有就是降级,前面我提到了阿里这么多的BU,每一个BU都提了很多的预算,去采购,这就浪费很多资源,这中间有一个共享的buffer,肯定会有抢占,这个在开源里面也会有一些策略。

   内外兼容如果是创业公司会有体会,一部分东西是基于内部的服务器,还有一些是购买上云的服务,这个时候管理起来,不可能上云的是一套管理方式,自己内部又是一套方式,至少在开发理解上是一模一样的,在设计和架构的时候要屏蔽很多的差异。基于这样的思考,我们觉得这种方式相对来说会比较好一点,不然尝试换其他的方式,问题解决可能会稍微麻烦一点。


   前面讲了设计的架构,接下来讲案例是一个真实的场景。

640?wx_fmt=png

  我们在阿里要做一次发布,比如说一开始要拉镜像,就要关闭告警,可能有一些应用要下线了,还要停止应用然后开始容器升级,还要做业务逻辑的检查,检查完之后才把告警打开不管是上云还是内部,这套流程肯定是公共的,具体对接的时候又不一样,内部云上不一样,云上提供一个SDK,云上的接口的异步性的流转和内部也不一样,所以要抽出来。

   内部阿里云上面是这样的事情,阿里云比较特别的是什么呢?在before会做一些处理,我们核心的架构是很固定的,在阿里主流的语言是JAVA,我们讨论架构的时候,没有局限于上来就用JAVA,Golang,或者别的,我们把架构想清楚,之后才选择用什么样的开发语言。

   是我们数据的表现

640?wx_fmt=png

这个表现是任务量,数字隐去了,这个大概是双十一的时候,发布的量非常大,平常的量比较小,可以看到它有周期性的规则,因为阿里的体量有发布窗口期,那一天整个的发布量大了,会有一个全局的管控。


案例2



640?wx_fmt=png

   调度里面核心的模块之一是Scheduler很多物理机上面选最佳的物理机,把容器布上去,常规的做法就是要有过滤链,过滤完之后会有一个权重的链,最后拿到我想要的机器上去创建这个容器。因为规模,一个请求在一个机房里面去扫,这一个机房可能就是万级别的节点,肯定要考虑到性能的优化,所以筛选的规模比较大一点。它是一个链式的,首先要选择哪些服务器作为点,在这一块,我们的想法就是要并发起来,怎么样从工程上面做一个实践。绿色的框是顺序过程,红色的框是并发的阶段,或者是关键资源的锁的过程。每一个并发的粒度看成是一个物理机,相当于每一个物理机去筛选这个是否要,这是一种模式。


640?wx_fmt=png

   另外一种模式是说在整个机器的顶层加一个glock,是顺序还是并行不用管,这个锁加在上面前面的锁是加在下面,两者看起来好像没有太大的区别,测试下来发现性能不一样有几个因素,一个是下面要做过滤链或者做weight,看开销还有每一次筛选的规模,比如一天下来有十万的请求,每个请求背景物理机是好几万的规模还是几百的规模,最后对整体的性能要求是不一样的。



640?wx_fmt=png

粗粒度并发的性能比较好,我们选择了第二种场景深入的思考的时候,我们发现第一种场景行不通,原因是有些全局的资源没有在一个协里面更改,另外一个协程不能立即可见。


案例3



640?wx_fmt=png

   Golang最近几年非常的火爆,但是在阿里大的氛围里面更强调的是JAVA语言,我们引入Golang不可能一上来就大规模,需要有一个成功的案例,或者是小规模实践的过程在这种环境下面,我们让Golang有一席之地,首选的方式就是如何做到快速的打磨,跑得很慢的时候,语言构架系统可能就会被淘汰掉了。这是我们上一个版本,有大概五个链路,有一个架子,每一条链路是做的过程当中,一步一步完善出来今天我们把链路搬出来跟k8s做比较的时候,很多地方都是相通的,但是具体框架的编码实践上面来说是有很大的差异。我们早些时候摸索的这些东西,可能就是业务驱动或者概念驱动,没有真正做到工程或是回馈社区的驱动未来我们换了一种方式,我们可能是以工程的方式驱动,就是回馈社区。


案例4



640?wx_fmt=png

   我讲一下怎么解读这个图分几层,上面这一层更强调的怎么样编排任务,中间的这一个层是讲整个容器的;纵向有三个部分,最左边是讲怎么样调度的,中间是一个容器的引擎,再后面是容器的运行时



640?wx_fmt=png

   在PouchContainer里面,官方写了很多的Features,这些Features是源于阿里真实的实践。为什么叫容器,容器经典的代表大家可能想到Docker,那Docker之前比如阿里的虚拟机比较流行,从虚拟机过渡到容器,这么大的规模,要适应运维的习惯,要有这样的感知、理念,这个时候容器的技术肯定做很厚。然后再编排,现在我们了解的有k8s,大家觉得很完美,但是那在k8s之前是什么,阿里的体量并不是2015年那一刻才长大的,2015年之前就很大了。

   我们有一个强隔离,为什么说强隔离?我平时和别人探讨问题的思路有两种思路,第一种思路是一上去就排查,把问题解决掉。还有就看自己的系统,拼命的证明不是我的问题在这个时候你强隔离就好说了,问题排查或者是黑盒子,特别是规模很大的时候就需要隔离很强,每个人在我的领域范围内很容易定位。从去年到今年,阿里做了很多混部的宣传,混部没有强隔离也有问题的。

   还有就是为什么要引入P2P?最早P2P是在流媒体里面,为什么又跟容器关联起来了,就是因为互联网里面有很强的思维,如果你慢的话,别人就会忍无可忍的。量少的话工作更快一点。但是规模大的时候没有办法快,在链路上面来,包括业界也是一样,连路瓶颈已经在镜像,由此阿里很自然的就推了P2P加速。

   还有内核的兼容,外界有一个说法——CTO说阿里的商业成功,掩盖了阿里的技术成功,这个话确实有道理。有些业务我们是在2011年的时候拿到2.6版本,现在业务最新的到了4.10,那些老的业务每天服务的人群量很小,不能说这个业务不赚钱或者没有前景。把它下掉了也不行,要给一个缓冲该升级了,这个没有办法一步到位。这个时候要做规模化的升级,或者技术的换代,内核必须要做兼容。

   前面的内容只是把阿里巴巴自己的问题解决了,并没有把这些赋能给社区,所以要做标准化的思考,这个是为未来把好东西回馈社区,所以必须要做标准化的兼容,再反过头来比这张图,就知道这个架构怎么考,为什么要兼容CRI。


代码1

  前面我说最难调的bug是低级错误造成的这个低级错误什么意思呢?



640?wx_fmt=png

Golang一开始很多人用map循环的时候,就受到了指针变量的误用典型的表现就是,一个对象循环之后就会发现中间的所有的值一一样,大家首先想到的是业务逻辑是不是不对,根本不会想到for循环是不是出了问题。后来我们发现我们对语言本身的理解还不是特别的到位,犯了低级的错误,这个PPT下面有详细的代码案例。


代码2



640?wx_fmt=png

    还有map对象的异步序列化,现在看来本质上是我们用法不对,当时理解跟业务场景有关,我们每次筛选服务器有上万的规模,为什么选择这条服务器,要用日志把它记下来后面的优化、迭代要进行消息分析。我们就想到异步做这个事情,把对象存在异步里面去刷盘。但是我们犯了致命的错误,就是Goroute对同一个map执行了读写并发,这样就出现了map读写的冲突。如果知道对象是共享的资源,我们就会加锁,但是这种场景下面我们没有考虑到这个资源也会导致问题。

  业务场景,特别是异步对象持久化的,要有这样一个意识,规模大的时候要考虑这样一个问题。


代码3



640?wx_fmt=png

   我们做的过程当中发现有一个资源泄露的问题,是场景导致的,我们起了很多Goroute,我们有个主任务,主任务会起很多子任务,子任务做一些循环的操作。到阿里云买服务器,阿里云是异步接口,现在起很多任务去买服务器,买完之后把请求发货去,它返还我一个异步的ID,我再请求他状态执行结果。比如说我先请求,完了之后它分配我一些资源,启动这些资源。比如发起申请,然后start,看看这个start是不是完成,start也是有一个过程。比如阿里90分钟再建一个淘宝,要拿到资源,肯定要很快。

   我们为什么会遇到泄露呢?我们主任务要申请200个实例,我会发起200或50个并发的请求到阿里云,但是不可能等200个请求全部完成了,200个来了15个就已经向业务交付,用户的体验就很好。但是这种滚动式的交付,不可能无休止的等待,你得一分钟之内完成。

  除了滚动式的交付,还有总体任务时间的控制,这就涉及到两级的Goroute,第一级Goroute是总任务,子任务也是Goroute,这就会涉及到泄露的问题。假设主任务超时了,就不管子任务,子任务一直在跑就会把资源耗完。

   如果大家对k8s里面的代码,关于定时任务框架有所了解的话,就会有更强的体会。当时我们如果看到这个的话就会借鉴他的方式,就不会采取我们自己造轮子的方式。这个案例最重要的就是以后写Goroute的时候要注意是不是有泄露。


代码4



640?wx_fmt=png

   我们为什么要做Pod打散Pod概念在阿里有几级,第一级是物理层级,第二是机框打散,往上还有核心交换机的打散。比如我有两台服务器,其中有一台服务器挂掉以后,可用性一下子降到了50%,其实创业公司还是可以接受的。但是在阿里如果今天挂了1%,非常多人投诉,所以要求非常高,所以哪怕是1%的流量受到损失了,客服的量就一下子多很多。硬件故障和软件故障是天然的,我们无法规避怎么办呢?所有的服务不要扎堆,物理机,核心交换机上面也不要扎堆这就是Pod打散。

   Pod打散背后还有一层思考,比如说这台机器已经有了相同的Pod,另外一台Pod一定不会往里面部署,有很强的隔离,要么是0,要么是1,这个打散是强制的。但是这里讲的Pod打散不是强的,是尽力交付默认情况下尽可能地打散,如果打散不了,只能在一起,但是一旦有打散的机会,一定给你打散,我们的打散是在得分之后再做的事情,这个跟k8s不一样,k8s是强硬的要打散就一定打散,要不然交付不成功。

   阿里的服务量很大,服务器各个地方都有,其实它可以有一定的容忍度。假设有一万个实例,可以允许1%的实例有波动偏差这个时候Pod就可以满足它之所以能够接受就是因为它的体量太大了,有波动的能力,这个跟我们接触到的k8s完全不一样这是规模化的视角看到的东西。


3.总结


  • 设计层面:整体架构层面优先语言选择

  • 性能层面:任务粒度选择

  • 数据驱动:状态一致性转移为存储一致性

  • 语言理解:Map异步序列化,Map循环指针

  • 多层并发:可控超时

  • 调度算法:规模下pod打散

  • Pouch Container:拥抱开源,回归社区



【提问环节】


提问者1

   提问:我想请教一下,你是利用redis实现数据一致性,数据一致性代替状态移植,这个什么意思?

   李雨前:比如说在k8s里面是面向结果交付的,我要三个实例我把数字改了你就可以扩出来,但是在k8s没有出来之前,阿里已经存在了这些需求,那个时候是面向过程的,什么意思呢?我要拉镜像,我要关报警,这一系列的动作从容器交付周期来说,就是事务的一部分,这个时候是有状态的比如说你想一种方式是每一个状态都带一个ID,一连串的串回去,不可能是并发的每一个状态都是并发的往前做,到一个阶段再并发做另外一个阶段的事情,这个我们理解为是有状态的,这些状态我们做的时候,做不出来很好效果

   为什么要放在redis,我们做之前尝试过一点一点去做,最后我们怎么做呢?每一个状态做完了就丢到redis里面去。下一个任务做的时候就知道上一个任务是什么,只要状态满足了就做下一个事情,最后你后面写很多的任务,做的事情就很简单,上一个状态任务是什么就完了,中间做什么事情,也不需要看到全局的交互关系,这个就放到存储,包括维护的一致性。

   提问:是不是变成异步任务,每完成一个任务就存下来?

   李雨前:可以这样理解。

   

提问者2

   提问:其实Sigma的思路好像跟k8s有点像,我不知道Sigma有没有像k8s这种封装吗?是怎么暴露服务的?

   李雨前:你觉得他们两个很像,那说明你对k8s或者Sigma有不错的理解。实际上包括Sigma,包括mesos,包括Omega,包括yarn这些东西你比较的时候,很多地方有相似之处,从整体上架构来理解,他们确实是相似的,但是我前面提到了Sigma肯定是在K8s出来之前,那个时候就有自己演进的思路和方式,解决的问题都是一样的。第二个问题,我们有没有社区里面大家看到的方式方法,比如说前面讲的pouch最后一项就是标准化的兼容,这已经反映了我们跟社区一些好的东西已经要开始吸收,把社区好的理念拿到阿里的场景进行实践,实践好了就回馈给社区,从这社区里面学到的东西有很多很多,我们也努力向社区做一些反馈。

   

提问者3

   提问:Sigma底层可以用pouch,docker这种吗?

   李雨前:可以的,前面我说了现在出了一个CRI,那个已经兼容了,实现了CRI的协议,你用起来是一样的,你把Pouch起来的话,你用Remoteruntime配一下就可以了,我推荐你看一下pouch官方文档,里面有一些案例。

 


2018年的 Gopher Meetup 将在深圳开启巡回第一站,这一次邀请了很多新的讲师给大家一起交流分享Go的使用经验〜

点击阅读原文报名参加

猜你喜欢

转载自blog.csdn.net/ra681t58cjxsgckj31/article/details/80251714