這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
時下,對於大部分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編寫分布式系統是多麼快速和簡單。