Cloud Foundry中基於Master/Slave機制的Service Gateway——解決Service Gateway單點故障問題

Cloud Foundry作爲業界最出色的PaaS平臺之一,給廣大的互聯網開發者和消費者提供出色的體驗。自Cloud Foundry開源以來,有關Cloud Foundry的研究越來越多,這也很好的支持着Cloud Foundry的生態系統。但是作爲一個平臺,Cloud Foundry仍然會存在一些可靠性,擴展性方面的不足,這也吸引着衆多的Cloud Foundry愛好者對其進行更多更深入的研究。


本文主要講述Cloud Foundry中Service Gateway的運行機制,Service Gateway存在的單點故障問題,以及解決單點問題的Master/Slave Service Gateway機制。


1. Service Gateway運行機制

關於Cloud Foundry中Service Gateway的介紹,可以參考我之前的博文:Cloud Foundry Service Gateway 源碼分析

介紹該組件運行機制的時候,我希望使用Service Gateway的最常用的功能provision a service 和 bind a service 來簡要闡述,雖然不能代表所有的功能,但是這兩個功能可以讓我們對Service Gateway運行機制瞭解個大概。


1.1 Service Gateway的啓動

以上提及的博文中已經介紹得很多,關於啓動,需要強調的是fetch_handles的功能,即Service Gateway向Cloud Controller請求獲得service instance的信息,並存儲在內存中,後面會講到在provision a service 的時候會用到該內存中的信息;還有send_heartbeat的功能,即Service Gateway向Cloud Controller發送心跳信息,證明自己的存活狀態(另外Cloud Controller也會通過這個心跳信息來存儲該類型Service Gateway)。


1.2 provision a service

provision a service 的流程很簡單明瞭,主要實現創建一個服務實例,無非是Cloud Controller接收用戶請求,使用HTTP方式發送給Service Gateway,Service Gateway接收並處理請求後通過NATS向Service Node發送provision請求,Service Node接收到請求到provision完畢後,通過NATS將結果返回給Service Gateway,Service Gateway將結果備份一份以後,通過HTTP方式返回給Cloud Controller,最後Cloud Controller收到結果信息,將其持久化至數據庫,並對用戶做迴應。這裏需要注意的是:首先,關於provision出來的service instance的信息最終會被持久化到Cloud Controller的postgres數據庫中;然後,關於這些信息,Service Gateway會備份一份(存儲在內存中)。這兩點在後文中會顯得尤爲重要。


1.3 bind a service

bind a service 主要是實現爲一個應用綁定一個服務實例。流程與通信方式和provision a service 大同小異,不同的是,在Service Gateway在接收到Cloud Controller的時候,會從自己關於service的備份信息中找出來相應的service instance,然後給該instance發送請求。


2. Service Gateway的單點故障問題

在Cloud Foundry中,同種類型service的Service Gateway只有一個,因此在運行過程中,肯定會出現單點故障問題。一旦Service Gateway所在節點宕機,那麼該Service Gateway變得不可用,所以關於該service的provision和bind等衆多請求都將得不到響應。需要注意的是,對於已經成功bind服務的應用來講,是不受影響的,因爲app可以直接通過URL訪問Service Node,而不用經過Service Gateway。


由於目前的Cloud Foundry缺乏對於組件的狀態監控機制,所以Cloud Foundry對於Service Gateway的宕機不會採取任何措施,因此Cloud Foundry的管理員也不會被告知Service Gateway的故障,從而導致該類型service的不可用。當Cloud Foundry的用戶感受到該類型service不可用,並且向管理員反饋時,管理員纔會去後臺去查看Service Gateway的狀態,發現狀態爲關閉狀態,從而進行人爲的啓動。在這一漫長的過程中,該類型的service均處於不可用狀態,大大影響Cloud Foundry的可用性。


