從零到生產:Go 在 Google 的歷程 [譯]

2007 年 Go 誕生於 Google,2009 年 Google 正式對外宣佈了 Go 語言的開源!時至今日,距離 Go 開源已經過去了近 15 個年頭了 [1]!Go 在 Google 公司內部究竟是怎樣的一個狀態呢?前 Google 員工 Yves Junqueira 近期撰文從其個人所見所聞談了 Go 在 Google 的歷程 [2]!這裏簡單翻譯,供大家參考!


最近,Jeremy Mason[3] 和 Sameer Ajmani[4] 撰寫了有關使 Go 成爲 Google 內部語言之一的傳奇故事。Go 目前是世界上第八大最受歡迎的編程語言(譯者注:2024.4,Go 已經攀升到第 7 位,見下圖),並且仍在增長,因此人們有興趣瞭解 Go 早期以及它是如何走到這一步的。

Go 在 TIOBE 排名升至第 7(譯者配圖)

我想我應該從 SRE、框架開發人員和早期採用者的角度來寫。我分享的所有信息都與谷歌已經公開記錄的系統相關,所以我不認爲我泄露了任何祕密。這個故事有一些重要的部分(例如:envelopei(譯者注:不知道是什麼鬼))我在其他地方沒有看到提到過,所以我不會討論它們。

破冰:我在 Google 的 Go 編程簡介

在 Go 公開發布之前我就開始關注它,當它發佈時,我立即成爲了它的粉絲和 Google 內部的早期用戶。我喜歡它的簡單性。

我在覈心庫上做了一些工作,並且在社區中很活躍,早期經常幫助 go-nuts 郵件列表中的用戶,並編寫開源庫。後來,我幫助組織了西雅圖的 Go Meetup[5],並與他人共同組織了備受喜愛的會議 Go Northwest[6]。

據我所知,我在 Google 編寫了第一個生產關鍵型工具,後來又用 Go 編寫了第一個面向用戶的服務。

第一個是用於監控 Google+[7] Bigtable[8] 服務器運行狀況的服務。這是我作爲 SRE 的工作之一。Bigtable 擁有有關每個 tablet 性能的詳細內部統計數據,但有時我們需要了解爲什麼某個 tablet 如此過載以及系統其他地方發生了什麼,以便我們能夠了解根本原因。我們需要隨着時間的推移收集這些數據並進行分析。因此,我構建了一個爬蟲,可以檢查數千臺服務器並在全局儀表板中顯示詳細的統計數據。

2011 年,Andrew Gerrand 在接受 The Register 採訪 [9] 時提到了這項工作。他當時向我證實,這指的是我的項目。我很興奮!他在採訪中這樣說道:

谷歌有管理應用程序和服務的人員,他們需要編寫工具來抓取幾千臺機器的狀態並聚合數據,” 他說。“以前,這些操作人員會用 Python 編寫這些內容,但他們發現 Go 在性能和實際編寫代碼的時間方面要快得多。”

Go 的運行速度和編寫速度確實更快。最重要的是,使用起來很有趣。它讓我更有效率,所以我迷上了 Go!

低級庫:節點身份驗證和 RPC

當 Go 啓動時,它無法與 Google 的內部基礎設施通信。

首先,團隊必須構建一個基於 proto buffer 的 stubby RPC 系統 [10]。這需要實現 LOAS[11] 來加密和驗證與遠程節點的通信,並使用 Chubby[12] 進行名稱解析(類似於 kubernetes 中使用的 etcd[13])。

Stubby 和 Chubby 是出了名的複雜。Stubby 需要一個複雜的狀態機來管理連接,但大部分繁重的工作都是由 Chubby 完成的,即使 Borg[14] 節點耗盡 CPU,或者因爲有人正在運行 map reduce 而佔用了所有機架的交換機帶寬而導致暫時的網絡斷開連接,Chubby 也需要提供一致的 world view,這很容易陷入死鎖或可靠性問題。

