當前位置:
首頁 > 科技 > FreeWheel基於Go的實踐經驗漫談

FreeWheel基於Go的實踐經驗漫談

FreeWheel 從 2014—2015 年間開始使用 Go 語言進行開發,截至目前,整個體系內有超過 30 個項目是基於 Go 編寫,占其總體應用數的四分之一以上,這幾年 FreeWheel 內部所有團隊都在或多或少使用 Go。對於新業務的開發,各工程師團隊會首先評估 Go 語言是否適合完成這樣的工作。

FreeWheel 在 Go 上的進化

作為美國最大的綜合性傳媒集團 Comcast 的子公司,FreeWheel 服務的對象大多為歐美地區大型的媒體公司,包括電視媒體和運營商等。這些公司擁有大量的視頻資源和廣告資源,因此 FreeWheel 主要為他們提供從廣告部署、管理、投放、計費到預測的一系列完整解決方案。

正因為系列流程的複雜和業務場景各不相同,FreeWheel 各個產品子系統所使用的編程語言也會根據實際的業務場景有不同選擇,其前期主要使用的語言包括 Ruby、C++、Erlang 等。

Go 語言在 FreeWheel 最早的一個嘗試是 IVI(Instant Video Ingestion) 系統的應用(該項實踐在後文進行詳解),用 Go 重寫 IVI 服務以後,在保持同樣 20 毫秒相應時間的前提下,整體性能可以從每天幾十萬條數據擴展到每天幾百萬條數據。

從 IVI 項目中,一個 Go Common Library 的共用代碼衍生而出 ,FreeWheel 中許多基本功能如日誌解析等,都會被封裝到這個庫里供其他團隊使用。此外,隨著該項目的成功,工程師團隊將其中積累的一些較好的經驗進行應用和傳承,Go 語言在 FreeWheel 的使用範圍也逐漸擴展到其他更多的團隊。比如由前端 UI 團隊、廣告決策和投放團隊所開發運維的幾大主要系統也得到了 Go 的很好的支持。

廣告部署、管理系統的開發和工作流處理:之前的廣告部署和管理系統主要是基於 Ruby on Rails 框架的 Web 應用程序,開發過程還結合了更前端的 JavaScript、CSS 等語言。Ruby 面向對象、貼近英語的主要特性,以及 Rails 的速度和靈活性,可以使廣告部署和管理系統的開發運維過程更加快捷高效,易於維護,另外歐美地區客戶也能更順暢地在該系統上登錄使用,進而管理他們的視頻和廣告。此後,因為 Go 語言應用在這種輕量級、高性能服務端的適應性很強,因此該團隊就用 Go 重寫了所有的 API,包括對 API 網關,以及網關相應功能 (如用戶的驗證、流量控制等)的實現。

廣告決策和投放系統:該系統對實施性要求較高,因此基於性能的考慮,FreeWheel 用運行時效率更高的 C++ 語言直接編寫廣告伺服器,使其具備支持高並發、低延遲、高吞吐等性能。Go 的使用得以廣泛延伸後,該系統主幹部分還是用 C++ 實現,但為了滿足一些客戶的新需求,比如在線視頻轉碼服務就是用 Go 語言寫的獨立的服務。最近該系統的技術負責團隊欲實現的一個新功能也是使用 Go 來開發,因其在性能和開發效率上的優勢,該團隊可以很方便的調用 Common Library 中已有的方法去完成大部分的基礎工作。

廣告預測系統:該系統與上面所說的廣告決策系統關係較緊密,最早實現是一個基於 Erlang 語言開發的調度系統,經過演變後的最新版本是使用 Go 語言進行的編寫。演變的原因有三點:第一是性能的考慮,Go 提供了較好的庫和工具鏈去幫助工程師團隊找到其自身的瓶頸、分析並提高其性能;第二是團隊適應性,FreeWheel 擁有上百位工程師,分布在全球亞、歐、美洲的一些國家,因此一致的工業化生產方式相比程序的美觀優雅,會有更優先順序需求,在讓所有工程師共享一致的、可控的、容易分享的公共編程框架和規範上,Go 語言也更具優勢;第三是相比之下 Go 在部署簡易性上的特性。

Go 語言是 FreeWheel 公司目前主要力推的一個方向,在其看來,面向服務的架構的大環境中,Go 非常適合做一些功能相對獨立、功能比較明確的微服務的語言。在結合已有的各種編程語言,計算框架(如 Hadoop、Java、Ruby、C++)的基礎上,FreeWheel 把 Go 語言定位成用來實現輕量級服務或 API 的預設編程語言,將之與用來完成更小粒度工作的 Python 結合在一起,就構成了 FreeWheel 的整個技術語言棧 。

FreeWheel 在 Go 上所經歷的「坑」

