经济学人使用Golang构建微服务历程回顾
關(guān)鍵點(diǎn)
-
經(jīng)濟(jì)學(xué)人內(nèi)容分發(fā)系統(tǒng)需要更大的靈活性,將內(nèi)容傳遞給日益多樣化的數(shù)字渠道。為了實(shí)現(xiàn)這一靈活性目標(biāo)并保持高水平的性能和可靠性,平臺(tái)從一個(gè)單體結(jié)構(gòu)過渡到微服務(wù)體系結(jié)構(gòu)。
-
用Go編寫的服務(wù)是新系統(tǒng)的一個(gè)關(guān)鍵組件,它使得團(tuán)隊(duì)能夠交付可伸縮的、高性能的服務(wù)并快速迭代新產(chǎn)品。
-
Go的并發(fā)性和對(duì)API的支持以及它作為靜態(tài)編譯語言的設(shè)計(jì),使得分布式事件系統(tǒng)能夠大規(guī)模執(zhí)行。與此同時(shí),Go對(duì)于測(cè)試的支持也非常出色。
-
總的來說,團(tuán)隊(duì)在Go上的使用經(jīng)驗(yàn)是積極的,這也是內(nèi)容平臺(tái)得以擴(kuò)展的關(guān)鍵因素之一。
?
隨著新聞消費(fèi)從紙媒轉(zhuǎn)向數(shù)字媒體,《經(jīng)濟(jì)學(xué)人》的使命是讓更廣泛的數(shù)字受眾看到這種技術(shù)轉(zhuǎn)變。因此需要更大的靈活性,將內(nèi)容傳遞給日益多樣化的數(shù)字渠道。為了實(shí)現(xiàn)這一靈活性目標(biāo)并保持高水平的性能和可靠性,平臺(tái)從一個(gè)單體結(jié)構(gòu)走向微服務(wù)體系結(jié)構(gòu)。用Go編寫的服務(wù)是新系統(tǒng)的一個(gè)關(guān)鍵組件,它將使團(tuán)隊(duì)能夠交付可伸縮的、高性能的服務(wù)并快速迭代新產(chǎn)品。
?
以下是基于Go的一些實(shí)踐與問題:
-
允許工程師快速迭代產(chǎn)品并開發(fā)新特性
-
強(qiáng)化智能錯(cuò)誤處理并針對(duì)服務(wù)快速失敗的實(shí)踐
-
為分布式系統(tǒng)中的高并發(fā)和網(wǎng)絡(luò)提供有力支持
-
在內(nèi)容和媒體所需的領(lǐng)域缺乏成熟度和支持
-
通過平臺(tái)實(shí)現(xiàn)規(guī)模化的數(shù)字內(nèi)容出版發(fā)行
?
為什么使用Go?
為了回答這個(gè)問題,先看看新平臺(tái)的總體架構(gòu)是很有幫助的。這個(gè)平臺(tái)稱為內(nèi)容平臺(tái),是一個(gè)基于事件的系統(tǒng)。它響應(yīng)來自不同內(nèi)容創(chuàng)作平臺(tái)的事件,并觸發(fā)獨(dú)立的微服務(wù)處理這些流程。這些服務(wù)的功能包括數(shù)據(jù)標(biāo)準(zhǔn)化、語義標(biāo)簽分析、ES索引,以及將內(nèi)容推送到蘋果新聞或Facebook等外部平臺(tái)。該平臺(tái)還有一個(gè)RESTful API,它與GraphQL相結(jié)合,是前端客戶端和產(chǎn)品的主要入口。
?
在設(shè)計(jì)總體架構(gòu)時(shí),團(tuán)隊(duì)研究了哪些語言適合平臺(tái)的需求。將Go與Python、Ruby、Node、PHP和Java進(jìn)行比較。雖然每種語言都有其優(yōu)點(diǎn),但最好與平臺(tái)的體系結(jié)構(gòu)保持一致。Go的并發(fā)性和API支持以及它作為靜態(tài)編譯語言的設(shè)計(jì)將使分布式事件系統(tǒng)能夠大規(guī)模執(zhí)行。此外,Go相對(duì)簡(jiǎn)單的語法使學(xué)習(xí)和開始編寫工作代碼變得很容易,這對(duì)于一個(gè)經(jīng)歷了如此多技術(shù)轉(zhuǎn)換的團(tuán)隊(duì)來說是一個(gè)好消息。總的來說,Go被認(rèn)為是分布式云系統(tǒng)中可用性和效率的最佳設(shè)計(jì)語言。
?
Go的目標(biāo)?
平臺(tái)設(shè)計(jì)的幾個(gè)元素與Go語言很好地結(jié)合在一起。快速失敗是系統(tǒng)的關(guān)鍵部分,因?yàn)橄到y(tǒng)本身是由分布式的、獨(dú)立的服務(wù)組成的。按照應(yīng)用程序的12個(gè)因素原則,應(yīng)用程序需要快速啟動(dòng)和失敗。Go作為一種靜態(tài)編譯語言的先天在快速啟動(dòng)上有優(yōu)勢(shì),并且隨著編譯器的性能不斷提高,對(duì)于工程或部署來說從來都不是問題。此外,Go的錯(cuò)誤處理從設(shè)計(jì)上不僅允許應(yīng)用程序快速失敗,還允許應(yīng)用程序更智能地失敗。
?
錯(cuò)誤處理
Go與其他語言相比有一個(gè)明顯的區(qū)別,它沒有異常,而是用一個(gè)錯(cuò)誤類型代替。在Go中,所有錯(cuò)誤都是值。錯(cuò)誤類型是預(yù)先聲明的,是一個(gè)接口。Go中的接口本質(zhì)上是一個(gè)命名的方法集合,如果它具有相同的方法,那么任何其他自定義類型都可以滿足該接口。錯(cuò)誤類型是一個(gè)可以用字符串描述自身的接口。
typeerror interface {
??? Error() string
}
Go為工程師提供了更好的控制錯(cuò)誤處理功能。通過在定制模塊中添加返回字符串的Error方法,可以創(chuàng)建定制錯(cuò)誤,如下面的函數(shù)所示,該函數(shù)來自errors包。
typeerrorString struct {
??? s string
}
?
func(e *errorString) Error() string {
??? return e.s
}
?
在Go中,函數(shù)允許多個(gè)返回值,因此如果函數(shù)可能失敗,它很可能返回一個(gè)錯(cuò)誤值。這種語言鼓勵(lì)開發(fā)人員顯式地檢查錯(cuò)誤發(fā)生的地方(而不是拋出和捕獲異常),因此代碼通常會(huì)有一個(gè)“if err != nil”檢查。在分布式系統(tǒng)中,可以通過包裝錯(cuò)誤輕松地啟用重試。
網(wǎng)絡(luò)問題總是會(huì)在系統(tǒng)中遇到,無論是向其他內(nèi)部服務(wù)發(fā)送數(shù)據(jù),還是向第三方工具推送數(shù)據(jù)。這個(gè)來自Net包的示例強(qiáng)調(diào)了如何利用錯(cuò)誤作為一種類型來區(qū)分臨時(shí)網(wǎng)絡(luò)錯(cuò)誤和永久網(wǎng)絡(luò)錯(cuò)誤。當(dāng)將內(nèi)容推送到外部api時(shí),團(tuán)隊(duì)使用類似的錯(cuò)誤包裝來構(gòu)建增量重試。
?
package net
typeError interface {
??? error
??? Timeout() bool?? // Is the error a timeout?
??? Temporary() bool // Is the error temporary?
}
?
ifnerr, ok := err.(net.Error); ok && nerr.Temporary() {
??? time.Sleep(1e9)
??? continue
}
?
iferr != nil {
??? log.Fatal(err)
}
Go的作者認(rèn)為并非所有異常都是例外。鼓勵(lì)工程師明智地從錯(cuò)誤中恢復(fù),而不是讓應(yīng)用程序失敗。此外,Go錯(cuò)誤處理允許您對(duì)錯(cuò)誤進(jìn)行更多的控制。在內(nèi)容平臺(tái)中,Go的這個(gè)設(shè)計(jì)特性使開發(fā)人員能夠圍繞錯(cuò)誤做出深思熟慮的決策,從而增強(qiáng)了整個(gè)系統(tǒng)的可靠性。
?
一致性
一致性是內(nèi)容平臺(tái)中的一個(gè)關(guān)鍵因素。內(nèi)容是業(yè)務(wù)的核心,而內(nèi)容平臺(tái)的目標(biāo)是確保內(nèi)容可以發(fā)布一次并可以到處閱讀。因此,每個(gè)產(chǎn)品和消費(fèi)者都必須具有內(nèi)容平臺(tái)API的一致性。產(chǎn)品主要使用GraphQL查詢API,這需要一個(gè)靜態(tài)模式作為消費(fèi)者和平臺(tái)之間的契約。平臺(tái)處理的內(nèi)容需要符合這一模式。靜態(tài)語言有助于實(shí)現(xiàn)這一點(diǎn),并在確保數(shù)據(jù)一致性方面能輕松取勝。
?
Go與測(cè)試
另一個(gè)提高一致性的特性是Go的測(cè)試包。Go的快速編譯能力和易于測(cè)試的特性相結(jié)合,使團(tuán)隊(duì)能夠?qū)?qiáng)大的測(cè)試實(shí)踐嵌入到工作流和構(gòu)建Pipeline中。Go的測(cè)試工具使它們易于安裝和運(yùn)行。運(yùn)行“go test”將在當(dāng)前目錄中運(yùn)行所有測(cè)試,測(cè)試命令有幾個(gè)有用的特性標(biāo)志。“Cover”標(biāo)志提供關(guān)于代碼覆蓋率的詳細(xì)報(bào)告。“bench”測(cè)試運(yùn)行基準(zhǔn)測(cè)試。TestMain函數(shù)為額外的測(cè)試提供便利的功能,例如模擬身份驗(yàn)證服務(wù)器。
?
此外,Go能夠使用匿名結(jié)構(gòu)創(chuàng)建表測(cè)試,并使用接口創(chuàng)建模擬,從而提高測(cè)試覆蓋率。盡管就語言特性而言,測(cè)試并不是什么新鮮事,但是Go使得編寫健壯的測(cè)試并將其無縫地嵌入工作流變得很容易。從一開始,工程師們就能夠在構(gòu)建Pipeline的過程中運(yùn)行測(cè)試,而不需要進(jìn)行特殊的定制。
然而,該項(xiàng)目在實(shí)現(xiàn)一致性方面并非沒有困難。該平臺(tái)面臨的第一個(gè)主要挑戰(zhàn)是從不可預(yù)知的后端管理動(dòng)態(tài)內(nèi)容。該平臺(tái)主要通過JSON端點(diǎn)(Endpoint)使用來自CMS系統(tǒng)的內(nèi)容,而JSON端點(diǎn)不能保證數(shù)據(jù)結(jié)構(gòu)和類型。這意味著平臺(tái)不能使用Go的標(biāo)準(zhǔn)編碼json包,該包支持將json解組到結(jié)構(gòu)(Struct)中,但是如果結(jié)構(gòu)字段和傳入的數(shù)據(jù)字段類型不匹配,就會(huì)出現(xiàn)異常。
?
為了克服這個(gè)挑戰(zhàn),需要一種將后端映射到標(biāo)準(zhǔn)格式的自定義方法。在對(duì)該方法進(jìn)行了幾次迭代之后,團(tuán)隊(duì)實(shí)現(xiàn)了一個(gè)自定義的數(shù)據(jù)編出流程。雖然這種方法感覺有點(diǎn)像重新構(gòu)建標(biāo)準(zhǔn)的lib包,但它為工程師提供了處理源數(shù)據(jù)的細(xì)粒度控制。
?
網(wǎng)絡(luò)支持
可伸縮性是新平臺(tái)關(guān)注的焦點(diǎn),Go的網(wǎng)絡(luò)和api標(biāo)準(zhǔn)庫支持可伸縮性。在Go中,可以在不需要框架的情況下快速實(shí)現(xiàn)可伸縮的HTTP端點(diǎn)入口。在下面的示例中,標(biāo)準(zhǔn)庫net/http包用于接受用戶請(qǐng)求并響應(yīng)。當(dāng)內(nèi)容平臺(tái)實(shí)施時(shí),首先嘗試使用了一個(gè)API框架。隨著團(tuán)隊(duì)認(rèn)識(shí)到標(biāo)準(zhǔn)庫能夠滿足所有的網(wǎng)絡(luò)需求而又不增加額外的負(fù)擔(dān),最終標(biāo)準(zhǔn)庫取代了該框架。Golang HTTP處理程序是可伸縮的,因?yàn)樘幚沓绦蛏系拿總€(gè)請(qǐng)求都在一個(gè)輕量級(jí)線程Goroutine中并發(fā)運(yùn)行。
?
package main
import(
??? "fmt"
??? "log"
??? "net/http"
)
?
funchandler(w http.ResponseWriter, r *http.Request) {
??? fmt.Fprintf(w, "Hello World!")
}
?
funcmain() {
??? http.HandleFunc("/", handler)
???log.Fatal(http.ListenAndServe(":8080", nil))
}
?
并發(fā)模型
Go的并發(fā)模型提供了跨平臺(tái)的性能改進(jìn)。處理分布式數(shù)據(jù)意味著要與向消費(fèi)者承諾的保證作斗爭(zhēng)。根據(jù)CAP定理,不可能同時(shí)提供以下三個(gè)保證中的兩個(gè)以上:一致性、可用性、分區(qū)容忍。在經(jīng)濟(jì)學(xué)人的平臺(tái)上,最終的一致性是可以接受的,這意味著來自數(shù)據(jù)源的讀取最終是一致的,所有數(shù)據(jù)源達(dá)到一致狀態(tài)的適度延遲是可以容忍的。縮小這種差距的方法之一是利用Goroutines。
?
Goroutines是Go運(yùn)行時(shí)管理的輕量級(jí)線程,用于防止線程耗盡。Goroutines支持跨平臺(tái)優(yōu)化異步任務(wù)。例如,該平臺(tái)的數(shù)據(jù)存儲(chǔ)之一是Elasticsearch。當(dāng)內(nèi)容在系統(tǒng)中更新時(shí),在Elasticsearch中引用該項(xiàng)目的內(nèi)容將被更新并重新索引。通過實(shí)現(xiàn)Goroutines,減少了再處理時(shí)間,確保項(xiàng)目的一致性更快。這個(gè)示例演示了在Goroutine中如何對(duì)每個(gè)符合再處理?xiàng)l件的項(xiàng)進(jìn)行再處理。
?
funcreprocess(searchResult *http.Response) (int, error) {
?????????????? responses := make([]response,len(searchResult.Hits))
?????????????? var wg sync.WaitGroup
?????????????? wg.Add(len(responses))
??????????????
?????????????? for i, hit := rangesearchResult.Hits {
?????????????????????????????? wg.Add(1)
?????????????????????????????? go func(i int,item elastic.SearchHit) {
????????????????????????????????????????????? deferwg.Done()
????????????????????????????????????????????? code,err := reprocessItem(item)
????????????????????????????????????????????? responses[i].code= code
????????????????????????????????????????????? responses[i].err= err
?????????????????????????????? }(i, *hit)
?????????????? }
?????????????? wg.Wait?
?????????????? return http.StatusOK, nil
}
設(shè)計(jì)系統(tǒng)不僅僅是簡(jiǎn)單的編程,工程師必須了解在何時(shí)何地使用哪些工具。雖然Go對(duì)于內(nèi)容平臺(tái)的大多數(shù)需求來說是一個(gè)強(qiáng)大的工具,但某些局限性需要其他解決方案。
?
依賴管理
Go發(fā)布時(shí)沒有依賴管理系統(tǒng),社區(qū)內(nèi)有一些工具來滿足這種需求。經(jīng)濟(jì)學(xué)人使用Git子模塊,整個(gè)社區(qū)與此同時(shí)也正在積極推動(dòng)一個(gè)標(biāo)準(zhǔn)的依賴管理工具。雖然社區(qū)更建議采用一致的方法進(jìn)行依賴關(guān)系管理,但仍有許多分歧。在經(jīng)濟(jì)學(xué)人內(nèi)部使用Git子模塊進(jìn)行依賴管理并沒有帶來重大的挑戰(zhàn),但對(duì)其他Go開發(fā)者來說,它是一個(gè)需要加以考慮的因素。
?
還有一些平臺(tái)需求是Go的功能或設(shè)計(jì)不太適合的。由于平臺(tái)增加了對(duì)音頻處理的支持,Go的元數(shù)據(jù)提取工具在當(dāng)時(shí)是有限的,因此團(tuán)隊(duì)選擇了Python的Exiftool。平臺(tái)服務(wù)在docker容器中運(yùn)行,這也允許了安裝Exiftool并從Go應(yīng)用程序調(diào)用它。
?
funcrunExif(args []string) ([]byte, error) {
?????????????? cmdOut, err :=exec.Command("exiftool", args...).Output()
?????????????? if err != nil {
?????????????????????????????? return nil, err
?????????????? }
?????????????? return cmdOut, nil
}
該平臺(tái)的另一個(gè)常見場(chǎng)景是從源CMS系統(tǒng)接收損壞的HTML,將HTML解析為有效的,并對(duì)HTML進(jìn)行清洗。Go最初用于此過程,但由于Go標(biāo)準(zhǔn)HTML庫期望得到有效的HTML輸入,因此需要大量定制代碼來解析HTML輸入。這段代碼很快變得不堪重負(fù),對(duì)于邊緣例外情況無法有效處理。Javascript實(shí)現(xiàn)了一種新的解決方案,為管理HTML驗(yàn)證和清洗提供了更大的靈活性和適應(yīng)性。
?
Javascript也是平臺(tái)中事件過濾和路由的常見選擇。事件使用AWS Lambdas進(jìn)行過濾,AWS Lambdas是輕量級(jí)函數(shù)。一個(gè)用例是將事件過濾到不同的通道中,例如快速通道和慢通道。此篩選基于事件包裝器JSON對(duì)象中的單個(gè)元數(shù)據(jù)字段完成。過濾實(shí)現(xiàn)利用Javascript JSON指針包抓取JSON對(duì)象中的元素。與Go所需的完整JSON解組相比,這種方法要有效得多。雖然這種類型的功能可以通過Go實(shí)現(xiàn),但是對(duì)于工程師來說,使用Javascript更容易,并且提供了更簡(jiǎn)單的Lambdas。
?
回顧
在實(shí)現(xiàn)了內(nèi)容平臺(tái)的實(shí)施并生產(chǎn)上線運(yùn)行之后,對(duì)于這一歷程回顧如下:
好的地方?
-
分布式系統(tǒng)的關(guān)鍵語言設(shè)計(jì)元素
-
并發(fā)模型,相對(duì)容易實(shí)現(xiàn)
-
愉快的編碼和有趣的社區(qū)
不足的地方?
-
版本控制和標(biāo)準(zhǔn)方面的需要進(jìn)一步提升
-
在某些領(lǐng)域缺乏成熟的解決方案
?
?
總的來說,使用Go來快速構(gòu)建系統(tǒng)是一種積極的體驗(yàn),Go是內(nèi)容平臺(tái)擴(kuò)展項(xiàng)目成功的關(guān)鍵元素之一。經(jīng)濟(jì)學(xué)人是一個(gè)多語言的平臺(tái)(注:此處指編程語言),在合適的地方使用不同的語言來解決特定問題。例如,在處理文本和動(dòng)態(tài)內(nèi)容時(shí),Go可能永遠(yuǎn)不會(huì)是首選,所以團(tuán)隊(duì)將Javascript納入工具集中。然而,Go在支持系統(tǒng)擴(kuò)展和發(fā)展起到主要作用。
?
在考慮是否要使用Go時(shí),可以考慮一下系統(tǒng)設(shè)計(jì)的關(guān)鍵問題:
-
系統(tǒng)目標(biāo)是什么?
-
你為你的消費(fèi)者提供了什么保證與承諾?
-
什么樣的架構(gòu)體系和模式適合你的系統(tǒng)?
-
系統(tǒng)需要如何擴(kuò)展?
如果你正在設(shè)計(jì)一個(gè)旨在解決分布式數(shù)據(jù)、異步工作流、高性能和可伸縮性挑戰(zhàn)的系統(tǒng),可以考慮使用Go來加速構(gòu)建并達(dá)成系統(tǒng)目標(biāo)。
?
原文作者:KathrynJonas??譯者:江瑋
英文原文:https://www.infoq.com/articles/golang-the-economist?utm_source=infoq&utm_medium=popular_widget&utm_campaign=popular_content_list&utm_content=
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/10305382.html
總結(jié)
以上是生活随笔為你收集整理的经济学人使用Golang构建微服务历程回顾的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何做自己的服务监控?spring bo
- 下一篇: Apache Kafka: 优化部署的1