当前位置: 首页 > 科技观察

震惊!用300行代码创建分布式系统

时间:2023-03-19 15:48:52 科技观察

构建分布式系统很难。它需要可扩展性、容错性、高可用性、一致性、可扩展性和效率。为了实现这些目标,分布式系统需要许多复杂的组件以复杂的方式协同工作。例如,ApacheHadoop在大型集群上并行处理TB级数据集时,需要依赖高容错的文件系统(HDFS)来实现高吞吐量。以前,每一个新的分布式系统,比如Hadoop和Cassandra,都需要构建自己的底层架构,包括消息处理、存储、网络、容错和可扩展性。幸运的是,像ApacheMesos这样的系统通过为分布式系统的关键构建块提供类似操作系统的管理服务,简化了构建和管理分布式系统的任务。Mesos抽象出CPU、存储和其他计算资源,因此开发人员在开发分布式应用程序时可以将整个数据中心集群视为一台超级计算机。基于Mesos构建的应用程序称为框架,它们可以解决许多问题:ApacheSpark,一种流行的集群数据分析工具;Chronos,一个类似cron的容错分布式调度器,它是两个建立在Mesos上的框架实例。构建框架以多种语言提供,包括C++、Go、Python、Java、Haskell和Scala。比特币挖矿是分布式系统用例的一个很好的例子。比特币将把生成可接受哈希的挑战转化为验证交易块的真实性。可能需要几十年,一台笔记本电脑挖出一个区块可能需要150多年。因此,出现了许多“矿池”,让矿工们可以集中他们的计算资源来加速挖矿。Mesosphere的一名实习生Derek编写了一个比特币挖掘框架,该框架利用集群资源来做同样的事情。下面以他的代码为例。Mesos框架由一个调度器和一个执行器组成。调度器与Mesosmaster通信并决定运行哪些任务,而执行器在从属上运行以执行实际任务。大多数框架实现自己的调度器并使用Mesos提供的标准执行器之一。当然,框架也可以自己定制执行器。在此示例中,编写了一个自定义调度程序,并使用一个标准命令执行程序来运行包含我们的比特币服务的Docker映像。对于这里的调度器,需要运行两种类型的任务——单矿服务器任务和多矿服务器任务。服务器与比特币矿池通信并为每个“工人”分配区块。“工人”会努力工作,即开采比特币。任务实际上封装在执行器框架中,因此运行任务意味着告诉Mesos主节点在其中一个从节点上启动一个执行器。由于此处使用标准命令执行器,因此可以指定任务是二进制可执行文件、bash脚本还是其他命令。由于Mesos支持Docker,因此本示例将使用可执行的Docker映像。Docker是一种允许您打包应用程序及其运行所需的依赖项的技术。为了在Mesos中使用Docker镜像,需要在Docker注册表中注册它们的名称:const(MinerServerDockerImage="derekchiang/p2pool"MinerDaemonDockerImage="derekchiang/cpuminer")然后定义一个常量来指定每个任务所需的资源:const(MemPerDaemonTask=128//miningshouldn'tbememory-intensiveMemPerServerTask=256CPUPerServerTask=1//aminerserverdoesnotusemuchCPU)现在定义一个真正的scheduler,对其跟踪,并确保其正确运行需要的状态:typeMinerSchedulerstruct{//bitcoindRPCcredentialsbitcoindAddrstringrpcUserstringrpcPassstring//mutablestateminerServerRunningboolminerServerHostnamestringminerServerPortint//theportthatminerdaemons//connectto//uniquetaskidstasksLaunchedintcurrentDaemonTaskIDs[]*mesos.TaskID}此调度程序必须实现以下接口:SchedulerDriver)(SchedulerDriver,[]*mesos.Offer)OfferRescinded(SchedulerDriver,*mesos.OfferID)StatusUpdate(SchedulerDriver,*mesos.TaskStatus)FrameworkMessage(SchedulerDriver,*mesos.ExecutorID,*mesos.SlaveID,string)SlaveLost(SchedulerDriver,*mesos.SlaveID)ExecutorLost(SchedulerDriver,*mesos.ExecutorID,*mesos.SlaveID,int)Error(SchedulerDriver,string)}现在在一起看一个回调函数:func(s*MinerScheduler)Registered(_sched.SchedulerDriver,frameworkId*mesos.FrameworkID,masterInfo*mesos.MasterInfo){log.Infoln("FrameworkregisteredwithMaster",masterInfo)}func(s*MinerScheduler)Registered(_sched.SchedulerDriver,masterInfo*mesos.MasterInfo){log.Infoln("FrameworkRe-RegisteredwithMaster",masterInfo)}func(s*MinerScheduler)Disconnected(sched.SchedulerDriver){log.Infoln("FrameworkdisconnectedwithMaster")}注册成功scheduler在Mesosmaster注册后调用。Reregistered在调度程序与Mesosmaster断开连接并再次注册时调用,例如,当master重新启动时。当调度程序与Mesosmaster断开连接时,将调用Disconnected。当master挂起时会发生这种情况。到目前为止,回调函数中只打印了日志消息,因为对于像这样的简单框架,大多数回调函数都可以留空。然而,下一个回调函数是每个框架的核心,必须仔细编写。ResourceOffers在调度器从master那里得到一个offer时被调用。每个报价都包含集群上框架可用的资源列表。资源通常包括CPU、内存、端口和磁盘。框架可以使用它提供的部分、全部或不使用任何资源。对于每个offer,现在期望聚合所有提供的资源,并决定是发布一个新的servertask还是一个新的workertask。在这里,您可以向每个报价发送任意数量的任务以测试最大容量,但由于挖掘比特币依赖于CPU,因此每个报价都运行一个矿工任务并使用所有可用的CPU资源。fori,offer:=rangeoffers{//…Gatherresourcebeingofferedanddosetupif!s.minerServerRunning&&mems>=MemPerServerTask&&cpus>=CPUPerServerTask&&ports>=2{//…Launchaservertasksincenoserverisrunningandwe//haveresourcestolaunchit.}elseifs.minerServerRunning&&mems>=MemPerDaemonTask{//…Launchaminersinceaserverisrunningandwehavemem//tolaunchone.}对于每个任务,需要创建一个相应的TaskInfomessage,其中包含运行此任务所需的信息。s.tasksLaunched++taskID=&mesos.TaskID{Value:proto.String("miner-server-"+strconv.Itoa(s.tasksLaunched)),}TaskID由框架决定,每个框架必须是唯一的。containerType:=mesos.ContainerInfo_DOCKERtask=&mesos.TaskInfo{Name:proto.String("task-"+taskID.GetValue()),TaskId:taskID,SlaveId:offer.SlaveId,Container:&mesos.ContainerInfo{Type:&containerType,Docker:&mesos.ContainerInfo_DockerInfo{Image:proto.String(MinerServerDockerImage),},},Command:&mesos.CommandInfo{Shell:proto.Bool(false),Arguments:[]string{//theseargumentswillbapassedtorun_p2pool.py"--bitcoind-address",s.bitcoindAddr,"--p2pool-port",strconv.Itoa(int(p2poolPort)),"-w??",strconv.Itoa(int(workerPort)),s.rpcUser,s.rpcPass,},},Resources:[]*mesos.Resource{util.NewScalarResource("cpus",CPUPerServerTask),util.NewScalarResource("mem",MemPerServerTask),},}TaskInfomessage指定了任务的一些重要的元数据信息,它允许Mesos节点运行Docker容器,特别是指定name、taskID、containerinformation和一些需要传递给容器的参数。任务所需的资源也在这里指定。现在TaskInfo已构建,因此任务可以像这样运行:,需要最后处理的是当矿工服务器宕机时发生的事情。这里可以使用StatusUpdate函数来处理。在任务的生命周期中,不同阶段有不同类型的状态更新。对于这个框架,你要保证的是,如果矿工服务器因为某种原因发生故障,系统会杀死所有的矿工,避免资源浪费。下面是相关代码:status.GetState()==mesos.TaskState_TASK_FINISHED||status.GetState()==mesos.TaskState_TASK_ERROR||status.GetState()==mesos.TaskState_TASK_FAILED){s.minerServerRunning=false//killalltasksfor_,taskID:=ranges.currentDaemonTaskIDs{_,err:=driver.KillTask??(taskID)iferr!=nil{log.Errorf("Failedtokilltask%s",taskID)}}s.currentDaemonTaskIDs=make([]*mesos.TaskID,0)}一切都是美好的!经过努力,这里是一个基于ApacheMesos构建的工作分布式比特币挖矿框架,它只使用了大约300行GO代码。这演示了使用Mesos框架的API编写分布式系统是多么快速和容易。