基於 Go 技術棧的微服務構建

Ucloud發表於2017-11-30

在大型系統的微服務化構建中,一個系統會被拆分成許多模組。這些模組負責不同的功能,組合成系統,最終可以提供豐富的功能。在這種構建形式中,開發者一般會聚焦於最大程度解耦模組的功能以減少模組間耦合帶來的額外開發成本。同時,微服務面臨著如何部署這些大量的服務系統、如何運維這些系統等新問題。

本文的素材來源於我們在開發中的一些最佳實踐案例,從開發、監控、日誌等角度介紹了一些我們基於 Go 技術棧的微服務構建經驗。

開發

微服務的開發過程中,不同模組由不同的開發者負責,明確定義的介面有助於確定開發者的工作任務。最終的系統中,一個業務請求可能會涉及到多次介面呼叫,如何準確清晰的呼叫遠端介面,這也是一大挑戰。對於這些問題,我們使用了 gRPC 來負責協議的制訂和呼叫。

傳統的微服務通常基於 HTTP 協議來進行模組間的呼叫,而在我們的微服務構建中,選用了 Google 推出的 gRPC 框架來進行呼叫。下面這張簡表比較了 HTTP RPC 框架與 gRPC 的特性:

gRPC 的介面需要使用 Protobuf 3 定義,通過靜態編譯後才能成功呼叫。這一特性減少了由於介面改變帶來的溝通成本。如果使用 HTTP RPC ,介面改變就需要先改介面文件,然後周知到呼叫者,如果呼叫者沒有及時修改,很可能會到服務執行時才能發現錯誤。而 gRPC 的這種模式,介面變動引起的錯誤保證在編譯時期就能消除。

在效能方面,gRPC 相比傳統的 HTTP RPC 協議有非常大的改善(根據這個評測,gRPC 要快 10 倍)。gRPC 使用 HTTP/2 協議進行傳輸,相比較 HTTP/1.1, HTTP/2 複用 TCP 連線,減少了每次請求建立 TCP 連線的開銷。需要指出的是,如果單純追求效能,之前業界一般會選用構建在 TCP 協議上的 RPC 協議(thrift 等),但四層協議無法方便的做一些傳輸控制。相比而言,gRPC 可以在 HTTP header 中放入控制欄位,配合 nginx 等代理伺服器,可以很方便的實現轉發/灰度等功能。

接下來著重談談我們在實踐中如何使用 gRPC 的一些特性來簡化相關開發流程。

1. 使用 context 來控制請求的生命週期

在 gRPC 的 go 語言實現中,每個 RPC 請求的第一個引數都是 context。 HTTP/2 協議會將 context 放在 HEADER 中,隨著鏈路傳遞下去,因此可以為每個請求設定過期時間,一旦遇到超時的情況,發起方就會結束等待,返回錯誤。

ctx := context.Background()     // blank context
ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
defer cancel( )
gRPC.CallServiveX(ctx, arg1)

除了能加入超時時間,context 還能加入其他內容,下文我們還會見到 context 的另一個妙用。上述這段程式碼,發起方設定了大約 5s 的等待時間,只要遠端的呼叫在 5s 內沒有返回,發起方就會報錯。

2. 使用 TLS 實現訪問許可權控制

gRPC 整合了 TLS 證書功能,為我們提供了很完善的許可權控制方案。在實踐中,假設我們的系統中存在服務 A,由於它負責操作使用者的敏感內容,因此需要保證 A 不被系統內的其他服務濫用。為了避免濫用,我們設計了一套自簽名的二級證書系統,服務 A 掌握了自簽名的根證書,同時為每個呼叫 A 的服務頒發一個二級證書。這樣,所有呼叫 A 的服務必須經過 A 的授權,A 也可以鑑別每個請求的呼叫方,這樣可以很方便的做一些記錄日誌、流量控制等操作。

3. 使用 trace 線上追蹤請求

gRPC 內建了一套追蹤請求的 trace 系統,既可以追蹤最近 10 個請求的詳細日誌資訊,也可以記錄所有請求的統計資訊。

當我們為請求加入了 trace 日誌後,trace 系統會為我們記錄下最近 10 個請求的日誌,下圖中所示的例子就是在 trace 日誌中加入了對業務資料的追蹤。

在巨集觀上,trace 系統為我們記錄下請求的統計資訊,比如請求數目、按照不同請求時間統計的分佈等。