根據海勒姆定律 [15],系統的所有可觀察行爲都將取決於某人,因此團隊必須確保與現有生產網絡預期的行爲完全匹配,並注意極端情況。例如,衆所周知,健康檢查很容易出錯,不應該太嚴格,否則當網絡的一部分暫時過載或與另一部分斷開連接時,它們會爲級聯故障敞開大門。必須實現的其他的分佈式系統功能,例如 backend subsetting 和負載均衡。我們需要診斷何時出現問題,因此很早就添加了日誌記錄和指標庫。

爲了找到要通信的 host:port,服務使用 Chubby 進行名稱解析 (name resolution)。它作爲少量數據的完全一致的存儲系統,其最常用的功能是解析 BNS[16] 地址 - 類似於你今天在 kubernetes 中使用 etcd 看到的功能。

系統使用 Stubby 協議向其他服務發送數據並從其他服務接收數據。在 Stubby(如 gRPC[17])中,消息使用 proto buffer wire format 進行編碼。使用反射在運行時創建 proto buffer 有效負載會太慢並且佔用大量資源。工程師還會錯過來自強類型系統的反饋。出於這些原因,谷歌爲所有語言使用了生成代碼庫。幸運的是,proto buffer 與語言無關。團隊只需爲現有構建系統邏輯編寫 Blaze[18] 擴展,瞧,我們就爲所有內部 RPC 服務提供了高質量的客戶端庫代碼。

奇怪的是,爲另一種語言生成代碼會產生少量的增量構建時間成本,而 Google 擁有成千上萬的 RPC 服務。因此,我們決定每個 RPC 服務的所有者必須選擇允許構建系統爲其特定服務生成 Go 代碼。雖然有點官僚主義,但隨着時間的推移,我們看到數千個 CL(谷歌的等效 Pull 請求)飛來飛去,將 Go 添加到每個服務的生成代碼集中。這對於我們的社區來說是一個愚蠢但有趣的進度衡量標準,因爲我們可以計算代碼庫中 “啓用 Go” 標誌的實例數量。

影響全局 Master 選擇和 Bigtable 引流執行

作爲這些早期庫的早期採用者和專注於生產系統的工程師,我能夠了解內部系統的工作原理。我幫助調試並解決了許多奇怪的問題。隨着時間的推移,我獲得了構建系統來自動化運維 SRE 工作的信心。注意到我們的服務中大多數面向用戶的中斷髮生在存儲層(Bigtable 或 Colossus),我產生了創建一個控制系統的想法,該系統可以監視 Bigtable 分區的運行狀況,並在檢測到問題時在 GSLB[19] 中小心地清空它們。當時,當發生中斷時,SRE 會進行分頁,在確認這是存儲問題後,他們會簡單地清空集羣並返回睡眠狀態。

我想用適當的控制系統取代這個手動 whackamole。抽取流量可能會導致級聯故障,因此這是一項危險的操作。當時,大多數 SRE 不想在自動化系統中冒這種風險。幸運的是,我有一個很好的團隊。他們仔細審查了我的提案,提供了有關潛在故障模式的大量反饋,我們最終提出了一個我們有足夠信心的設計。我們需要仔細聚合來自不同監控系統的信息(這可能會失敗或提供不正確的數據),使用全局負載均衡器安全地離開集羣,然後最終在 Buganizer[20] 中開具 ticket,以便待命的 SRE 在工作期間進行處理。

系統需要多個副本始終處於運行狀態以對中斷做出反應,但一次只有一個副本保持活動狀態至關重要。爲了支持這一點,我爲 Go 編寫了一個全局 “主選舉(master election)” 庫,它將確保系統的單個副本一次處於活動狀態。它使用全局 Chubby 鎖服務來提供一個高級庫來告訴應用程序開始運行或在無法證明我們持有 “全局鎖” 時自行關閉。

爲了支持這項工作,我還到處編寫了一些小實用程序,並與 Go 團隊合作修復錯誤。我報告了我發現的問題,他們修復了這些問題。

當時,Go 團隊的重點是外部用戶。他們所有的注意力都集中在發佈 Go 1.0 上。這是一個資源很少的小團隊,但他們的 “祕密武器” 是他們是傑出的工程師,而且團隊非常高效。不知何故,儘管針對內部用戶的支持時間非常有限,但他們還是很好地完成了支持工作。內部郵件列表非常活躍,谷歌員工大多在業餘項目中使用 Go,但 Go 團隊採用了非常強大的內部流程來使事情順利運行。他們仔細審查了每個人的代碼,並幫助建立了強大的內部代碼質量文化。每當他們發佈新的 Go 候選版本時,他們都會使用新版本重建所有內部項目並重新運行我們的測試以確保一切正常。他們總是以正確的方式做事。

