Mesos根据官方介绍,它是分布式操作系统的内核。目标是“针对您的数据中心进行编程,就像它是一个单一的资源池”,也就是说,您可以将整个数据中心用作一台计算机。可以说,这个目标是所有自称是DCOS的系统的共同目标。本文从架构和源代码的角度分析Mesos及其周边的框架,看看Mesos是如何实现这个目标的,目前距离这个目标还有多远。最后,我比较了Mesos和Kubernetes这两个受Google的Borg影响的系统的异同。读者对象:对Mesos或分布式系统感兴趣的技术人员设计理念和架构引用Mesos论文中的一句话来说明Mesos的设计理念:定义一个最小的接口,实现跨框架的高效资源共享,否则推控制任务调度框架的执行定义了一个支持跨框架资源共享的最小接口,其他调度和执行任务委托给框架本身进行控制。这句话标志着Mesos并不是要成为一个单栈解决问题的系统,而是要以最小的成本实现资源共享。先来看看官方的架构图:主要组件及概念:Zookeeper主要用于实现Master的选举,支持Master的高可用。MasterMesos的主节点接受Slave和Framework调度器的注册并分配资源。从节点接受主节点发送的任务,并调度执行器执行。Framework比如上图中的Hadoop,MPI就是Framework,包括scheduler和executor。调度器独立运行,启动后向master注册,接受master发送的ResourceOffer消息,决定是否接受。Executor被slave调用来执行framework的任务。Mesos有两个内置的执行器,CommandExecutor(直接调用shell)和DockerExecutor。其他自定义Executor需要提供uri供slave下载。TaskMesos的主要任务其实就是分配资源,然后询问调度器是否可以使用资源来执行任务。调度器将资源绑定到任务上,发送给指定的slave,由master执行。使用批处理可以使任务长期存在或短期存在。另一个官方的资源分配例子:Slave1向Master报告有4个CPU和4GB内存可用。Master向Framework1发送ResourceOffer来描述Slave1有多少资源可用。FrameWork1中的FWScheduler会回复Master。我有两个OneTask需要在Slave1上运行,一个Task需要<2CPUs,1GBmemory=””>,另一个Task需要<1CPU,2GBmemory=””>最后,Master将这些Tasks发送给Slave1。那么,Slave1还有1个CPU和1GB的内存没有使用,分配模块就可以将这些资源提供给Framework2。从这个例子可以看出Mesos的核心工作其实很少,资源管理和分配,任务转发。调度由Framework实现,任务的定义和具体执行也由Framework实现。Mesos的资源分配粒度是基于任务的,但是由于执行者对任务的执行可能是在同一个进程中实现的,资源限制只是一种流量控制机制。它实际上无法控制任务的粒度。看完上面官方的架构描述,你应该大概了解了Mesos的大致架构,但是为什么Mesos要这样,下面我们来详细分析一下。历史沿革让我们回到2009年Mesos被发明的时候。那个时候,Hadoop逐渐成熟,被广泛使用,正在吞噬着大家的服务器。当时Spark也在筹备中。在服务器配置管理领域,Puppet还没有发布1.0,Chef才刚刚出现,“配置管理InfrastructureasCode”的思想才慢慢被接受,Ansible/Salt还没有出现。我们仍然以静态方式管理服务器。当你购买服务器时,你会安排在这些服务器上运行什么服务,配置多少个CPU/内存/磁盘,安装和维护通常使用shell脚本。对服务器进行静态管理,无论是使用shell还是Puppet等工具,一方面是资源浪费,另一方面服务故障恢复和服务迁移需要人工干预,因为此类工具只在部署时进行管理,以及服务进程运行后,不会被部署工具管理。而Mesos则看到了这种做法的弊端,试图实现一个资源共享的平台,提高资源利用率,实现动态运维。这样更容易理解Mesos的做法。Mesos的master相当于Puppet/Salt的master,Mesos的slave相当于Puppet/Salt的agent。两者所做的都是通过master向slave/agent发送指令。执行,不同的是Puppet/Salt执行成功后不关心,而Mesos执行后会维护Master中任务的状态,任务挂掉后可以重启或迁移。同时,Mesos看到Hadoop等分布式系统已经自己实现了调度器和执行器,所以发布了调度器和执行器的具体实现。通过Framework标准的制定,第三方分布式框架实现自身。它只负责将任务转发给slave。Framework的执行器被slave调用来执行任务。也就是说,由于历史时机的问题,Mesos采用了保守的进化策略。资源冲突和隔离机制资源共享带来的首要问题是如何解决资源冲突:cpu/mem这个Mesos默认内置了一个MesosContainerizer,可以通过cgroups和namespaces来限制。网络端口Mesos默认会为每个任务分配一个随机未使用的端口(可以分配多个端口)。应用程序需要从环境变量中获取该端口以供使用。这是约定的规则,不是强制性的。文件系统默认情况下,Mesos文件系统由多个应用程序共享。默认情况下,沙箱目录被分配给每个任务作为工作目录。宿主机上sandbox的目录与taskid相关,不会冲突。生命周期与任务绑定。另外还支持PersistentVolume,用于在应用程序中保存持久化数据,也是通过目录映射实现的。PersistentVolume的生命周期与任务无关,由调度器决定是否重用它。也就是说,Mesos将持久化数据和动态迁移的冲突留给了调度器本身。另外,你可以选择使用DockerContainerizer,通过Docker来隔离文件系统。服务发现和负载均衡Mesos默认不支持服务发现和负载均衡,需要用户自行实现。Mesos上的MarathonFramework提供了marathon-lb,通过监听Marathon事件修改HAProxy,实现动态负载均衡,但只支持通过Marathon部署的应用。容器改进Mesos内置的容器不是基于Image的,而Docker是基于Image的。问题是有些功能需要通过两种方式实现(比如磁盘隔离等)。另外,Docker自身的daemon机制导致Mesos难以直接管理容器进程,因此Mesos也在计划完善内置容器对Image的支持,兼容Docker/Appc的Image,不会使用Docker作为默认容器.详情参见:https://github.com/apache/mesos/blob/master/docs/container-image.md,MESOS-2840。源码架构分析Mesos的核心是用C++写的,主要使用了一个libprocess库,它是一个C++的actor模型库(如果你对actor模型不太了解,可以参考我之前的文章:ThePainofConcurrencyThread、Goroutine、Actor,这个库顺便实现了Option、Nothing、Try、Future、Lambda、Defer,让我充分领略了c++的神奇)。Libprocess基本是参照erlang模型实现的。其中的actors称为进程,每个进程都有独立的ID。为了方便理解,我们将其中的抽象称为actor。具体的actor协作方式如下图所示:在Mesos的master节点中,每一个Framework和Slave都是一个remoteactor。在slave节点上,每个executor都是一个actor,但是内置的executor在同一个进程中,而其他自定义的executor是独立的进程,executor和slave通过进程间通信(网口)进行交互。Mesos通过actor模型简化了分布式系统的调用和并发编程的复杂性。Actor通过消息进行异步通信,只需要知道对方的ID,而无需知道对方和自己是否在同一个节点上。libprocess封装的actormanager知道接收者是本地actor还是远程actor,如果是远程,则通过请求接口转发消息。libprocess也封装了网络层。传输层使用http协议。用户只需要为不同的消息注册不同的处理程序即可。它还支持http的长轮询方式订阅事件。为了提高消息传递和解析的效率,mesos支持两种格式:json和protobuf。这种架构的优点是Mesos消除了对消息队列的需要。一般来说,这种分布式消息分发系统需要消息队列或者中心存储的支持。例如Salt使用ZeroMQ,Kubenetes使用Etcd,Mesos不依赖外部资源支持,仅通过actor模型的容错机制来实现。缺点也是演员本身的缺点。因为消息是异步的,actor需要处理消息丢失和超时逻辑。Mesos不保证消息的可靠传递。提供的投递策略是“at-most-once”,actor需要通过timeout重试机制解决消息丢失的问题。但是任何需要远程调用的分布式系统都需要处理类似的问题。Framework实现分析从上面的分析,我们可以了解到Framework在Mesos中扮演着重要的角色。如果你想自己开发一个分布式系统并打算在Mesos中运行,你需要考虑自己实现一个Framework。Mesos提供了框架基础库,第三方只需要实现调度器和执行器接口即可。基础库用c++实现,java版本通过jni提供,python版本通过python的native方法提供。golang版本独立开发,不依赖c++库。代码结构更加优雅。想用go实现actor的可以参考一下。这个Framework基础库(SchedulerDriver和ExecutorDriver的实现)主要实现了我们前面提到的actor模型,与master和slave进行交互,收到消息后回调用户自定义的scheduler和executor。下面是java的Scheduler接口:publicinterfaceScheduler{voidregistered(SchedulerDriverdriver,FrameworkIDframeworkId,MasterInfomasterInfo);voidreregistered(SchedulerDriverdriver,MasterInfomasterInfo);//这是最重要的方法,当系统有空闲资源时,会询问Scheduler,//是否接受或拒绝要约。如果接受,需要同时使用Offer封装任务信息,调用驱动执行。voidresourceOffers(SchedulerDriverdriver,List