需要說明的是,這套系統暴露了一個 HTTP 服務,我們可以通過 debug 開關在執行時按需開啟或者關閉,以減少資源消耗。

監控

1. 確定監控指標

在接到為整個系統搭建監控系統這個任務時,我們面對的第一個問題是要監控什麼內容。針對這個問題,Google SRE 這本書提供了很詳細的回答,我們可以監控四大黃金指標,分別是延時、流量、錯誤和飽和度。

  • 延時衡量了請求花費的時間。需要注意的,考慮到長尾效應,使用平均延時作為延時方面的單一指標是遠遠不夠的。相應的,我們需要延時的中位數 90%、95%、99% 值來幫助我們瞭解延時的分佈,有一種更好的辦法是使用直方圖來統計延時分佈。
  • 流量衡量了服務面臨的請求壓力。針對每個 API 的流量統計能讓我們知道系統的熱點路徑,幫助優化。
  • 錯誤監控是指對錯誤的請求結果的統計。同樣的,每個請求有不同的錯誤碼,我們需要針對不同的錯誤碼進行統計。配合上告警系統,這類監控能讓我們儘早感知錯誤,進行干預。
  • 飽和度主要指對系統 CPU 和記憶體的負載監控。這類監控能為我們的擴容決策提供依據。

2. 監控選型

選擇監控方案時,我們面臨的選擇主要有兩個,一是公司自建的監控系統,二是使用開源 Prometheus 系統搭建。這兩個系統的區別列在下表中。

考慮到我們的整個系統大約有 100 個容器分佈在 30 臺虛擬機器上,Prometheus 的單機儲存對我們並不是瓶頸。我們不需要完整保留歷史資料,自建系統的最大優勢也不足以吸引我們使用。相反,由於希望能夠統計四大黃金指標延生出的諸多指標, Prometheus 方便的 DSL 能夠很大程度上簡化我們的指標設計。

最終,我們選擇了 Prometheus 搭建監控系統。整個監控系統的框架如下圖所示。

各服務將自己的地址註冊到 consul 中,Prometheus 會自動從 consul 中拉取需要監控的目標地址,然後從這些服務中拉取監控資料,存放到本地儲存中。在 Prometheus 自帶的 Web UI 中可以快捷的使用 PromQL 查詢語句獲取統計資訊,同時,還可以將查詢語句輸入 grafana,固定監控指標用於監控。

此外,配合外掛 AlertManager,我們能夠編寫告警規則,當系統出現異常時,將告警傳送到手機/郵件/信箱。

日誌

1. 日誌格式

一個經常被忽略的問題是如何選擇日誌記錄的格式。良好的日誌格式有利於後續工具對日誌內容的切割,便於日誌儲存的索引。我們使用 logrus 來列印日誌到檔案,logrus 工具支援的日誌格式包裹以空格分隔的單行文字格式、JSON 格式等等。

time=”2015-03-26T01:27:38-04:00″ level=debug g=”Started observing beach” animal=walrus number=8
time=”2015-03-26T01:27:38-04:00″ level=info msg=”A group of walrus emerges from the ocean” animal=walrus size=10Json格式
{“animal”:”walrus”,”level”:”info”,”msg”:”A group of walrus emerges from theocean”,”size”:10,”time”:”2014-03-10 19:57:38.562264131 -0400 EDT”}
{“level”:”warning”,”msg”:”The group’s number increased tremendously!”,”number”:122,”omg”:true,”time”:”2014-03-10 19:57:38.562471297 -0400 EDT”}

在微服務架構中,一個業務請求會經歷多個服務,收集端到端鏈路上的日誌能夠幫助我們判斷錯誤發生的具體位置。在這個系統中,我們在請求入口處,生成了全域性 ID,通過 gRPC 中的 context 將 ID 在鏈路中傳遞。將不同服務的日誌收集到 graylog中,查詢時就能通過一個 ID,將整個鏈路上的日誌查詢出來。

2. 端到端鏈路上的呼叫日誌收集

上圖中,使用 session-id 來作為整個呼叫鏈的 ID 可以進行全鏈路檢索。

小結

微服務構建的系統中,在部署、排程、服務發現、一致性等其他方面都有挑戰,Go 技術棧在這些方面都有最佳實踐(docker、k8s、consul、etcd 等等)。具體內容在網上已經有很完善的教程,在此不用班門弄斧,有需要的可以自行查閱。

相關文章