生產中 JID 代理部署的最初洞察

幾個月後,我在 Google 用 Go 編寫了第一個面向用戶的服務。我所說的面向用戶的意思是,如果它停止工作,許多面向用戶的產品將停止工作。這是一個簡單的 RPC 服務,但所有 Google 消息服務都使用它。

該服務根據從另一個 RPC 服務獲取的內部用戶 ID 將數據與 JID 格式) 相互轉換。該服務很簡單,但規模很大,當時每秒執行數十萬個請求。它對於爲 Android、Hangouts 和其他產品提供支持的 Google 消息服務核心至關重要。

這次遷移是 Google Go 的一個非常重要的測試平臺。重要的是,它爲我們提供了一個令人難以置信的基礎來比較 Go 與其他生產語言(特別是 Java)的性能。該服務正在取代難以維護的基於 Java 的服務(不是因爲 Java,而是因爲其他原因),因此我們使用實際生產流量同時運行這兩個服務,並密切比較它們的性能。

我們從第一個大規模實驗中吸取了重要的教訓:Go 使用比 Java 更多的 CPU 內核來服務相同的流量,但垃圾收集 (GC) 暫停非常短。作爲一個努力減少 GC 暫停以改善面向用戶的服務的尾部延遲的 SRE,這是非常有希望的。Go 團隊對這個結果很滿意,但他們並不感到驚訝:Go 只是在做它設計的事情!

事實上,幾年後,當 SRE 領導層正式審查 Go 的生產就緒情況 [21] 並要求 Go 團隊確保 Go 具有良好的 GC 性能時,我認爲這很大程度上只是形式上的。Go 很早就證明了 Go 具有出色的 GC 性能,並且多年來它不斷變得更好。

遇到內部庫缺失的情況

在早期,在 Flywheel[22] 之前,在 dl.google.com[23] 服務之前,在 Vitess[24] 之前,Go 被 Google 的大多數工程師忽視了。如果有人想向用戶交付產品,他們首先必須編寫基本構建塊,讓他們連接到谷歌的其他服務。對於大多數人來說,這是不可能的。

鎖服務(chubby)和 RPC 系統(stubby)的底層庫相對較快地出現(同樣,Go 團隊非常優秀),Google 最重要的庫是與我們存儲系統的接口:Bigtable、 Megastore、Spanner、Colossus。如果你想讀取或寫入數據,你基本上還不能使用 Go。但是,慢慢地,Go 團隊(有時與核心基礎設施團隊合作)開始應對這一挑戰。

他們最終一一爲 Bigtable、Colossus 甚至 Spanner 創建了庫(不是 Megastore,因爲它很大程度上是一個被 Spanner 取代的庫)。這是一項重大成就。

Google 的 Go 使用量仍然有限,但我們的社區正在不斷壯大。我在 Google 開設了第一門官方的 Go 編程簡介課程,並幫助位於蘇黎世的 Google 員工找到了可以使用 Go 進行工作的有趣項目。大約在這個時候我終於獲得了 Go 的 “可讀性”(譯者注:這似乎是 Go 團隊對代碼 review 者資格的一種認可),後來加入了 Go 可讀性團隊。

需要站點可靠性工程師來指導應用程序功能

Go 中缺少的另一件事是與生產相關的功能,我們多年來了解到這些功能對於生產團隊來說是必需的。也就是說,如果你想運行大型系統而不需要一直處於運維和救火模式。

每當發生中斷並診斷根本原因時,隨着時間的推移,我們會了解到系統中應該改進的弱點。目標是減少停機和運維開銷。很多時候,爲了使系統更加可靠,我們必須對應用程序運行時進行更改。我們很難理解我們需要觀察和控制系統以使其真正可靠的細節深度。

