使用 Mesos、Docker 和 Go 在 300 行代码中创建分布式系统

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

构建分布式系统很难。它们需要具有可扩展性、容错性、高可用性、一致性、安全性、弹性和高效性。为了实现这些特性,分布式系统需要许多复杂的组件以复杂的方式协同工作。例如,Apache Hadoop 依赖于高容错文件系统 (HDFS) 来提供高吞吐量,以便在大型集群上并行处理数 TB 的数据集。

过去,每个新的分布式系统,例如 Hadoop 或 Cassandra,都必须为消息传递、存储、网络、容错和弹性构建自己的底层结构。幸运的是,像 Apache Mesos (及其商业化版本 Mesosphere DCOS )这样的系统通过为分布式系统的关键构建块提供类似操作系统的原语,简化了构建和管理分布式系统的任务。 Mesos 抽象出 CPU、内存、存储和其他计算资源,以便开发人员可以编写分布式应用程序,就好像他们的数据中心集群是一台巨型机器一样。

Mesos 的应用程序称为框架,可以用来解决各种问题。 Apache Spark 是一种流行的通用集群计算工具,用于数据分析,而 Chronos 是一种分布式容错的类似 cron 的调度程序,它们是构建在 Mesos 之上的框架的两个示例。框架可以用多种语言构建,包括 C++、Go、Python、Java、Haskell 和 Scala。

比特币挖矿是需要分布式解决方案的一个主要例子。比特币改变了生成可接受哈希值以验证交易块真实性的难度。几十年后,一台笔记本电脑需要 150 多年才能开采一个区块。因此,现在出现了“矿池”,允许矿工将他们的计算资源组合在一起,更快地挖矿。我们的一名实习生 Derek 编写了一个 比特币挖掘框架 ,该框架利用集群资源来完成同样的工作。在本文的其余部分,我们将浏览 他的代码

Mesos 框架由一个调度器和一个执行器组成。调度程序与 Mesos 主节点通​​信并决定启动什么任务,而执行程序在从节点上运行以实际执行预期任务。大多数框架实现自己的调度器并使用 Mesos 提供的标准执行器之一。框架也可以实现自己的自定义执行器。在这种情况下,我们将编写自己的调度程序并使用标准命令执行器来启动包含我们的比特币服务的 Docker 映像。

对于我们的调度程序,我们将要启动两种任务:一个矿工服务器任务和多个矿工工作任务。矿工服务器与比特币矿池通信,并为每个矿工分配区块。矿工做艰苦的工作,就是开采比特币。

任务实际上封装在框架执行器中,因此启动任务意味着告诉 Mesos 主节点在它的一个从节点上启动一个执行器。由于我们将使用标准命令执行器,我们可以将任务指定为二进制可执行文件、bash 脚本或其他命令。在我们的例子中,由于 Mesos 原生支持 Docker,我们将使用可执行的 Docker 镜像。 Docker 是一种允许您将应用程序与其所需的所有运行时依赖项打包在一起的技术。

要在 Mesos 中使用 Docker 镜像,您只需要在 Docker 注册表中提供它们的名称:


 const (
    MinerServerDockerImage = "derekchiang/p2pool"
    MinerDaemonDockerImage = "derekchiang/cpuminer"
)

然后,我们定义了一些常量来指定每个任务的资源需求:


 const (
    MinerServerDockerImage = "derekchiang/p2pool"
    MinerDaemonDockerImage = "derekchiang/cpuminer"
)

现在我们定义实际的调度程序。调度程序应该跟踪正确运行所需的状态。


 const (
    MinerServerDockerImage = "derekchiang/p2pool"
    MinerDaemonDockerImage = "derekchiang/cpuminer"
)

调度程序必须实现以下接口:


 const (
    MinerServerDockerImage = "derekchiang/p2pool"
    MinerDaemonDockerImage = "derekchiang/cpuminer"
)

现在让我们看看回调:


 const (
    MinerServerDockerImage = "derekchiang/p2pool"
    MinerDaemonDockerImage = "derekchiang/cpuminer"
)

当调度程序成功注册到 Mesos 管理节点时,将调用 “已注册”

`Reregistered` 在调度器与 Mesos master 断开连接时调用,然后再次注册,例如,当 master 重启时。

`Disconnected` 当调度程序与 Mesos 管理节点断开连接时被调用。这可能会在 master 宕机时发生。

到目前为止,我们只在回调函数中打印日志消息,因为对于像这样的简单框架,大多数回调都可以有效地留空。然而,下一个回调是每个框架的核心,需要小心编写。

`ResourceOffers` 在调度器收到来自 master 的提议时被调用。每个提议都包含可供框架在集群上使用的资源列表。资源通常包括 CPU、内存、端口和磁盘。框架可以使用部分、全部或不使用它所提供的资源。

对于每个报价,我们都希望收集所提供的资源,并决定我们是要启动服务器任务还是工作任务。您可以在每个报价中启动尽可能多的任务,但由于比特币挖掘依赖于 CPU,因此我们使用所有可用的 CPU 资源为每个报价启动一个矿工任务。


 const (
    MinerServerDockerImage = "derekchiang/p2pool"
    MinerDaemonDockerImage = "derekchiang/cpuminer"
)

对于每个任务,我们需要创建一个相应的 TaskInfo 消息,其中包含启动该任务所需的信息。


 const (
    MinerServerDockerImage = "derekchiang/p2pool"
    MinerDaemonDockerImage = "derekchiang/cpuminer"
)

任务 ID 由框架决定,每个框架应该是唯一的。


 const (
    MinerServerDockerImage = "derekchiang/p2pool"
    MinerDaemonDockerImage = "derekchiang/cpuminer"
)

TaskInfo 消息指定了一些关于允许 Mesos 节点启动 Docker 容器的任务的重要元数据。具体来说,我们指定名称、任务 ID、容器信息和要传递给容器的参数。我们还指定了任务所需的资源。

现在我们已经构建了我们的 TaskInfo,我们可以使用以下方法启动我们的任务:


 const (
    MinerServerDockerImage = "derekchiang/p2pool"
    MinerDaemonDockerImage = "derekchiang/cpuminer"
)

现在我们正在启动任务!我们需要在我们的框架中处理的最后一件事是当矿工服务器关闭时会发生什么。我们可以使用 StatusUpdate 函数来做到这一点。

有几种类型的状态更新,对应于任务生命周期中的不同阶段。对于我们的框架,我们要确保如果矿工服务器因任何原因发生故障,我们会杀死所有矿工以避免浪费资源。这是相关代码:


 const (
    MinerServerDockerImage = "derekchiang/p2pool"
    MinerDaemonDockerImage = "derekchiang/cpuminer"
)

就是这样!我们在 Apache Mesos 上有一个可用的分布式比特币挖掘框架(大约)300 行 Go。这演示了如何使用 Mesos 框架 API 快速直接地编写分布式系统。我们鼓励您尝试编写自己的框架。如果您正在寻找灵感,请查看分布式网络爬虫 RENDLER 和变位词查找器 ANAGRAMMER

为本报告做出贡献的 Mesosphere 工程师包括 Cody Roseborough、Lily Chen、Neeral Dodhia、Derek Chiang、Luke Leslie 和 Brendan Chang。