雖然從 2012 年 Go 1.0 發布到團隊相繼採用 Go 來編寫項目,這中間經歷了大致三年左右的時間,但由於在 GC 等許多問題的克服上需要 Go 本身去做一部分迭代,FreeWheel 也需要把技術對客戶的影響控制在一個可控的範圍內,因此作為一家 B-to-B 企業,其採用了更為漸進的方式將 Go 語言應用到自身的生產平台上。

在這個過程中,FreeWheel 也經歷過兩個較為重要的「坑」。

GC 的問題

如多數人所知道的一樣,Go 語言垃圾回收器存在一定的缺陷,特別是容易導致整個進程不可預知的間歇性停頓。像某些大型後台服務程序,如遊戲伺服器、APP 容器等,由於佔用內存巨大,其內存對象數量極多,GC 完成一次回收周期,可能需要數秒甚至更長時間,這段時間內,整個服務進程是阻塞的、停頓的,在外界看來就是服務中斷、無響應。FreeWheel 在使用 Go 1.4 版本時也遇到過類似問題:廣告預測團隊用 Go 來實現調度器,平常運行的時候沒有問題,但一旦觸發 1.4 版本下 GC 的時候,該系統的 downgrade 非常厲害,導致任務的堆積非常嚴重,觸發報警,同時其處理性會下降很多,也會影響其他上下游系統的正常運轉。

於是,FreeWheel 在當時主要採取了三種對策:一、並不把 Go 用在非常關鍵的、對服務進程穩定性要求較高的系統里;二、引入 Kafka 之類的能夠持久化的消息隊列,能夠緩存和重釋這樣的方式去解決這個問題,使系統能扛住衝擊,並在後面把它消化掉;三、盡量復用已經創建的對象,防止 Go 頻繁的創建了回收對象。

Go 1.5 到 1.7 版本相繼出來後,GC 的系統性能得到不斷改進和持續提升(從秒級到毫秒級)。對於目前 FreeWheel 內生存環境不太關鍵的系統來說,Go 1.7 之後的 GC 已經可以達到可接受的範圍和程度以內。

內置數據結構的變化

很多人都知道,Go 語言提供的字典類型並不是並發安全的,此外由於 Go 語言發展較快,有些內置的數據結構如 Map 的行為也發生了變化。因為 Map 為引用類型,所以即使函數傳值調用,參數副本依然指向映射 m,所以多個 goroutine 並發寫同一個映射 m。例如,如果 map 由多協程同時讀和寫就會出現 fatal error:concurrent map read and map write 的錯誤。

Go 1.6 版本之前 Map 可以支持並發讀寫,但 FreeWheel 開發的程序在升級到 1.6 之後也就發現 Map 產生了讀寫競爭的問題。

對於這一問題,常用的有兩種解決方案,一是如上所說的加鎖(包括通用鎖和讀寫鎖),二是利用 channel 串列化處理。FreeWheel 的做法也主要靠兩方面,其一是將鎖粒度設計的更細,使得並發的依賴更少;另外是在不同的數據結構中,選擇性能更高的一方。比如 array 和 slice 中,前者就是更優選擇。

FreeWheel 首席架構師劉昊植認為,並發的時候不能假設 Go 能完美處理所有的工作,工程師需要結合并借鑒傳統(成熟)的編程語言,比如 Java 或者 C 對並發的經驗,在動手之前就想清楚並發的規模、鎖的粒度等,並對系統會如何運行有非常明確的設計和理解。

同時,劉昊植也坦言,隨著 Go 的快速發展階段,FreeWheel 內部也有多種不同的聲音,例如運維團隊就會覺得快速發展階段的語言穩定性不夠,它的特性和數據結構會因為版本升級等原因產生很大變化,並且部分變化不能完全保持向前兼容。在這個過程中 FreeWheel 的總結是:不管 Go 怎樣實現,都要對系統並發做很好的支持,在應用層面做保護和控制,這樣才保證這個系統能夠正常的運行。

基於 Go 創建微服務的例子

如上文所說,Go 在 FreeWheel 的第一個試水項目是 IVI(Instant Video Ingestion) 系統。這個系統的主要功能是為了能夠接受客戶的海量視頻資源元信息的插入,並完成後續的一些處理任務。於是,FreeWheel 主要以 Service API 提供相應的服務。該服務的第一版使用 Ruby 語言實現,但因為 Ruby 對並發的控制相對複雜,因此後續的性能、響應時間和吞吐率都不足以支撐整個公司不斷發展的業務量。此後,FreeWheel 將 Ruby 替換為 Go,用 Go 的 gRPC 去實現了新的 API(當然也支持 RESTful API)。

正因為 API 架構需要足夠靈活地支持未來的業務集成,所以它也成為其自身開發微服務的一個很好的例子。在設計和實現過程中,FreeWheel 把該架構分解成很多基礎模塊,比如流量控制、用戶驗證等,可以很靈活的把這些模塊根據定製化的需求拼裝在一起,提供適應市場和客戶發展需求的真正價值。通過清晰的介面定義,Go 語言可以很好地把整個系統串聯在一起。