在這樣的情況下,最理想的解決方案自然可以使得Cloud Foundry中關於某種類型的service可以具有多個Service Gateway ,如此一來,在一個Service Gateway宕機的時候,還可以有其他的Service Gateway繼續工作。通過以上的假設,在實際過程中,我曾經嘗試過兩種方案:Master/Slave Gateway 以及Multi Gateways。以下是兩個方案的簡述:

  1. Master/Salve Gateway: 該方案在執行過程中,只有一個Service Gateway接收到Cloud Controller的請求,並做處理;只有當一個Service Gateway宕機的時候,Cloud Controller纔會將請求發給另一個請求。看到這裏,肯定會有一些問題:那就是Cloud Controller如何獲知Gateway是否宕機的信息,另外Cloud Controller如何決策將請求發送給哪個gateway,還有一個Service Gateway已經provision完一個服務後宕機,則關於該服務的bind請求會發送至另一個Service Gateway,而這個Service Gateway不具有這個服務的任何信息,從而導致請求不能被執行。本文主要講述這種方案的設計與實現。
  2. Multi Gateways: multi 就是多個,另外有一種多個之間地位平等的概念,也就是說在Cloud Controller在接收到請求之後,分發給所有的該類型service的Service Gateway,從而使得當一個Service Gateway宕機之後,而其他的所有gateway仍能正常工作。當然,該方案也會存在難點:除了上一種方案中的部分困難之外,還有:由於請求爲多個,一個Service Node接收到多個Service Gateway的請求後,該如何決策該請求是否執行過等。


3. 基於Master/Slave模式的Service Gateway

關於Master/Slave模式的Service Gateway框架如下圖:


3.1 兩個Service Gateway的註冊問題

由於該方案中有兩個同種類型的Service Gateway與Cloud Controller相連,則必須要解決的問題是:如何讓Cloud Controller認識這兩個Service Gateway。在這裏,我們可以回憶在原先的Cloud Foundry中,Cloud Controller如何認識或者存儲一個Service Gateway:Cloud Controller接收Service Gateway的heartbeat信息的同時,在自己的postgres數據庫中存儲該Service Gateway的信息,也相當於完成Service Gateway的註冊。


解決方案

啓動同種類型service的兩個Service Gateway,他們分別向Cloud Controller發送heartbeat,由Cloud Controller接收這些請求,並決策哪一個是Master,哪一個是Slave ,從而存入數據庫。


具體實現

Cloud Controller的軟件框架是基於Rails編寫的,關於postgres數據庫的數據表都有一個model(MVC模式中的M),而修改數據表的模式則必須修改這個model文件,由於之前的模式會驗證service類型的唯一性,現在則必須將/cloudfoundry/cloud_controller/cloud_controller/app/models/service.rb中的驗證代碼validates_uniqueness_of :label 給註釋掉,這樣就有了向數據庫中添加相同類型service的可能性。另外我們還需要在該模式中加入一個屬性MasterOrSlave,這樣可以使得Cloud Controller決策Master/Slave後,將帶有Master/Slave標記的service記錄存入postgres數據庫。


以上操作只是從理論上,允許在數據庫中出現兩個相同label的service記錄,關於最終往數據庫中存兩條記錄還是需要Cloud Controller的controller來實現(MVC中的C)。在具體程序運行中,service_controller.rb文件中的create方法會接收來自Service Gateway的heartbeat信息,並做相應的註冊。爲了實現Master/Slave模式,我們需要改進該方法,加入決策機制,實現Master/Slave的註冊。下圖是決策機制:



3.2 Cloud Controller決策使用Service Gateway的問題

解決了以上的註冊問題,Cloud Foundry還需要解決Cloud Controller如何選擇Service Gateway的問題。


解決方案:

Cloud Controller關於某一label的service 請求,首先找到該label的Master Service Gateway,如果該Service Gateway的active屬性爲TRUE,則表示該Service Gateway存活狀態爲存活,可以接收請求;若active屬性爲FALSE,則表示該Service Gateway存活狀態爲不存活,則找到該label的Slave Service Gateway,若Slave Service Gateway的active屬性爲TRUE,則表示Slave Service Gateway可以接收請求,否則該類型的Master Slave Service Gateway都不可用,請求不會執行,用戶被告知Service Gateway不可用。決策流程如下圖:



3.3 Master/Slave Service Gateway之間服務信息的同步問題

在Cloud Foundry中Service Gateway的運行機制中,已經介紹了provision和bind的功能,在這兩個過程中,都涉及了一個內存空間(具體代碼爲@prov_svcs),這個內存空間用來存放這個service類型的service信息。Service Gateway啓動的時候會通過fetch_handles方法獲取全部的信息,另外在每次經過這個Service Gateway的provision和bind等請求時,會有相應的信息更新。


那如果使用Master/Slave Service Gateway機制的話,爲什麼要考慮兩個Service Gateway之間@prov_svcs的同步信息呢?關於這個問題,可以推測:要保證Master/Slave Service Gateway的高可用性,那麼在一個Service Gateway宕機的時候,另一個Service Gateway仍然可以工作,並接管之後的請求。但是,如果一個請求通過Master Service Gateway 成功provision 一個服務實例後,Master Service Gateway宕機了,那麼關於這個服務實例的bind請求,肯定會通過Slave Service Gateway來完成,然而Slave Service Gateway不具有這個服務實例的任何信息,故bind失敗。毫無疑問,Master/Slave Service Gateway機制必須解決服務信息的同步問題,也就是在一個Service Gateway完成某一個請求,更新@prov_svcs後,必須將相同的更新告知另一個Service Gateway,完成信息的同步。


解決方案:

從Service Gateway的請求流程來看,關於service instance的信息在@prov_svcs中更新後,都會存儲到Cloud Controller的postgres數據庫中。爲了做到Master/Slave Service Gateway的服務信息同步,最有效的方法就是一有信息被更新到Cloud Controller的postgres數據庫中,就馬上把這個更新信息發送給另一個Service Gateway。爲了簡單起見,本文采取的同步方式是:Service Gateway以一個可以人爲設定的頻率向Cloud Controller發送獲取服務信息的請求。這樣的話,從一定程度上說,可以保證兩個Service Gateway服務信息的同步。


具體實現:

在上文的Service Gateway啓動的時候,已經講述過,Service Gateway會使用fetch_handles方法來獲取服務信息,具體代碼見asynchronous_service_gateway.rb:

  update_callback = Proc.new do |resp|
      @provisioner.update_handles(resp.handles)
      @handle_fetched = true
      event_machine.cancel_timer(@fetch_handle_timer)

      # TODO remove it when we finish the migration
      current_version = @version_aliases && @version_aliases[:current]
      if current_version
        @provisioner.update_version_info(current_version)
      else
        @logger.info("No current version alias is supplied, skip update version in CCDB.")
      end
    end

    @fetch_handle_timer = event_machine.add_periodic_timer(@handle_fetch_interval) { fetch_handles(&update_callback) }
    event_machine.next_tick { fetch_handles(&update_callback) }

從代碼中可以看到Service Gateway是首先是通過一個週期性的timer向Cloud Controller發送獲取請求,當收到Cloud Controller的響應之後,取消了這個timer。在Master/Slave Service Gateway 中爲了不斷地發送fetch_handles請求,只需要將代碼event_machine.cancel_timer(@fetch_handle_timer)註釋掉即可。這樣的話,Service Gateway就會以週期@handle_fetch_interval發送fetch_handles請求。只要Cloud Controller關於service的信息有變動,Service Gateway就會在@handle_fetch_interval時間內獲知。在此基礎上,修改以上代碼只是實現週期性發送請求,而Service Gateway在接受週期性響應後,還需要在處理的時候作出相應的修改,具體代碼在provision.rb中,可以每次執行的時候都將@prov_svcs置空,然後再將所有的服務信息放入hash對象@prov_svcs。


