通過Mesos、Docker和Go,使用300行代碼創建一個分布式系統

2020-12-20 CSDN技術社區

【編者按】時下,對於大部分IT玩家來說,Docker和Mesos都是熟悉和陌生的:熟悉在於這兩個詞無疑已成為大家討論的焦點,而陌生在於這兩個技術並未在生產環境得到廣泛使用,因此很多人仍然不知道它們究竟有什麼優勢,或者能幹什麼。近日,John Walter在Dzone上撰文Creating a Distributed System in 300 Lines With Mesos, Docker, and Go,講述了Mesos、Docker和Go配合帶來的強大破壞力,由OneAPM工程師翻譯。

以下為譯文

構建一個分布式系統是很困難的。它需要可擴展性、容錯性、高可用性、一致性、可伸縮以及高效。為了達到這些目的,分布式系統需要很多複雜的組件以一種複雜的方式協同工作。例如,Apache Hadoop在大型集群上並行處理TB級別的數據集時,需要依賴有著高容錯的文件系統(HDFS)來達到高吞吐量。

在之前,每一個新的分布式系統,例如Hadoop和Cassandra,都需要構建自己的底層架構,包括消息處理、存儲、網絡、容錯性和可伸縮性。慶幸的是,像Apache Mesos這樣的系統,通過給分布式系統的關鍵構建模塊提供類似作業系統的管理服務,簡化了構建和管理分布式系統的任務。Mesos抽離了CPU、存儲和其它計算資源,因此開發者開發分布式應用程式時能夠將整個數據中心集群當做一臺巨型機對待。

構建在Mesos上的應用程式被稱為框架,它們能解決很多問題:Apache Spark,一種流行的集群式數據分析工具;Chronos,一個類似cron的具有容錯性的分布式scheduler,這是兩個構建在Mesos上的框架的例子。構建框架可以使用多種語言,包括C++,Go,Python,Java,Haskell和 Scala。