例如,我們需要確保,除了記錄傳入請求之外,應用程序還應該記錄有關該操作中涉及的傳出請求的詳細信息。這樣,我們就可以確定地指出,比如說,我們的 “CallBob” 服務在上午 11:34 變慢是因爲 “FindAddress” 調用的延遲增加。當我們操作大型系統時,我們不能滿足於猜測工作和弱相關性。有太多的轉移注意力和根本原因查找工作需要處理。我們需要對原因有更高的確定性:我們希望看到失敗的特定請求確實經歷了高延遲,並排除其他解釋(即:未觸發緩慢的 FindAddress 調用的傳入請求不應失敗)。

同樣,多年來我們注意到 SRE 的大部分時間都花在團隊之間的協調上,以確定一個服務每秒應發送到另一個服務的確切連接數和請求數,以及如何準確建立這些連接。例如,如果多個服務想要連接到後端,我們希望清楚哪些節點正在連接到哪些其他節點。這稱爲後端子集化 (backend subsetting)。需要仔細調整,考慮整個系統的健康狀況,而不僅僅是一個節點或一對節點的健康狀況,而是整個網絡的健康狀況。太大的子集會導致資源佔用過多,太小的子集會導致負載不平衡。因此,隨着時間的推移,SRE 團隊開始幫助維護用於與其服務通信的客戶端庫,以便他們可以檢測正在發生的情況,並保留對其他節點與其系統通信方式的一些控制。

揭開魔法:Go 服務器工具包

SRE 共同擁有客戶端庫的模型在實踐中運行得非常好,隨着時間的推移,我們瞭解到向這些庫添加流量和負載管理是一個好主意。

Alejo Forero Cuervo 在 SRE 書籍章節 “處理過載”[25] 中寫了一些經驗教訓,值得一讀。我們一一向庫中添加了謹慎的邏輯,以根據經驗和內部傳感器自動設置這些參數。

在《不斷髮展的 SRE 參與模型 [26]》中,我的前同事 Ashish Bhambhani 和我的前老闆 Acacio Cruz 解釋說,我們最終發展了 SRE 參與模型,以包括服務器框架 (server framework) 的工作和採用。該模型使 SRE 能夠直接影響系統在細微差別領域的行爲,這得益於我們豐富的現場經驗。

我和我的 SRE 團隊希望將這些功能引入 Go,但它們對於 Go 團隊來說太過奇特和專業,無法處理。我設立了一個 20% 的項目(後來變成了一個全職項目),並招募了一羣願意做出貢獻的經驗豐富的工程師。我飛往紐約,會見了一位非常出色的 Go 團隊成員,我們共同努力爲 Go 中的 “服務器框架” 構建了路線圖。

Go 團隊一開始不太願意接受我們的方法。整個 “框架” 概念對他們來說有點危險。這可能會成爲一場宗教戰爭,但 Go 團隊花時間詳細解釋了他們擔心的原因。Sameer 尤其具有一種不可思議的能力,能夠用技術術語反思和解釋爲什麼他認爲某件事以某種方式比另一種方式效果更好。

Sameer 強烈認爲,Go 不應該有不一致的開發人員體驗,無論是內部還是外部,無論是否有 “框架”。如果 Google 有不同的方法來構建 Go 應用程序,那將對內部 Go 社區造成損害。與他的擔憂一致,我們的 20% 人組成的烏合之衆團隊竭盡全力確保我們的“框架” 感覺更像是另一個庫,而不是一個框架,並且它不會爲 Go 引入不同的編程模型。目標是通過簡單的庫導入來引入我們的可靠性功能。如果你使用我們的庫包裝你的 Go HTTP 或 Stubby 服務器,所有內容在代碼中看起來都一樣,但你神奇地獲得了開箱即用的日誌記錄、檢測、負載卸載、流量管理,甚至每請求級別的實驗性支持。

爲了創建這個讓服務變得更好的神奇庫,我們必須對 Google 的內部 RPC 庫甚至構建系統進行重大更改 - 以使我們的框架團隊能夠爲 RPC 系統創建任意 “擴展”,從而無需任何操作即可無縫運行,並避免接收和發送請求時產生顯着的性能開銷。

