这个scheduler必须实现下面的接口:
type Scheduler interface { Registered(SchedulerDriver, *mesos.FrameworkID, *mesos.MasterInfo) Reregistered(SchedulerDriver, *mesos.MasterInfo) Disconnected(SchedulerDriver) ResourceOffers(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("Framework registered with Master ", masterInfo) } func (s *MinerScheduler) Reregistered(_ sched.SchedulerDriver, masterInfo *mesos.MasterInfo) { log.Infoln("Framework Re-Registered with Master ", masterInfo) } func (s *MinerScheduler) Disconnected(sched.SchedulerDriver) { log.Infoln("Framework disconnected with Master") }Registered在scheduler 成功向Mesos master注册之后被调用。
Reregistered在scheduler 与Mesos master断开连接并且再次注册时被调用,例如,在master重启的时候。
Disconnected在scheduler 与Mesos master断开连接时被调用。这个在master挂了的时候会发生。
目前为止,这里仅仅在回调函数中打印了日志信息,因为对于一个像这样的简单框架,大多数回调函数可以空在那里。然而,下一个回调函数就是每一个框架的核心,必须要认真的编写。
ResourceOffers在scheduler 从master那里得到一个offer的时候被调用。每一个offer包含一个集群上可以给框架使用的资源列表。资源通常包括CPU、内存、端口和磁盘。一个框架可以使用它提供的一些资源、所有资源或者一点资源都不给用。
针对每一个offer,现在期望聚集所有的提供的资源并决定是否需要发布一个新的server任务或者一个新的worker任务。这里可以向每个offer发送尽可能多的任务以测试最大容量,但是由于开采比特币是依赖CPU的,所以这里每个offer运行一个开采者任务并使用所有可用的CPU资源。
for i, offer := range offers { // … Gather resource being offered and do setup if !s.minerServerRunning && mems >= MemPerServerTask && cpus >= CPUPerServerTask && ports >= 2 { // … Launch a server task since no server is running and we // have resources to launch it. } else if s.minerServerRunning && mems >= MemPerDaemonTask { // … Launch a miner since a server is running and we have mem // to launch one. } }针对每个任务都需要创建一个对应的TaskInfo message ,它包含了运行这个任务需要的信息。
s.tasksLaunched++ taskID = &mesos.TaskID { Value: proto.String("miner-server-" + strconv.Itoa(s.tasksLaunched)), }Task IDs由框架决定,并且每个框架必须是唯一的。
containerType := mesos.ContainerInfo_DOCKER task = &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 { // these arguments will be passed to run_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), }, }TaskInfo message指定了一些关于任务的重要元数据信息,它允许Mesos节点运行Docker容器,特别会指定name、task ID、container information以及一些需要给容器传递的参数。这里也会指定任务需要的资源。
现在TaskInfo已经被构建好,因此任务可以这样运行:
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, &mesos.Filters{RefuseSeconds: proto.Float64(1)})在框架中,需要处理的最后一件事情是当开采者server关闭时会发生什么。这里可以利用StatusUpdate 函数来处理。
在一个任务的生命周期中,针对不同的阶段有不同类型的状态更新。对这个框架来说,想要确保的是如果开采者server由于某种原因失败,系统会Kill所有开采者worker以避免浪费资源。这里是相关的代码:
if strings.Contains(status.GetTaskId().GetValue(), "server") && (status.GetState() == mesos.TaskState_TASK_LOST || status.GetState() == mesos.TaskState_TASK_KILLED || status.GetState() == mesos.TaskState_TASK_FINISHED || status.GetState() == mesos.TaskState_TASK_ERROR || status.GetState() == mesos.TaskState_TASK_FAILED) { s.minerServerRunning = false // kill all tasks for _, taskID := range s.currentDaemonTaskIDs { _, err := driver.KillTask(taskID) if err != nil { log.Errorf("Failed to kill task %s", taskID) } } s.currentDaemonTaskIDs = make([]*mesos.TaskID, 0) }