在分布式系統用例上,比特幣開採就是一個很好的例子。比特幣將為生成 acceptable hash 的挑戰轉為驗證一塊事務的可靠性。可能需要幾十年,單臺筆記本電腦挖一塊可能需要花費超過150年。結果是,有許多的「採礦池」允許採礦者將他們的計算資源聯合起來以加快挖礦速度。Mesosphere的一個實習生,Derek,寫了一個比特幣開採框架(https://github.com/derekchiang/Mesos-Bitcoin-Miner),利用集群資源的優勢來做同樣的事情。在接下來的內容中,會以他的代碼為例。

1個Mesos框架有1個scheduler 和1個executor組成。scheduler 和Mesos master通信並決定運行什麼任務,而executor 運行在slaves上面,執行實際任務。大多數的框架實現了自己的scheduler,並使用1個由Mesos提供的標準executors。當然,框架也可以自己定製executor。在這個例子中即會編寫定製的scheduler,並使用標準命令執行器(executor)運行包含我們比特幣服務的Docker鏡像。

對這裡的scheduler來說,需要運行的有兩種任務——one miner server task and multiple miner worker tasks。server會和一個比特幣採礦池通信,並給每個worker分配blocks。Worker會努力工作,即開採比特幣。

任務實際上被封裝在executor框架中,因此任務運行意味著告訴Mesos master在其中一個slave上面啟動一個executor。由於這裡使用的是標準命令執行器(executor),因此可以指定任務是二進位可執行文件、bash腳本或者其他命令。由於Mesos支持Docker,因此在本例中將使用可執行的Docker鏡像。Docker是這樣一種技術,它允許你將應用程式和它運行時需要的依賴一起打包。

為了在Mesos中使用Docker鏡像,這裡需要在Docker registry中註冊它們的名稱:

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

然後定義一個常量,指定每個任務所需資源:

const ( MemPerDaemonTask = 128 // mining shouldn't be memory-intensive MemPerServerTask = 256 CPUPerServerTask = 1 // a miner server does not use much CPU)

現在定義一個真正的scheduler,對其跟蹤,並確保其正確運行需要的狀態:

type MinerScheduler struct { // bitcoind RPC credentials bitcoindAddr string rpcUser string rpcPass string // mutable state minerServerRunning bool minerServerHostname string minerServerPort int // the port that miner daemons // connect to // unique task ids tasksLaunched int currentDaemonTaskIDs []*mesos.TaskID}

這個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_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 { // 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)}

萬事大吉!通過努力,這裡在Apache Mesos上建立一個正常工作的分布式比特幣開採框架,它只用了大約300行GO代碼。這證明了使用Mesos 框架的API編寫分布式系統是多麼快速和簡單。

原文連結:Creating a Distributed System in 300 Lines With Mesos, Docker, and Go (責編/仲浩)


相關焦點

  • 使用Gitea+Drone來搭建自己的輕量級CI/CD自動構建平臺
    我們使用Gitea搭建了自己的Git版本控制系統,可以用來管理自己的代碼還需要一個自動構建工具來解放生產力,這裡我推薦使用Drone來搭建CI/CD持續集成,持續部署平臺 為什麼選用Gitea+Drone呢?
  • Docker 1.12實踐:Docker Service、Stack與分布式應用捆綁包
    Docker Stack與分布式應用捆綁包:分布式應用捆綁包,或者簡稱DAB,是一種多服務可分發鏡像格式。在後文中我們會進一步討論。截至目前,大家已經可以選定一個Dockerfile,並利用docker build命令由此創建鏡像。使用docker run命令則可啟動容器。這條命令亦能夠輕鬆同時啟動多套容器。
  • Docker 入門教程
    它能讓你將運行環境和配置放在代碼匯總然後部署, 同一個Docker的配置可以在不同的環境環境中使用, 這樣就降低了硬體要求和應用環境之間耦合度.2.代碼流水線管理代碼從開發者的機器到最終在生產環境上的部署, 需要經過很多的中堅環境.
  • 雲計算核心技術Docker教程:Docker多階段構建
    為了編寫一個真正有效的Dockerfile,傳統上,您需要使用Shell技巧和其他邏輯來使各層儘可能小,並確保每一層都具有上一層所需的工件,而沒有其他任何東西。實際上,通常只有一個Dockerfile用於開發(包含構建應用程式所需的一切),而精簡的Dockerfile用於生產時,僅包含您的應用程式以及運行該應用程式所需的內容。這被稱為「構建器模式」。
  • docker下高並發和高可用之docker swarm使用
    ,操作步驟參考Linux下安裝和使用Docker安裝完,使用命令sudo systemctl start docker啟動docker,再通過命令這樣就創建了一個Worker的節點,即將當前虛擬機節點關聯到Manager節點輸入命令docker info在兩臺虛擬機上分別查看Manager和Worker兩個節點的信息情況
  • Docker常用命令就該這麼學!
    容器是完全使用沙箱機制,相互之間不會有任何接口。Docker 是一個用於開發,交付和運行應用程式的開放平臺。Docker 使您能夠將應用程式與基礎架構分開,從而可以快速交付軟體。 藉助 Docker,您可以與管理應用程式相同的方式來管理基礎架構。通過利用 Docker 的方法來快速交付,測試和部署代碼,您可以大大減少編寫代碼和在生產環境中運行代碼之間的延遲。
  • 大數據常用組件介紹(無代碼入門)
    這幾年大數據和人工智慧(AI)、AR、VR都比較火爆,我呢也有幸加入了一家有大數據相關業務的公司,在這裡給大家分享下期間所學,一方面做下自我總結,另一方面也為後來者提供個參考。本文儘可能不涉及代碼,只使用通俗的語言對一些軟體組件進行介紹。
  • 雲計算核心技術Docker教程:Docker Swarm 使用
    Docker Swarm 提供了標準的 Docker API,所有任何已經與 Docker 守護程序通信的工具都可以使用 Swarm 輕鬆地擴展到多個主機。如下圖所示,swarm 集群由管理節點(manager)和工作節點(work node)構成。
  • [Go 語言教程] Go 語言簡介
    解析型語言——原始碼是先翻譯為中間代碼,然後由解析器對代碼進行解釋執行。編譯型語言——原始碼編譯生成機器語言,然後由機器直接執行機器碼即可執行。百度,阿里巴巴,oppo,vivo等微服務的開發模式下Go語言是新寵4 Go 擅長領域服務開發,web的api開發,分布式服務集群的開發容器docker是go開源的產品,k8s等這些都是基於go語言的對高並發
  • 雲計算核心技術Docker教程:Docker容器使用
    docker 客戶端非常簡單 ,我們可以直接輸入 docker 命令來查看到 Docker 客戶端的所有命令選項。可以通過命令 docker command --help 更深入的了解指定的 Docker 命令使用方法。
  • 不用Docker也能構建容器的4種方法
    Podman和buildah組合——由RedHat/IBM使用他們自己的OSS工具鏈來生成OCI鏡像。Podman是無守護進程和無根的,但最後仍然需要掛載文件系統以及使用UNIX套接字。pouch——來自阿里巴巴,被標榜為「高效的企業級容器引擎」。
  • CoreOS實踐指南(七):Docker容器管理服務
    但它在伺服器的系統上還不是那麼普及,至少與你手上的智慧型手機系統相比。至今在伺服器系統上流行的安裝軟體方式依然是編譯原始碼、手工的安裝包或各種包管理工具,雖然包管理工具的出現解決了應用軟體安裝、卸載以及自身依賴等諸多問題,卻無法很好的解決軟體之間的依賴衝突。而早在Docker誕生以前,「沙盒」的概念已經被普遍使用在Android、iOS等主流的手機系統中了。
  • 不用Docker也能構建容器的4種方法
    Podman和buildah組合——由RedHat/IBM使用他們自己的OSS工具鏈來生成OCI鏡像。Podman是無守護進程和無根的,但最後仍然需要掛載文件系統以及使用UNIX套接字。pouch——來自阿里巴巴,被標榜為「高效的企業級容器引擎」。
  • 萬字長文:編寫Dockerfiles最佳實踐
    /appRUN make /appCMD python/app/app.py每條指令創建一個層:當你運行一個鏡像生成一個容器,在底層的頂部添加一個可寫層(容器層)。對正在運行容器所做的所有更改,比如寫文件,修改已存在的文件,和刪除文件,都被寫入這個可寫空口層。
  • 小米開源監控系統Open-Falcon國際化推進 0.2版本英文文檔發布
    ,現覆蓋小米、美團、快網、滴滴等300多家企業,已經成為國內最流行的監控系統之一。為了能夠讓更多人使用和參與Open-Falcon項目,小米近期完成了英文文檔0.2版本的翻譯工作,進一步推進了Open-Falcon的國際化。
  • 30分鐘帶你了解Web工程師必知的Docker知識
    本文主要會介紹Docker的基礎知識和應用領域,並通過實際部署一個web項目來帶大家了解Docker的使用方式。作為一名前端工程師,為什麼要學習Docker呢?做過B端系統或有Saas系統開發經驗的朋友也許會清楚其中的繁瑣,為了客戶安全和私有化往往需要研發人員給企業配置和部署獨立的Web應用,如果你有上百家客戶上千家客戶,我們一個個部署顯然是效率極低的,而且不能保證環境的一致性和穩定性,因為一旦我們的Web系統使用的環境或者包更新了,應用很可能不能正常Work,這種情況下採用Docker容器化技術可以很好的解決這一問題。
  • Kubernetes決定棄用Docker,到底會影響到誰?
    開發者和企業會受到什麼樣到影響?近幾年,Kubernetes 已經成為自有機房、雲上廣泛使用的容器編排方案,最廣泛的使用方式是 Kubernetes+Docker。從 DevOps 人員的角度,一面用 kubctl 命令、k8s API 來操作集群,一面在單機用 Docker 命令來管理鏡像、運行鏡像。
  • SpringBoot+GitLab+Docker+Jenkins實現持續集成上
    本文通過下圖的模式進行持續集成的方案:開發人員開發代碼,推送到Git伺服器中當Git伺服器中的代碼發生變化時,會觸發配置在Git伺服器中的鉤子地址,通知到JenkinsJenkinsdocker start docker-registry4. GitLabGitLab是一個利用 Ruby on Rails 開發的開源應用程式,實現一個自託管的Git項目倉庫,可通過Web界面進行訪問公開的或者私人項目安裝。
  • Docker入門的第一本書,我選擇它!
    閱讀路線本書分為九章,下面我們會通過向大家展示一些書中的知識點來幫助大家更好的了解Docker第一章主要是向讀者簡單介紹Docker,Docker 是一個能夠把開發的應用程式自動部署到容器的開源引擎。● 快速、高效的開發生命周期Docker 的目標之一就是縮短代碼從開發、測試到部署、上線運行的周期,讓你的應用程式具備可移植性,易於構建,並易於協作。● 鼓勵使用面向服務的架構Docker 還鼓勵面向服務的架構和微服務架構。Docker 推薦單個容器只運行一個應用程式或進程。
  • 分布式文件系統 SeaweedFS 2.00 發布
    SeaweedFS 是一個簡單且高度可擴展的分布式文件系統,包含兩部分:存儲數十億的文件;快速為文件服務。SeaweedFS 作為支持全 POSIX 文件系統語義替代,Seaweed-FS 選擇僅實現 key-file 的映射,類似 "NoSQL",也可以說是 "NoFS"。