關於該問題的解決,以上只是闡述了Service Gateway需要作的改動,這包括Service Gateway發送請求和接受請求,但是隻有這些還是不夠的,對於通信的Server端,Cloud Controller也需要作一定的修改,主要是如何根據Service Gateway的請求,讀取postgres數據庫中所有的服務信息。代碼地址主要爲cloudfoundry/cloud_controller/cloud_controller/app/controllers/service_controller.rb的list_handles方法。由於Cloud Controller都是通過Service Gateway的URL信息去尋找數據庫中數據表service_bindings和service_configs的關於該Service Gateway的所有service instance,而現在是Master/Slave Service Gateway機制,需要將兩個Service Gateway的服務信息全部信息找出,並返回給Service Gateway。比如:service instance A是由 Master Service Gateway來provision或者bind的,那麼在service_configs或者service_bindings中是屬於Master Service Gateway的,因此Slave Service Gateway在獲取handles的時候,也需要將Master Service Gateway取出,並返回給Slave Service Gateway。其中的具體實現可以參考Rails中的model,在這裏service_binding blongs_to :service_config,service_config belongs _to :service。


3.4 unprovison, bind和unbind等請求的部分修改

關於這部分的修改,以unprovision請求爲例:


Cloud Controller在收到用戶關於某service instance的unprovision後,首先會從service_configs數據表中找出這個service instance,然後再取出Service Gateway的URL信息,並向這個URL發送unprovision請求。但是如果現在這個Service Gateway已經宕機的話,之前的機制將不能響應這個請求。如果要在Master/Slave Service Gateway機制中解決這個問題,只需要在每次執行unprovision請求的時候,不是去找service instance的Service Gateway信息,而是去尋找當前正在工作的Service Gateway 來完成unprovision工作。


關於bind和unbind操作,也是如此,在Cloud Controller向Service Gateway發送請求的時候,不是通過service instance來尋找Service Gateway,而是通過當前存活的Service Gateway來尋找。


4. 評價

以上關於Master/Slave Service Gateway設計與實現,已經闡述完畢,現在可以在評價這個機制。


首先從可用性入手,該機制很好的解決了之前Cloud Foundry中Service Gateway的單點故障問題,一旦Cloud Foundry中的一個Service Gateway宕機的話,該機制下,Cloud Foundry仍然可以使用另一個Service Gateway來完成工作。當然,當兩個Service Gateway都宕機的情況下,該機制也不能正常保證Service的工作,但是在兩個Service Gateway相同環境下,出現這種情況的概率要遠遠低於只有一個Service Gateway的時候。


Master/Slave Service Gateway機制最重要的部分主要是如何實現service信息的同步問題,本設計主要是通過週期性的發送獲取請求,來實現最終的同步。但是週期性的請求,其實不能嚴謹的保證consistency。如果關於一個service instance的porvision和bind操作在一個週期內需要完成,而在兩個請求之間的某個時刻,Master Service Gateway宕機的話,Slave Service Gateway也不可用。當這個週期非常粗粒度的時候,這樣的問題是不能容忍的,但是如果這個週期比較細粒度的話,問題能得到非常好的緩解,這樣的情況幾乎可以忽略不計。但是當週期性的請求粒度很細的話,勢必會造成Cloud Controller的通信的擁擠,這也是一個在請求很多,系統很大的時候不得不考慮的問題。


5. 總結

本文主要旨在解決原先Cloud Foundry中關於Service Gateway的單點故障問題。在簡要介紹Service Gateway的運行流程後,講述了Service Gateway可能存在的單點故障問題,並對於該問題提出了兩種解決方案。隨後,本文大篇幅介紹了其中一個解決方案Master/Slave Service Gateway機制,主要包括該方案的具體解決的問題和具體實現。最後,對於該機制進行了簡要的評價,主要着眼於可用性與對系統的性能影響。





轉載請註明出處。

這篇文檔更多出於我本人的理解,肯定在一些地方存在不足和錯誤。希望本文能夠對開始接觸Cloud Foundry中service的人有些幫助,如果你對這方面感興趣,並有更好的想法和建議,也請聯繫我。

我的郵箱:[email protected]

新浪微博:@蓮子弗如清





發佈了47 篇原創文章 · 獲贊 10 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章