此外,因為 Go 對容器及虛擬化技術有一些天然的支持,FreeWheel 的 API 團隊也正在加速採用這種架構,他們準備將 API 都封裝在 Docker 的 image 鏡像里,用 Kubernetes 把所有的系統都管理起來,用 ETCD 或其他類似軟體來輔助服務的發現和管理。

為什麼 FreeWheel 沒有全部用系統重寫 Go

從編程範式的角度來說, Go 語言是變革派,而不是改良派。對於 C++、Java 和 C# 等語言為代表的面向對象的思想體系,總體來說全球範圍內許多公司對 Go 語言的態度更為保守,多數持有限吸收的觀念(這可從下圖中 Go 的熱度分布情況看出)。

即使 FreeWheel 在實踐中發現 Go 比其他類編程語言具有許多更為明顯的優勢,如在寫並行上,相比 Python 這樣的解釋語言要高一個數量級;如前期由 Python 開發的很多輕量級 API,因為全局解釋鎖 GIL 的關係而面臨著進程間通信帶來額外開銷,所以就把輕量級 API 遷移到 Go 上;又比如 Go 在並發上的優勢,適合面向多用戶同時上傳、同時調用 API 的場景……但其內部團隊也並沒有用 Go 來重寫全部系統。原因主要有兩點:

第一,FreeWheel 有很多已有的演算法實現,想全部切入到 Go 上會面臨巨大的開銷和成本;第二,相比 C 或者 C++,Go 在高性能方面還沒有完全的證明自己。在 Web 伺服器端,它目前也沒有一個特別好的像 RoR、Django、或者 PHP 的流行框架。對於 FreeWheel 來說,整個廣告伺服器是不允許出現明顯的 downgrade 情況(尤其是當 GC 時),所以對這種非常關鍵的系統,目前還不能完全用 Go 去寫。所以劉昊植也認為,Go 在擴展使用場景層面可能還需要做一些較大變革。

此外,FreeWheel 基於 Go 的 Web 程序,目前使用的是 Gorilla 框架。但從 Martini、Revel、Gocraft/web 等幾款主流框架的使用和評價上看(可以下圖 github 上的數據做個參考),Go 社區還沒有一款處於統治地位的 Web 框架。如果 Go 想把它的觸角伸得更長,這可能是其未來發力的一個方向。

但總的來說,Go 在高並發、開發效率等特性上的優勢,決定了 Go 在 FreeWheel 內的採用程度會越來越深。劉昊植說:「除了一些已有業務依賴於 Hadoop、Spark 這樣的基礎設施,對於新增的業務和功能,Go 語言會是我們的首選。」

另外,FreeWheel 希望其使用的編程語言是能夠得到跨大陸、跨時區、受所有工程師共同認可的,所以 Go 或許會是其最好的選擇。這個過程中,FreeWheel 也評估過很多其他語言如 Scala、Rust 等,但最終因為 Go 在學習成本、統一實踐、社區規模等方面的優勢而勝出。

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 InfoQ 的精彩文章:

微服務並不一定是你的最佳實踐,別把它想得太美好
年薪50W不是夢,100天掌握BAT前端中高級工程師必備技能
阿里高可用的兩大法寶
餓了么大前端總監 Sofish:前端和移動程序員如何在大前端浪潮下成長?今晚直播!

TAG:InfoQ |

您可能感興趣

GemFire與Greenplum的最佳集成實踐之實施經驗談
TensorFlow入門必看:Google AI實習生經驗談
JMeter測試WebSocket的經驗總結
Isaiah Thomas 作客最新一期《Sneaker Shopping》分享購買 Air Jordan 假鞋經驗
經驗分享:我的Deeplearning.ai課程學習之旅!
ZooKeeper運維部署的一些經驗
SQLAlchemy 使用經驗
Flowsik分享了為「MIC Drop」Remix幫助BTS歌詞的經驗
ES6——Promise使用經驗
積累經驗! 判斷你的iPad支持哪種Apple Pencil
Mobileye CEO談Uber事故: 傳統經驗很重要,尤其在性命攸關的領域
JamesYalin的攝影經驗分享
Mobileye CEO談Uber事故:傳統經驗很重要,尤其在性命攸關的領域
Crunch團隊分享SpringCloud微服務的使用經驗
LOL:RNG慘遭玲瓏塔 Rookie:Uzi只是有經驗的Jacklove
Netflix採用GraphQL的經驗分享
Immerse分享Rift應用移植到Quest平台的開發經驗
Jackeylove實力有多強,Rookie評價他是沒有經驗的UZI
Linux系統管理員的Bash指南,11條Bash實踐經驗!
經驗:解決Inno Setup 和一些應用程序在Windows 中不能訪問UNC路徑的問題