結果是值得的。效果非常好。我們的項目使服務變得更容易管理,而無需強加與 Go 團隊想要的不同的編程風格。爲了避免混淆,我們將其稱爲服務器 “工具包”,它成爲在 Google 構建生產就緒系統的正確方法。人們經常在他們的 LinkedIn 個人資料中引用我們的內部服務器框架:)。它被稱爲 Goa,不要與不相關的外部 Goa 框架 [27] 混淆。以下是某人 LinkedIn 個人資料中的示例:

憑藉其生產就緒功能,我們的 Go 工具包消除了 Go 內部增長的主要障礙。工程師現在可以確信他們的 Go 項目的性能與舊的 Java 和 C++ 項目一樣好,並且可調試。也就是說,增長還沒有完全發生。Go 需要一個殺手級用例才能在 Google 流行起來。

Go 在多個 SRE 團隊中的採用

當時,我所在的 SRE 團隊在 Google 具有特殊地位,即社交 SRE 團隊。我們在 SWE 和 SRE 都有出色的工程師和出色的管理人員。所以我們能夠以正確的方式做事。一些 SRE 團隊正在追尾救火,但我們有幸能夠正確地進行工程設計。這創造了一個良性循環,我們在問題變得嚴重之前不斷解決問題,這意味着我們有時間進一步優化運維,等等。

結果,我們的 SRE 團隊編寫了很多有用的代碼。像我的高級工程師同事一樣,我幫助人們找到要做的事情,因此我幫助啓動了許多早期的 Go 中與生產相關的工具。如果其中一個工具發現有問題,它會自動、安全地從整個 Bigtable 集羣中刪除流量。

還有其他與流量和負載管理相關的 Java 和 C++ 項目,由其他高級工程師領導。這種創新環境吸引了人才,我們不斷取得良好的成果,因此我們的 SRE 團隊不斷壯大。

我們的工程總監 Acacio Cruz[28](負責我們團隊以及山景城的同事所發生的許多積極的事情)非常關注工程效率:我們是否將工程時間用於最有影響力的事情?他明白標準化可以提高效率,而且他看到我們的工程師很高興並且富有成效。他的想法是推動 Go 成爲我們團隊中任何自動化的首選工具。該建議是避免使用 Python 並使用 Go 來編寫生產工具。令我驚訝的是,我的隊友沒有人反對。這加速了 Go 在我們的社交 SRE 團隊中的使用,很快我們區域之外的人們就注意到了。

核心庫、服務器框架、成功的生產工具和圍繞 Go 的社交 SRE 標準化——它們都促成了人們對 Go 正在成爲 Google 的一種嚴肅語言的看法的改變。

與此同時,SRE 已經看到了幾代用 Python 編寫的工具,這些工具運行得非常好,但隨着時間的推移變得非常難以維護。Google SRE 喜歡 Python,我們編寫了大量的 Python 代碼。不幸的是,當時缺乏類型和編譯時語法錯誤檢查導致了許多難以修復的問題:

隨着越來越多的 SRE 開始用 Go 編寫自動化,很明顯這些團隊很高興並且富有成效,並且不太可能陷入難以維護的代碼中。人們開始意識到,Go 項目更容易發展和維護,而這不僅僅是這些項目更新、更乾淨或設計得更好的結果。

SRE 領導層注意到了這種影響,並決定採取行動並在組織內進行廣泛的溝通:SRE 團隊最好使用 Go 進行與生產相關的項目 [29],並避免使用 Python。我不知道這在谷歌現在是否被視爲獨裁,但當時我認爲這感覺像是整個組織範圍內良好的溝通和決策。

Go 生產平臺和爆炸式增長

此後事情進展得很快。我們創建了一個從早期就對 Go 提供強大支持的生產平臺,並用高級抽象取代了許多樣板配置和重複過程。該平臺出現了強勁增長,最終其他平臺也出現了。Go 和我們的服務器框架變得無處不在。我最終離開了谷歌,但我仍然快樂地記得那些日子。

雖然我只是該語言的用戶,但觀看一個項目從零到成爲前 10 名的編程語言的經歷教會了我很多東西。我親眼看到,一個強大的團隊,周圍有一個強大的社區,真的可以做出大事。

觀察 Go 的崛起

我在 Google 從事 Go 編程工作改變了遊戲規則,讓我對項目的技術方面以及世界著名團隊的運作方式有了深入的瞭解。隨着項目的進行,我可以清楚地看到 Go 如何使項目和團隊擴展變得更容易。

