Python 非常適合快速編寫更高級別的應(yīng)用程序,但并不總是能夠提供企業(yè)級所需的高性能。C 可以創(chuàng)建高性能的可執(zhí)行文件,但是添加功能會花費(fèi)更多時間。這篇文章分享了 Einstein Analytics 企業(yè)級軟件從 C-Python 混合遷移到完全使用 Go 應(yīng)用程序的經(jīng)驗(yàn)。
我們很少有機(jī)會直接將兩種技術(shù)彼此比較以完成同一任務(wù)。但是有時就會那么巧遇到星星排成一行的情況,比如從當(dāng)前技術(shù)堆棧中你一直得到的是負(fù)面影響,而這時恰巧出現(xiàn)了滿足你確切需求的新技術(shù),或者項(xiàng)目的規(guī)模和功能集超過了現(xiàn)有技術(shù)的能力范圍。
在 Salesforce,我們在過去幾年中遇到了這種情況。我們將大多數(shù) Einstein Analytics 后端從 Python-C 混合平臺移植到了 Go。Go 是 Google 為大規(guī)?,F(xiàn)代軟件工程設(shè)計的一種語言。傳說中,谷歌工程師想創(chuàng)建一種為大型應(yīng)用程序設(shè)計的語言,并在等待大型 C ++ 項(xiàng)目編譯時開始了對 Go 的設(shè)計。
這篇文章將分享了我們將企業(yè)級軟件從 C-Python 混合遷移到(幾乎)完全使用 Go 應(yīng)用程序的經(jīng)驗(yàn)。
Einstein Analytics 將業(yè)務(wù)智能處理添加到 Salesforce 實(shí)例中。通過基于云的 AI 處理,無論結(jié)構(gòu)和格式如何,它都直接從 Salesforce CRM 數(shù)據(jù)以及盡可能多的客戶外部數(shù)據(jù)中生成可行的見解或預(yù)測、管道報告、性能度量。
在后臺,給定的 Salesforce 實(shí)例將 Einstein Analytics 功能公開為常規(guī) Salesforce REST API 的一部分。這些鏈接到一個查詢服務(wù)器集群,每個查詢服務(wù)器都提供緩存在內(nèi)存中的鏈接數(shù)據(jù)集的查詢,但是它們可以從集群中的任何節(jié)點(diǎn)填充緩存的數(shù)據(jù)。為了管理所有這些請求,我們在每個服務(wù)器上都有一個優(yōu)化的流程,該流程將請求路由到適當(dāng)?shù)墓?jié)點(diǎn),并將響應(yīng)轉(zhuǎn)發(fā)到 API 請求的發(fā)起者。對于任何讀取數(shù)據(jù)集的查詢服務(wù)器,這些調(diào)用都看起來是本地的。而本地意味著快速。較大的數(shù)據(jù)集是分區(qū)的,無狀態(tài)查詢協(xié)調(diào)器聚合來自遠(yuǎn)程分區(qū)子查詢的數(shù)據(jù)。
數(shù)據(jù)集是使用 ETL(提取,轉(zhuǎn)換,加載)創(chuàng)建的 批處理,然后以專有的列式數(shù)據(jù)庫格式存儲。最初成為 Einstein Analytics 的產(chǎn)品的查詢引擎和數(shù)據(jù)集創(chuàng)建工具是用 C 編寫的, 使用 Python 包裝器提供高級功能解析查詢、REST API 服務(wù)器、表達(dá)式引擎等等。
從本質(zhì)上講,該產(chǎn)品具有兩全其美的優(yōu)勢。Python 非常適合快速編寫更高級別的應(yīng)用程序,但并不總是能夠提供企業(yè)級所需的高性能。C 可以創(chuàng)建高性能的可執(zhí)行文件,但是添加功能會花費(fèi)更多時間。
轉(zhuǎn) Go 初體驗(yàn)
最初,這種組合是起作用的。但是,在開發(fā)該軟件多年之后,Einstein Analytics 開始出現(xiàn)性能下降問題。這是因?yàn)椴粚儆诤诵牟樵円娴暮芏喙δ芏急惶砑拥搅?Python 包裝器中。這種方式可以快速開發(fā)和部署功能,但是隨著時間的流逝,它們會拖累整個系統(tǒng)。Python 的多線程性能不是很好,因此要求包裝程序執(zhí)行的次數(shù)越多,其執(zhí)行效果就越差。
之前的團(tuán)隊已經(jīng)在考慮將包裝器移植到 Go 上,因此我們也做了一些研究。我們很快意識到,在企業(yè)級系統(tǒng)上,我們將面臨另外兩個問題。首先,Python 使用松散類型輸入,這對于一個快速開發(fā)新想法并將其投入生產(chǎn)的小型團(tuán)隊非常有用,但對于某些客戶為此付出數(shù)百萬美元的企業(yè)級應(yīng)用程序而言,卻不太合適。其次,我們預(yù)見到一個巨大的依賴噩夢即將來臨,因?yàn)椴渴鹫_的 Python 庫、版本和文件是一件苦差事。所以在 2014 年,我們決定移植 Python 包裝器到 Go 上。
最初,我們對年輕的 Go 生態(tài)系統(tǒng)持謹(jǐn)慎態(tài)度,但是當(dāng)我們研究過該語言的設(shè)計目標(biāo)后(轉(zhuǎn)到 Google:軟件工程服務(wù)中的語言設(shè)計)),它給我們留下了深刻的印象。它是為軟件工程而設(shè)計的,而不僅僅是語言的復(fù)雜性,因此它的優(yōu)勢包括可靠的內(nèi)置工具,快速的編譯和部署以及簡單的故障排除。
企業(yè)軟件面臨的現(xiàn)實(shí)問題是,與編寫代碼相比,需要花費(fèi)更多的時間閱讀代碼。我們感謝 Go 使代碼易于理解。在 Python 中,你可以編寫超級優(yōu)雅的列表推導(dǎo)式和幾乎是數(shù)學(xué)式的漂亮代碼。但是,如果你沒有參與編寫代碼,那么這種優(yōu)雅可能讓可讀性付出代價。
第一個項(xiàng)目進(jìn)展順利。我們對新項(xiàng)目的性能和可維護(hù)性感到非常滿意。我們遇到的為數(shù)不多的抱怨之一是,在選擇可伸縮性而不是原始性能來幫助它們進(jìn)行垃圾回收時,需要在語言上進(jìn)行權(quán)衡:他們決定開始將原始類型作為指針而不是值存儲在接口中,這為我們帶來了性能開銷和額外的分配。
全部遷移到 Go 上
這個體驗(yàn)是相當(dāng)好的,以至于在 2016 年編寫具有更好的優(yōu)化程序的新查詢引擎內(nèi)核并改進(jìn)我們的數(shù)據(jù)集創(chuàng)建工具時,我們決定使用 Go 進(jìn)行操作。我們獲得專業(yè)知識的速度與 Go 生態(tài)系統(tǒng)成熟的速度差不多,因此減少開銷并使我們的代碼在單一語言中可重用是有意義的。另外,我們希望消除 CGO 接口的開銷。
最大的不確定因素是性能。Go 在其 Goroutines 中 使用了異步 IO 的輕量級“ 綠色線程 ”模型,它為我們提供了優(yōu)于 Python 的多線程優(yōu)勢, 但是 C 代碼運(yùn)行的要多快就有多快——它用內(nèi)置的安全性來換取速度,加上 C 編譯器更成熟,有更好的優(yōu)化。我們的團(tuán)隊創(chuàng)建了一個概念驗(yàn)證(POC),它在性能上幾乎與 C 引擎相當(dāng),但前提是我們使用正確的編程模式:
緩沖所有 IO,以減少 Go 系統(tǒng)調(diào)用的開銷。在系統(tǒng)調(diào)用中,當(dāng)前 Goroutines 會讓步于該調(diào)用。
如果可能發(fā)生緊密循環(huán),請使用結(jié)構(gòu)代替接口,以最大程度地減少接口方法的間接開銷。
在緊密循環(huán)內(nèi)使用預(yù)分配的緩沖區(qū)(類似于 io.Reader 的 工作方式),以最大程度地減少垃圾收集壓力。
批量處理數(shù)據(jù)行是解決不良編譯器內(nèi)聯(lián)的一種解決方法,以使實(shí)際計算更接近數(shù)據(jù),并最大程度地減少每次函數(shù)調(diào)用的開銷。
2017 年我們完成了重寫,新的 Go 版本的 Einstein Analytics 在 2018 年正式投入使用。通過將所有內(nèi)容保持為同一語言,我們可以重用代碼并提高工作效率??缙脚_和可移植的潛力使移植代碼變得容易。如果我們需要在移動應(yīng)用程序中使用任何這些代碼,則可以將其交叉編譯到 iOS 或 Android,這樣就可以正常工作了。
之前,我說過該版本(幾乎)完全用 Go 編寫。但我們的集群管理器是一個例外,它看起來似乎有些奇怪,因?yàn)?Kubernetes 和其他類型的集群協(xié)調(diào)應(yīng)用程序是 Go 的最常見用法,但是負(fù)責(zé)此服務(wù)的團(tuán)隊對使用 Java 感到更自在。讓團(tuán)隊掌控自己的組件很重要;你不能強(qiáng)迫人們?nèi)プ鏊麄儾幌胱龅氖虑椤?/p>
盡管 Go 有一些必須解決的局限性,但我們對結(jié)果感到非常滿意。Go 還會繼續(xù)改進(jìn)。他們通過將其移至 靜態(tài)單一分配形式 來解決其編譯器中的某些缺陷,這使得進(jìn)行花式優(yōu)化變得更加容易。垃圾回收變得越來越高效,并且編譯器通常很智能,可以執(zhí)行轉(zhuǎn)義分析,以檢測何時可以廉價地在堆棧而不是堆上分配變量值。
作為開發(fā)人員,如果你想用任何語言編寫高性能代碼,你需要熟悉編譯器的工作方式。這不是語言的全部。Go 有一個非常簡單的參考文檔——只有兩頁!但是了解編譯器需要收集所有這些零散的知識,它詳細(xì)說明了你可以在所使用的特定版本的 Go 中使用的所有優(yōu)化。
經(jīng)過這些移植之后,我們的團(tuán)隊在 Go 及其編譯器技術(shù)方面積累了一定的專業(yè)知識。但是仍然還是會遇到一些問題。例如,你可以很容易地將數(shù)據(jù)寫入到 更便宜的堆棧中,而不是寫入到更昂貴的堆中。僅僅通過閱讀代碼,你甚至都不知道會發(fā)生這種情況。因此,與需要高性能的任何新語言一樣,你需要密切監(jiān)視進(jìn)程并創(chuàng)建有關(guān) CPU 和內(nèi)存使用情況的基準(zhǔn)。然后與社區(qū)分享你所學(xué)到的知識,以使這些知識變得不那么局部化。
結(jié) 論
選擇一種較新的語言并將其引入企業(yè)公司可能是一場賭博。幸運(yùn)的是,Go 生態(tài)系統(tǒng)與我們一同成長。Google 繼續(xù)支持該語言發(fā)展,并已被 其他很多大型公司 接納?,F(xiàn)在,我們擁有一支全職從事 Go 的工程師團(tuán)隊,并且我們繼續(xù)獲得了一些積極的成果。我們期待與 Go 社區(qū)一起成長,并分享我們從經(jīng)驗(yàn)中學(xué)到的更多知識。
Salesforce 相信支持 Go 之類的開源技術(shù)可以推動我們的行業(yè)向前發(fā)展,開啟新的職業(yè)生涯并建立對我們創(chuàng)建的產(chǎn)品的信任。
【責(zé)任編輯:張燕妮 TEL:(010)68476606】