1.取材
资源调度领域Sigma
主要思路是说资源调度领域的Sigm a,共性的借鉴性的东西,阿里特有的就不讲,更多在Q&A里面。因为 涉及到实践会聚焦工程的问题,所以我会讲一些架构设计与语言的选择,和并发模式下面任务粒度怎么样去选择,还有一些比较大型、综合的解决方案。
2. 工程问题
所有的工程问题,突出 背后的故事,有几个线索,第一个线索就是跟规模化相关,阿里的规模很大,背后就要支撑很大的规模 。另外就是阿里还有很大一块就是上云,包括双十一,很多东西跟云相关。
另外,我们踩过的坑 ,针对这些坑的解决方案跟大家分享一下。
最后就是在Golang当中的 bug,最怕低级错误引起的。
Sigma的业务有两块,一块是对内,一块是对外,对内是所有的bu都接入了Sigma的系统,我们的规模会有100万级别。 Sigma的内部有一个logo,很容易理解,就是数学求和,Sigma是很大的生态系统,需要很多人和系统配合完成。
这个业务分了几个层次,上层业务偏运维,下层业务偏系统, 从接入层,到中心的Master,到Slave,到最底 会有一个Pouch。
这是sigma的架构
可以看出来,颜色有三块,一块是Sigma的,一个是0层,还有一块是关于Fuxi的,通俗的理解就是Sigma管在线,然后是 Fuxi管离线的,中间是一个协调层,这样理解起来就跟 Mesos比较接近一点。这个架构肯定不是最优的,但是它存在有它客观的原因。
我会从Sigma这边抽象的四个案例。我会给大家展示是什么的同时,讲背后我们的故事。
案例1
首先看一下APIServer
我们平常写代码经常会接触到这些事情,在业务领域、调度领域稍稍不同,背后要做发布、扩容、销毁、启停、升级,还有云化,特别是双十一到阿里云买服务器,所以会有混合云需求。我们规模很大,就会要求所有简单的事情 ,怎么样让它在规模化的场景下面依然能够工作。我们调度系统跟运维有关, 核心的内容就是怎么样做到运维友好。还有就是我们做在线的容器服务,肯定要做到高可用,还 有一致性。
解决思路
1. 数据一致性
数据的一致性上面我们是用etcd/redis ,我们会用一个实时+全量的方式做到数据的一致性
2. 状态的一致性
想做到很好是很难的,但是我们把状态的一致性转为存储一致性来做,就会降低处理问题的难度。
3. 简单的
我们没有追求技术看起来非常完美的方案,先把业务推起来能够用就好 了。
4. 高可用-无状态
我们要做到前面说的高可用,有几种方案,一个是多Master 结构,还有无状态,还有就是快速的failover,我们希望做到无状态。
5. 降级-抢占
规模大了以后就会有一个问题,很多人都要资源,这个时候肯定会有一些稀缺的资源,系统要支持降级抢占。
6. 内外兼容:一个团队两块牌子
要做到上云,所有的思考都要考虑到对内对外是一班人马,一个团队会有两个牌子,这是我们整个出发点的思路。
思路之后我们选择由APIServer把前面的发布、扩容等都放在task ,丢到Redis里,底层的Worker消费一些任务,做到无状态,得出来的结论就是架构整体的设计大于语言的选择。
数据一致性里面,如果能做到了实时,数据应该就是一致性的,为什么还要加全量来弥补这个过程?背后一个重要的原因就是物理机的硬件或者是软件会经常出故障,系统多了之后,很难保证故障的事件跟整个链路 100闭环起来,就导致总有一些地方的数据不知道为什么不一致,有很多硬件、软件的故障导致实践的层面做不到 一致性,就只好加一个全量弥补。还有另外一种方案不要全量,我们定时同步,但是有一个问题,时间窗口怎么定, 定几分钟。同步的一瞬间,数据会有一些比对,这个时候加锁,成本维护会多一点。
状态一致性问题转为存储一致性的问题最好的就是转为存储一致性,我们为什么说要面向 etcd,在很大的系统里面有很多 的设备系统,没有办法面向 事务去做,最好是分布式的存储把这些问题兜掉。
为什么说简单够用,后面会有数据给大家看一下,要承载的量很大,没有办法做到95%以上才上线,大家等不及,可能做到85%就要上线了。
还有就是降级,前面我提到了阿里这么多的BU,每一个BU都提了很多的预算,去采购,这就浪费很多资源,这中间有一个共享的buffer,肯定会有抢占,这个在开源里面也会有一些策略。
内外兼容, 如果是创业公司会有体会,一部分东西是基于内部的服务器,还有一些是购买上云的服务,这个时候管理起来,不可能上云的是一套管理方式,自己内部又是一套方式,至少在开发理解上是一模一样的,在设计和架构的时候 要屏蔽很多的差异。基于这样的思考,我们觉得这种方式相对来说会比较好一点,不然尝试换其他的方式,问题解决可能会稍微麻烦一点。
前面讲了设计的架构,接下来讲的 案例是一个真实的场景。
我们在阿里要做一次发布,比如说一开始要拉镜像,就要关闭告警,可能有一些应用要下 线了,还要停止应用。 然后开始容器升级,还要做业务逻辑的检查,检查完之后才把告警打开。不管是上云 还是内部,这套流程肯定是公共的,具体对接的时候又不一样,内部和 云上不一样,云上提供一个SDK,云上的接口的异步性的流转 和内部也不一样,所以要 抽出来。
内部和 阿里云上面都是这样的事情,阿里云比较特别的是什么呢?在before会做一些处理,我们核心的架构是很固定的,在阿里主流的语言是JAVA,我们讨论架构的时候,没有局限于上来就用JAVA,Golang, 或者别的,我们把架构想清楚,之后才选择用什么样的开发语言。
这 是我们数据的表现
这个表现是任务量,数字隐去了,这个大概是 双十一的时候,发布的量非常大,平常的量 比较小,可以看到它有周期性的规则,因为阿里的体量有发布窗口期,那一天整个的发布量大了,会有一个全局的管控。
案例2
调度里面核心的模块之一是Scheduler ,在很多物理机上面选最佳的物理机,把容器布上去,常规的做法就是要有过滤链,过滤完之后会有一个权重的链,最后拿到我想要的机器上去创建这个容器。因为规模 大,一个请求在一个机房里面去扫,这一个机房可能就是万级别的节点,肯定要考虑到性能的优化,所以筛选的规模比较大一点。它是一个链式的,首先要选择哪些服务器作为点,在这一块,我们的想法就是要并发起来,怎么样从工程上面做一个实践。绿色的框是 顺序过程,红色的框是并发
的阶段,或者是关键资源的锁的过程。每一个并发的粒度 看成是一个物理机,相当于每一个物理机去筛选这个是否要,这是一种模式。
另外一种模式是说在整个机器的顶层加一个 glock,是顺序还是 并行都不用管,这个锁加在上面 ,前面的锁是加在下面,两者看起来好像没有太大的区别,测试下来发现性能不一样 。有几个因素,一个是下面要做过滤链或者做 weight,看开销 ;还有每一次筛选的规模, 比如一天下来有十万的请求,每个请求背景物理机是好几万的规模还是几百的规模,最后对整体的性能要求是不一样的。
粗粒度并发的性能比较好,我们选择了第二种场景 。深入的思考的时候,我们发现第一种场景行不通,原因是有些全局的资源没有在一个协 程里面更改,另外一个协程 不能立即可见。
案例3
Golang最近几年非常的火爆,但是在 阿里大的 氛围里面更强调的是JAVA语言,我们引入Golang不可能一上来就大规模,需要有一个成功的案例,或者是小规模实践的过程 。在这种环境下面,我们 想让Golang有一席之地,首选的方式就是 如何做到快速的打磨,跑得很慢的时候,语言构架系统可能就会被淘汰掉了。这是我们上一个版本,有大概五个链路,有一个架子,每一条链路是做的过程当中,一步一步完善出来 。今天我们把链路搬出来跟 k8s
做比较的时候,很多地方都是相通的,但是具体框架的编码实践上面来说是有很大的差异。我们早些时候摸索的这些东西,可能就是业务驱动或者概念驱动,没有真正做到工程或是回馈社区的驱动 。 未来我们换了一种方式,我们可能是以工程的方式驱动,就是回馈社区。
案例4
我讲一下 怎么解读这个图 。 分几层,上面这一层更强调的怎么样编排任务,中间的这一个层是讲整个容器的 ;纵向 有三个部分,最左边是讲怎么样调度的,中间是一个容器的引擎,再后面是容器的运行时 。
在PouchContainer里面,官方写了很多的Features,这些Features是源于阿里真实的实践。为什么叫 富 容器,容器经典的代表大家可能想到Docker,那Docker之前 呢 ? 比如 阿里的虚拟机比较流行,从虚拟机过渡到 容器,这么 大的规模, 需 要适应运维的习惯,要有这样的感知、理念,这个时候容器的技术肯定 要 做很厚。然后再编 排, 现在我们了解的有 k8s ,大家觉得很完美,但是那在 k8s 之前是什么,阿里的体量并不是2015年那一刻才长大的,2015年之前就很大了。
我们有一个强隔离,为什么说强隔离 ?我 们 平时和 别人探讨问题的思路有两种思路,第一种思路是一上去就排查,把问题解决掉。还有就看自己的系统,拼命的证明不是我的问题 。 在这个时候你强隔离就好说了,问题排查或者是黑盒子,特别是规模很大的时候就需要隔离很强,每个人在我的领域范围内很容易定位。从去年到今年,阿里做了很多 混部的宣传,混部 没有强隔离也有问题的。
还有就是 为什么要引入P2 P? 最早P2P是在流媒体里面,为什么又跟容器关联起来了,就是因为互联网里面有很强的思维,如果你慢的话,别人 就会 忍无可忍的。量少的话 比 工作更快一点。但是规模大的时候没有办法快,在链路上面来,包括业界也是一样, 连路 瓶颈已经在 拉 镜像, 由此 阿里很自然的就推了 P2P 加速。
还有内核的兼容,外界有一个说法 —— CTO说阿里的商业成功,掩盖了阿里的技术成功,这个话确实有道理。有些业务我们是在2011年的时候拿到 2.6 版本,现在业务最新的到了4.10,那些老的业务每天服务的人群量很小,不能说这个业务不赚钱或者没有前景 。把它下掉了 也不行,要给一个缓冲 期 , 该升级了, 这个没有办法一步到位。这个时候要做规模化的升级, 或者技术的换代, 内核必须要做兼容。
前面的内容只是把阿里巴巴自己的问题解决了,并没有把这些赋能给社区,所以要做标准化的 思考, 这个是为 了 未来把好东西回馈社区,所以必须要做标准化的兼容,再反过头来比这张图,就知道这个架构怎么 思 考,为什么要兼容CRI。
代码1
前面我说最难调的 bug 是低级错误造成的 , 这个低级错误什么意思呢?
Golang一开始 很多人用 map 循环的时候,就受到了指针变 量的误用 , 典型的表现就是,一个对象循环之后就会发现 中间的所有的值一 模 一样, 大家首先想到的是 业务逻辑是不是不对 ,根本不会想到 for 循环是不是出了问题。 后来我们发现我们对语言本身的理解还不是特别的到位,犯了低级的错误,这个PPT下面有详细的代码案例。
代码2
还有map 对象的异步序列化 ,现在 看来 本质上是我们用法不对,当时理解跟业务场景有关,我们每次筛选服务器有上万的规模,为什么选择这条服务器,要用日志把它记下来 。 后面的优化、迭代要进行消息 分析。 我们就想到 异步做这个事情,把对象存在异步里面去刷盘。但是我们犯了致命的错误,就是Goroute对同一个map执行了读写并发,这样就出现了map读写的冲突。如果知道对象是共享的资源,我们就会加锁,但是这种场景下面我们没有考虑到这个资源也会导致问题。
业务场景,特别是异步对象持久化的,要有这样一个意识,规模大的时候要考虑这样一个问题。
代码3
我们做的过程当中发现有一个资源泄露的问题,是场景导致的,我们起了很多Goroute,我们有个主任务,主任务会起很多子任务,子任务做一些循环的操作。到阿里云买服务器,阿里云是异步接口,现在起很多任务去买服务器,买完之后把请求发货去,它返还我一个异步的ID,我再请求他 状态执行结果。 比如说我先请求,完了之后它分配我一些资源,启动这些资源。比如发起申请,然后start,看看这个start是不是完成,start也是有一个过程。比如阿里90分钟再建一个淘宝,要拿到资源,肯定要很快。
我们为什么会遇到泄露呢?我们主任务要申请200个实例,我会发起200或50个并发的请求到阿里云,但是不可能等200个请求全部完成了,200个来了15个就已经向业务交付,用户的体验就很好。但是这种滚动式的交付,不可能无休止的等待,你得一分钟之内完成。
除了滚动式的交付,还有总体任务时间的控制,这就涉及到两级的Goroute,第一级Goroute是总任务,子任务也是Goroute,这就会涉及到泄露的问题。假设主任务超时了,就不管子任务,子任务一直在跑就会把资源耗完。
如果大家对 k8s 里面的代码,关于定时 任务 的 框架 有所了解的话,就会有更强的体会 。当 时我们 如果看到这个 的话就会借鉴他的方式,就不会采取我们自己造轮子的方式。这个 案例 最重要的就是以后写Goroute的时候要 注意 是不是有泄露。
代码4