1. 程式人生 > >高速換輪:Uber如何用微服務重構工程系統?

高速換輪:Uber如何用微服務重構工程系統?

幾個月前,我們討論過Uber關於放棄它單一整體的程式碼庫,而支援一種模組化的靈活的微服務結構。自那時候以來,我們已經花費了數千個小時,使用多種語言和多種不同的框架來擴充套件Uber的微服務(數以百計)生態系統。這種持續的重構是一個巨大的挑戰,因此,我們趁機在Uber的微服務中採用了一套新的技術。通過一個技術棧和一套符合SOA遷移的標準,我們已經大大簡化了整個服務的開發。

微服務重構工程系統

開始一個新服務

在一個快速發展工程組織中,我們可能很難跟蹤所有正在進行的工作。這種增長需要一個流程來防止不同團隊之間的重複工作。在Uber,我們通過要求新服務的作者提交一份請求註解(RFC)來解決這個問題,RFC是一個新服務的高層次提案,概述了新服務的目的,架構,依賴,以及其他實現細節,以便其他Uber工程師進行討論。RFC的目的有兩個:

1)徵求意見,以便提高開發的服務質量,

2)避免重複工作和挖掘合作機會。

多個熟悉該領域的工程師會稽核服務的設計。一旦反饋被納入服務提案,就可以開始構建有趣的服務。

實現一個新服務

Tincup,我們的貨幣和匯率服務,這是一個很好的關於在Uber如何實現微服務的例子。Tincup是一個最新貨幣和匯率資料的介面。它有兩個主要的服務:第一個是獲取貨幣物件,第二個是獲取給定貨幣的當前匯率(兌美元)。這兩項功能是必須的,因為Uber是全球性的業務,匯率變動頻繁,我們支援近60種貨幣的便捷交易。

微服務

無論你在什麼地方,你都可以通過點選側邊欄的圖示來請求搭車。Tincup能保證你支付的是你所在國家的貨幣。

通過新技術啟動微服務

在構建Tincup時,需要重寫所有與貨幣和匯率相關的邏輯,這就提供了一個好的機會來重新評估很久以前在Uber的設計方案。我們使用了很多新的框架,協議,約定來實現Tincup。

首先,我們討論了貨幣和匯率相關程式碼的整體結構。

在Uber,近幾年我們已經多次修改了許多資料集的持久層(像這一次)。每次的改變都是漫長而繁瑣的。我們在這個過程中得到了很多經驗教訓,如果有可能的話,最好是將持久層與具體的應用邏輯進行分離。這就產生了一種應用開發的模式,我們稱之為MVCS,它是對我們常用的MVC模式的擴充套件。它包含了一個服務層來實現應用的業務邏輯,通過將包含業務邏輯的服務層與應用的其它部分進行隔離,持久層就可以在不需要重構業務邏輯的前提下進行升級和替換(只有那些直接處理儲存和讀取資料的程式碼需要改變)。

其次,我們認真考慮了貨幣和匯率的持久層。在Tincup之前,資料存在一個ID遞增的關係型資料庫——PostgreSQL中。

然而,這種資料的儲存方式不允許Uber的所有資料中心進行全球化的資料複製,因此,這一模式不符合我們現在全面發展(所有的資料中心同時提供服務)的架構。由於全球的資料中心都需要訪問貨幣和匯率,我們在持久層使用UDR(Uber的全球複製可擴充套件資料儲存)來實現資料交換。

微服務中心成長的預期顧慮

在決定改變貨幣和匯率的設計之後,我們解決了很多工程生態系統中由微服務數量不斷增加而自然而然會出現的問題。

網路I/O的阻塞是一個嚴重的問題,可能會導致uWSGI工作執行緒的飢餓。如果所有的請求服務跟Tincup一樣都是同步的,一個服務惡化的風險是可能會引起連鎖反應,從而影響所有的呼叫者。我們決定採用Tornado(一個Python的基於迴圈事件的非同步框架)來防止阻塞。由於我們已經將大量的程式碼從整體的基礎程式碼庫分離出來,我們便通過選擇一個非同步框架來保持現有應用邏輯保持與原來邏輯相同,這一點對保證風險最小化是非常重要的。Tornado滿足我們這個要求,因為它允許我們使用一種非阻塞式的I/O來同步查詢程式碼(此外,為了解決上述的I/O問題,許多服務的提供者使用一種新的Go語言)。