Go 對簡約設計的強調促進了統一編碼,使新程序員可以輕鬆地集成到項目中,這一功能在時間緊迫的項目中特別有用。隨着項目的發展,新的庫和工具包也出現了,提高了它的受歡迎程度,並促進了包括 Apple、Facebook 和 Docker 在內的幾家大型科技公司的採用。

儘管 Rust 具有更爲廣泛和豐富的功能特性,但 Go 在各個行業的廣泛接受表明,強大的軟件不一定需要複雜。

回顧過去,很明顯,雖然我們的旅程充滿了挑戰,但每一次的曲折、每一次的調整和進步,都是塑造今天 Go 的關鍵。隨着社區不斷向前發展,我很高興看到我們下一步的發展方向。

Go gopher 由 Renee French 設計,並根據 Creative Commons 3.0 屬性許可證獲得許可。


Gopher Daily(Gopher 每日新聞) - https://gopherdaily.tonybai.com

我的聯繫方式:

參考資料

[1] 

距離 Go 開源已經過去了近 15 個年頭了: https://tonybai.com/2023/11/11/go-opensource-14-years/

[2] 

Go 在 Google 的歷程: https://i-admin.cetico.org/posts/early-days-golang-google/

[3] 

Jeremy Mason: https://www.linkedin.com/pulse/language-policy-google-lets-go-jeremy-manson-ffmac

[4] 

Sameer Ajmani: https://www.linkedin.com/pulse/gos-early-growth-2012-2016-sameer-ajmani-oxtjc/

[5] 

西雅圖的 Go Meetup: https://www.meetup.com/golang/

[6] 

Go Northwest: https://www.youtube.com/channel/UCq9zCm9qiQ6glsz8B3kwsxw

[7] 

Google+: https://en.wikipedia.org/wiki/Google%2B

[8] 

Bigtable: https://en.wikipedia.org/wiki/Bigtable

[9] 

The Register 採訪: https://www.theregister.com/2011/05/05/google_go/?page=4

[10] 

stubby RPC 系統: https://cloud.google.com/blog/products/gcp/grpc-a-true-internet-scale-rpc-framework-is-now-1-and-ready-for-production-deployments

[11] 

LOAS: https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45728.pdf

[12] 

Chubby: https://static.googleusercontent.com/media/research.google.com/en//archive/chubby-osdi06.pdf

[13] 

etcd: https://etcd.io/docs/v3.3/faq/

[14] 

Borg: https://research.google/pubs/large-scale-cluster-management-at-google-with-borg/

[15] 

海勒姆定律: https://www.hyrumslaw.com/

[16] 

BNS: https://sre.google/sre-book/production-environment/

[17] 

gRPC: https://grpc.io/

[18] 

Blaze: https://bazel.build/basics/artifact-based-builds

[19] 

GSLB: https://sre.google/workbook/managing-load/

[20] 

Buganizer: https://www.freecodecamp.org/news/messing-with-the-google-buganizer-system-for-15-600-in-bounties-58f86cc9f9a5/

[21] 

SRE 領導層正式審查 Go 的生產就緒情況: https://www.linkedin.com/pulse/language-policy-google-lets-go-jeremy-manson-ffmac/

[22] 

Flywheel: https://research.google/pubs/flywheel-googles-data-compression-proxy-for-the-mobile-web/

[23] 

dl.google.com: https://dl.google.com/

[24] 

Vitess: https://vitess.io/

[25] 

“處理過載”: https://sre.google/sre-book/handling-overload/

[26] 

不斷髮展的 SRE 參與模型: https://sre.google/sre-book/evolving-sre-engagement-model/

[27] 

Goa 框架: https://goa.design/

[28] 

Acacio Cruz: https://www.linkedin.com/in/acacio/

[29] 

SRE 團隊最好使用 Go 進行與生產相關的項目: https://go.dev/solutions/google/sitereliability

[30] 

Gopher 部落知識星球: https://public.zsxq.com/groups/51284458844544

[31] 

鏈接地址: https://m.do.co/c/bff6eed92687

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4XsLbHRq9lRDdQwb3j_m3Q