基于Go技術(shù)棧的微服務(wù)構(gòu)建
在大型系統(tǒng)的微服務(wù)化構(gòu)建中,一個(gè)系統(tǒng)會(huì)被拆分成許多模塊。這些模塊負(fù)責(zé)不同的功能,組合成系統(tǒng),最終可以提供豐富的功能。在這種構(gòu)建形式中,開發(fā)者一般會(huì)聚焦于***程度解耦模塊的功能以減少模塊間耦合帶來的額外開發(fā)成本。同時(shí),微服務(wù)面臨著如何部署這些大量的服務(wù)系統(tǒng)、如何運(yùn)維這些系統(tǒng)等新問題。
本文的素材來源于我們?cè)陂_發(fā)中的一些***實(shí)踐案例,從開發(fā)、監(jiān)控、日志等角度介紹了一些我們基于Go技術(shù)棧的微服務(wù)構(gòu)建經(jīng)驗(yàn)。
開發(fā)
微服務(wù)的開發(fā)過程中,不同模塊由不同的開發(fā)者負(fù)責(zé),明確定義的接口有助于確定開發(fā)者的工作任務(wù)。最終的系統(tǒng)中,一個(gè)業(yè)務(wù)請(qǐng)求可能會(huì)涉及到多次接口調(diào)用,如何準(zhǔn)確清晰的調(diào)用遠(yuǎn)端接口,這也是一大挑戰(zhàn)。對(duì)于這些問題,我們使用了gRPC來負(fù)責(zé)協(xié)議的制訂和調(diào)用。
傳統(tǒng)的微服務(wù)通?;趆ttp協(xié)議來進(jìn)行模塊間的調(diào)用,而在我們的微服務(wù)構(gòu)建中,選用了Google推出的gRPC框架來進(jìn)行調(diào)用。下面這張簡表比較了http rpc框架與gRPC的特性:
gRPC的接口需要使用Protobuf3定義,通過靜態(tài)編譯后才能成功調(diào)用。這一特性減少了由于接口改變帶來的溝通成本。如果使用http rpc,接口改變就需要先改接口文檔,然后周知到調(diào)用者,如果調(diào)用者沒有及時(shí)修改,很可能會(huì)到服務(wù)運(yùn)行時(shí)才能發(fā)現(xiàn)錯(cuò)誤。而gRPC的這種模式,接口變動(dòng)引起的錯(cuò)誤保證在編譯時(shí)期就能消除。
在性能方面,gRPC相比傳統(tǒng)的http rpc協(xié)議有非常大的改善(根據(jù)這個(gè)評(píng)測(cè),gRPC要快10倍)。gRPC使用http 2協(xié)議進(jìn)行傳輸,相比較http 1.1, http 2復(fù)用tcp連接,減少了每次請(qǐng)求建立tcp連接的開銷。需要指出的是,如果單純追求性能,之前業(yè)界一般會(huì)選用構(gòu)建在tcp協(xié)議上的rpc協(xié)議(thrift等),但四層協(xié)議無法方便的做一些傳輸控制。相比而言,gRPC可以在http header中放入控制字段,配合nginx等代理服務(wù)器,可以很方便的實(shí)現(xiàn)轉(zhuǎn)發(fā)/灰度等功能。
接下來著重談?wù)勎覀冊(cè)趯?shí)踐中如何使用gRPC的一些特性來簡化相關(guān)開發(fā)流程。
1. 使用context來控制請(qǐng)求的生命周期
在gRPC的go語言實(shí)現(xiàn)中,每個(gè)rpc請(qǐng)求的***個(gè)參數(shù)都是context。http2協(xié)議會(huì)將context放在HEADER中,隨著鏈路傳遞下去,因此可以為每個(gè)請(qǐng)求設(shè)置過期時(shí)間,一旦遇到超時(shí)的情況,發(fā)起方就會(huì)結(jié)束等待,返回錯(cuò)誤。
- ctx := context.Background() // blank context
- ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
- defer cancel( )
- grpc.CallServiveX(ctx, arg1)
上述這段代碼,發(fā)起方設(shè)置了大約5s的等待時(shí)間,只要遠(yuǎn)端的調(diào)用在5s內(nèi)沒有返回,發(fā)起方就會(huì)報(bào)錯(cuò)。
除了能加入超時(shí)時(shí)間,context還能加入其他內(nèi)容,下文我們還會(huì)見到context的另一個(gè)妙用。
2.使用TLS實(shí)現(xiàn)訪問權(quán)限控制
gRPC集成了TLS證書功能,為我們提供了很完善的權(quán)限控制方案。在實(shí)踐中,假設(shè)我們的系統(tǒng)中存在服務(wù)A,由于它負(fù)責(zé)操作用戶的敏感內(nèi)容,因此需要保證A不被系統(tǒng)內(nèi)的其他服務(wù)濫用。為了避免濫用,我們?cè)O(shè)計(jì)了一套自簽名的二級(jí)證書系統(tǒng),服務(wù)A掌握了自簽名的根證書,同時(shí)為每個(gè)調(diào)用A的服務(wù)頒發(fā)一個(gè)二級(jí)證書。這樣,所有調(diào)用A的服務(wù)必須經(jīng)過A的授權(quán),A也可以鑒別每個(gè)請(qǐng)求的調(diào)用方,這樣可以很方便的做一些記錄日志、流量控制等操作。
3. 使用trace在線追蹤請(qǐng)求
gRPC內(nèi)置了一套追蹤請(qǐng)求的trace系統(tǒng),既可以追蹤最近10個(gè)請(qǐng)求的詳細(xì)日志信息,也可以記錄所有請(qǐng)求的統(tǒng)計(jì)信息。
當(dāng)我們?yōu)檎?qǐng)求加入了trace日志后,trace系統(tǒng)會(huì)為我們記錄下最近10個(gè)請(qǐng)求的日志,下圖中所示的例子就是在trace日志中加入了對(duì)業(yè)務(wù)數(shù)據(jù)的追蹤。
在宏觀上,trace系統(tǒng)為我們記錄下請(qǐng)求的統(tǒng)計(jì)信息,比如請(qǐng)求數(shù)目、按照不同請(qǐng)求時(shí)間統(tǒng)計(jì)的分布等。
需要說明的是,這套系統(tǒng)暴露了一個(gè)http服務(wù),我們可以通過debug開關(guān)在運(yùn)行時(shí)按需打開或者關(guān)閉,以減少資源消耗。
監(jiān)控
1.確定監(jiān)控指標(biāo)
在接到為整個(gè)系統(tǒng)搭建監(jiān)控系統(tǒng)這個(gè)任務(wù)時(shí),我們面對(duì)的***個(gè)問題是要監(jiān)控什么內(nèi)容。針對(duì)這個(gè)問題,GoogleSRE這本書提供了很詳細(xì)的回答,我們可以監(jiān)控四大黃金指標(biāo),分別是延時(shí)、流量、錯(cuò)誤和飽和度。
延時(shí)衡量了請(qǐng)求花費(fèi)的時(shí)間。需要注意的,考慮到長尾效應(yīng),使用平均延時(shí)作為延時(shí)方面的單一指標(biāo)是遠(yuǎn)遠(yuǎn)不夠的。相應(yīng)的,我們需要延時(shí)的中位數(shù)90%、95%、99%值來幫助我們了解延時(shí)的分布,有一種更好的辦法是使用直方圖來統(tǒng)計(jì)延時(shí)分布。
流量衡量了服務(wù)面臨的請(qǐng)求壓力。針對(duì)每個(gè)API的流量統(tǒng)計(jì)能讓我們知道系統(tǒng)的熱點(diǎn)路徑,幫助優(yōu)化。
錯(cuò)誤監(jiān)控是指對(duì)錯(cuò)誤的請(qǐng)求結(jié)果的統(tǒng)計(jì)。同樣的,每個(gè)請(qǐng)求有不同的錯(cuò)誤碼,我們需要針對(duì)不同的錯(cuò)誤碼進(jìn)行統(tǒng)計(jì)。配合上告警系統(tǒng),這類監(jiān)控能讓我們盡早感知錯(cuò)誤,進(jìn)行干預(yù)。
飽和度主要指對(duì)系統(tǒng)CPU和內(nèi)存的負(fù)載監(jiān)控。這類監(jiān)控能為我們的擴(kuò)容決策提供依據(jù)。
2.監(jiān)控選型
選擇監(jiān)控方案時(shí),我們面臨的選擇主要有兩個(gè),一是公司自建的監(jiān)控系統(tǒng),二是使用開源Prometheus系統(tǒng)搭建。這兩個(gè)系統(tǒng)的區(qū)別列在下表中。
考慮到我們的整個(gè)系統(tǒng)大約有100個(gè)容器分布在30臺(tái)虛擬機(jī)上,Prometheus的單機(jī)存儲(chǔ)對(duì)我們并不是瓶頸。我們不需要完整保留歷史數(shù)據(jù),自建系統(tǒng)的***優(yōu)勢(shì)也不足以吸引我們使用。相反,由于希望能夠統(tǒng)計(jì)四大黃金指標(biāo)延生出的諸多指標(biāo),Prometheus方便的DSL能夠很大程度上簡化我們的指標(biāo)設(shè)計(jì)。
最終,我們選擇了Prometheus搭建監(jiān)控系統(tǒng)。整個(gè)監(jiān)控系統(tǒng)的框架如下圖所示。
各服務(wù)將自己的地址注冊(cè)到consul中,Prometheus會(huì)自動(dòng)從consul中拉取需要監(jiān)控的目標(biāo)地址,然后從這些服務(wù)中拉取監(jiān)控?cái)?shù)據(jù),存放到本地存儲(chǔ)中。在Prometheus自帶的Web UI中可以快捷的使用PromQL查詢語句獲取統(tǒng)計(jì)信息,同時(shí),還可以將查詢語句輸入grafana,固定監(jiān)控指標(biāo)用于監(jiān)控。
此外,配合插件AlertManager,我們能夠編寫告警規(guī)則,當(dāng)系統(tǒng)出現(xiàn)異常時(shí),將告警發(fā)送到手機(jī)/郵件/信箱。
日志
1.日志格式
一個(gè)經(jīng)常被忽略的問題是如何選擇日志記錄的格式。良好的日志格式有利于后續(xù)工具對(duì)日志內(nèi)容的切割,便于日志存儲(chǔ)的索引。我們使用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”}
2.端到端鏈路上的調(diào)用日志收集
在微服務(wù)架構(gòu)中,一個(gè)業(yè)務(wù)請(qǐng)求會(huì)經(jīng)歷多個(gè)服務(wù),收集端到端鏈路上的日志能夠幫助我們判斷錯(cuò)誤發(fā)生的具體位置。在這個(gè)系統(tǒng)中,我們?cè)谡?qǐng)求入口處,生成了全局ID,通過gRPC中的context將ID在鏈路中傳遞。將不同服務(wù)的日志收集到graylog中,查詢時(shí)就能通過一個(gè)ID,將整個(gè)鏈路上的日志查詢出來。
上圖中,使用session-id來作為整個(gè)調(diào)用鏈的ID可以進(jìn)行全鏈路檢索。
小結(jié)
微服務(wù)構(gòu)建的系統(tǒng)中,在部署、調(diào)度、服務(wù)發(fā)現(xiàn)、一致性等其他方面都有挑戰(zhàn),Go技術(shù)棧在這些方面都有***實(shí)踐(docker,k8s,consul,etcd等等)。具體內(nèi)容在網(wǎng)上已經(jīng)有很完善的教程,在此不用班門弄斧,有需要的可以自行查閱。