曾經單個API的呼叫現在可能會波及到許多的微服務呼叫。為了便於在一個大的生態系統中發現其它服務和識別失敗的點,Uber微服務在Hyperbahn(一個針對RPC在內部開發的網路多路複用幀協議)上使用開源的TChannel,TChannel為客戶端和服務端提供協議,這兩者通過Hyperbahn的智慧路由進行連線。它解決了微服務世界中的幾個核心的問題:

1、服務發現所有的生產者和消費者通過路由網進行自我註冊。消費者通過生產者的名稱進行訪問,而不需要知道生產者的伺服器或埠。
2、容錯。路由網可以追蹤故障率和SLA( 服務等級協議)違例。它可以檢測到不健康的主機,並隨後將其從可用的主機池中進行刪除。
3、速率限制和電路斷開。這些特性確保來自客戶端的錯誤請求和響應慢不會引起級聯故障。
由於微服務呼叫數量的快速增長,有必要為每個呼叫維護一個定義良好的介面。我們很清楚需要使用IDL(介面定義語言)來進行維護,我們最終決定使用Thrift。Thrift強制服務的提供者釋出嚴格的介面定義,這就簡化了整合服務的過程。Thrift拒絕不遵守介面定義的呼叫,而不是將其洩露到更深層次的程式碼而引起故障。這種公開宣告介面的策略,強調向後相容的重要性,因為多個版本的Thrift服務介面可能在任意時間中同時使用。服務的作者不能對介面做重大修改,只能對定義好的介面新增一些非重大改變的內容,直到所有消費者都不再使用。

讓Tincup成為行業佼佼者的準備:產品

最後,在Tincup的實現階段將要完成時,我們是用一些有用的工具來準備生產環境的Tincup:

首先,我們知道Uber的流行每天、每週、每年都是變化的。我們可以在預期的時間內看到巨大的高峰期,如除夕和萬聖節,所以我們必須在釋出之前確保這些服務能夠處理這些增加的負載。按要求,在Uber釋出一個新服務時,我們使用我們內建的Hailstorm來進行負載測試,確定Tincup功能點的缺陷和服務的中斷點。

然後,我們考慮Uber工程的另一個主要目標:更加有效的使用硬體。因為Tincup是一個相對輕量級的服務,它很容易與其他微服務共享機器。服務就是關愛,對吧?當然,事情也不總是如此:我們還是想確保每個服務獨立執行,而不影響在同一臺機器上的其他服務。為了防止這個問題,我們使用uContainer(Uber的Docker)來部署Tincup,以實現資源的隔離和限制。

正如其名稱所暗示的,uContainer使用Linux的容器功能和Docker來實現Uber服務的容器化。它將一個服務包裝在一個隔離的環境中,以保證即使有其他服務在同一臺主機上執行,該服務也能夠始終如一的執行。uContainer擴充套件了Docker的功能,在Docker容器中增加了:

1)更靈活的構建功能

2)更多視覺化工具。

最後,為應對生產環境中不可避免的宕機和網路連線問題所做的準備,我們使用一個稱之為uDestroy的內部工具來釋放服務控制混亂問題。通過模擬宕機,我們可以認識的系統的恢復能力。由於我們在其發展的過程中定期的、有目的破壞系統,我們可以發現系統的漏洞,不斷的提高系統耐用性。

完成後的經驗教訓

通過構建Tincup來擴充套件 SOA的過程中,我們學到幾條經驗教訓:

  1. 使用者的的遷移是一個長期的、緩慢的過程,所以儘可能的做到操作簡單。提供程式碼示例。預測遷移完成所需的時間。
  2. 技術棧的學習最好是建立在一個小的服務上。Tincup的應用邏輯非常簡單,這樣開發者可以集中精力去學習新的技術棧,而不是浪費時間在業務邏輯的遷移上。
  3. 開發的初始階段投入大量的時間在單元測試和整合測試,最終會收到回報的。在開發環境中執行程式碼除錯會簡單很多(壓力也更小)。
  4. 儘可能早的、頻繁的去做負載測試。沒有什麼事情比發現你花費了幾周或幾個月實現的系統無法處理流量峰值更糟的了。

Uber的微服務

Uber的SOA遷移為許多服務提供者,甚至是那些行業經驗有限的人展示了機會。擁有一個服務是一項很大的責任,但是Uber開放的、知識共享的文化使得選擇一套新的技術和擁有一個程式碼庫成為了一種有回報、有價值的體驗。

譯/劉曉鵬原文出處——REWRITING UBER ENGINEERING: THE OPPORTUNITIES MICROSERVICES PROVIDE