導(dǎo)讀: 網(wǎng)易考拉(以下簡稱考拉)是網(wǎng)易旗下以跨境業(yè)務(wù)為主的綜合型電商,自2015年1月9日上線公測后,業(yè)務(wù)保持了高速增長,這背后離不開其技術(shù)團(tuán)隊(duì)的支撐。微服務(wù)化是電商IT架構(gòu)演化的必然趨勢,網(wǎng)易考拉的服務(wù)架構(gòu)演進(jìn)也經(jīng)歷了從單體應(yīng)用走向微服務(wù)化的整個(gè)過程,以下整理自網(wǎng)易考拉陶楊在近期Apache Dubbo Meetup上的分享,通過該文,您將了解到:
考拉架構(gòu)的演進(jìn)過程 考拉在2015年初上線的時(shí)候,線上只有七個(gè)工程,商品詳情頁、購物車下單頁等都耦合在中間這個(gè)online的工程里面。 在上線之初的時(shí)候,這種架構(gòu)還是比較有優(yōu)勢的,因?yàn)楫?dāng)時(shí)考拉的開發(fā)人員也不是很多,把所有的功能都耦合在一個(gè)進(jìn)程里面,利于集中開發(fā)、測試和上線,是一種比較高效和節(jié)省成本的方式。 但是隨著業(yè)務(wù)的不斷發(fā)展,包括需求的逐步增多,開發(fā)團(tuán)隊(duì)的不斷擴(kuò)容,這時(shí)候,單體架構(gòu)的一些劣勢就逐漸的暴露出來了,例如開發(fā)效率低:功能之間的相互耦合,不同需求的不同分支也經(jīng)常會修改同一塊代碼,導(dǎo)致合代碼的過程非常痛苦,而且經(jīng)常會出問題。
再例如上線成本高:幾乎所有的發(fā)布需求都會涉及到這些應(yīng)用的上線,同時(shí)不斷增長的業(yè)務(wù)需求,也會使得我們的代碼越來越臃腫,造成維護(hù)困難、可用性差,功能之間相互耦合,都耦合在一個(gè)進(jìn)程里面,導(dǎo)致一旦某一個(gè)業(yè)務(wù)需求涉及的代碼或者資源出現(xiàn)問題,那么就會影響其他的業(yè)務(wù)。比如說我們曾經(jīng)在online工程里面,因?yàn)閮?yōu)惠券兌換熱點(diǎn)的問題,影響了核心的下單服務(wù)。
這個(gè)架構(gòu)在考拉運(yùn)行的4到5個(gè)月的時(shí)間里,從開發(fā)到測試再到上線,大家都特別痛苦。所以我們就開始進(jìn)行了服務(wù)化拆分的工作。 這個(gè)是考拉現(xiàn)在的分布式服務(wù)架構(gòu)。伴隨著服務(wù)化的拆分,我們的組織架構(gòu)也進(jìn)行了很多調(diào)整,出現(xiàn)了商品中心、用戶中心和訂單中心等等。拆分其實(shí)是由業(yè)務(wù)驅(qū)動的,通過業(yè)務(wù)來進(jìn)行一些橫向拆分或者縱向拆分,同時(shí),拆分也會面對一個(gè)拆分粒度的問題,比如怎么才算一個(gè)服務(wù),或者說服務(wù)拆的過細(xì),是不是會導(dǎo)致我們管理成本過高,又或者說是否會帶來架構(gòu)上的新問題。
考拉的拆分由粗到細(xì)是一個(gè)逐步演進(jìn)的過程。隨著服務(wù)化的拆分,使得服務(wù)架構(gòu)越來越復(fù)雜,隨之而來產(chǎn)生了各種各樣的公共技術(shù),比如說服務(wù)治理、平臺配置中心、分布式事務(wù)和分布式定時(shí)任務(wù)等等。 考拉的服務(wù)化實(shí)踐 微服務(wù)框架在服務(wù)化中起到了很重要的作用,是服務(wù)化改造的基石,經(jīng)過嚴(yán)格的技術(shù)選型流程后,我們選用了Dubbo來作為考拉服務(wù)改造的一個(gè)重要支柱。Dubbo可以解決服務(wù)化過程中服務(wù)的定義、服務(wù)的注冊與發(fā)現(xiàn)、服務(wù)的調(diào)用和路由等問題,此外,Dubbo也具有一些服務(wù)治理的功能和服務(wù)監(jiān)控的功能。下面我將介紹考拉基于Dubbo做的一些服務(wù)化實(shí)踐。 首先來說一下 熔斷。 在進(jìn)行服務(wù)化拆分之后,應(yīng)用中原有的本地調(diào)用就會變成遠(yuǎn)程調(diào)用,這樣就引入了更多的復(fù)雜性。比如說服務(wù)A依賴于服務(wù)B,這個(gè)過程中可能會出現(xiàn)網(wǎng)絡(luò)抖動、網(wǎng)絡(luò)異常,或者說服務(wù)B變得不可用或者不好用時(shí),也會影響到A的服務(wù)性能,甚至可能會使得服務(wù)A占滿整個(gè)線程池,導(dǎo)致這個(gè)應(yīng)用上其它的服務(wù)也受影響,從而引發(fā)更嚴(yán)重的雪崩效應(yīng)。
因此,服務(wù)之間有這樣一種依賴關(guān)系之后,需要意識到服務(wù)的依賴其實(shí)是不穩(wěn)定的。此時(shí),需要通過采取一些服務(wù)治理的措施,例如熔斷、降級、限流、隔離和超時(shí)等,來保障應(yīng)用不被外部的異常拖垮。Dubbo提供了降級的特性,比如可以通過mock參數(shù)來配置一些服務(wù)的失敗降級或者強(qiáng)制降級,但是Dubbo缺少自動熔斷的特性,所以我們在Dubbo上引入了Hystrix。 消費(fèi)者在進(jìn)行服務(wù)調(diào)用的時(shí)候會經(jīng)過熔斷器,當(dāng)服務(wù)提供者出現(xiàn)異常的時(shí)候,比如暫時(shí)性的不可用,熔斷器就會打開,對消費(fèi)端進(jìn)行調(diào)用短路,此時(shí),消費(fèi)端就不會再發(fā)起遠(yuǎn)程調(diào)用,而是直接走向降級邏輯。與此同時(shí),消費(fèi)端會持續(xù)的探測服務(wù)的可用性,一旦服務(wù)恢復(fù),熔斷器就會關(guān)閉,重新恢復(fù)調(diào)用。在Dubbo的服務(wù)治理平臺上,可以對Hystrix上運(yùn)行的各種動態(tài)參數(shù)進(jìn)行動態(tài)的配置,包括是否允許自動熔斷,是否要強(qiáng)制熔斷,熔斷的失敗率和時(shí)間窗口等等。 下面再說一下 限流。 當(dāng)用戶的請求量,調(diào)用超過系統(tǒng)可承受的并發(fā)時(shí)系統(tǒng)QPS會降低、出現(xiàn)不可用甚至存在宕機(jī)的風(fēng)險(xiǎn)。這就需要一個(gè)機(jī)制來保護(hù)我們的系統(tǒng),當(dāng)預(yù)期并發(fā)超過系統(tǒng)可承受的范圍時(shí),進(jìn)行快速失敗、直接返回,以保護(hù)系統(tǒng)。
Dubbo提供了一些基礎(chǔ)的限流特性,例如可以通過信號量的配置來限制我們消費(fèi)者的調(diào)用并發(fā),或者限制提供者的執(zhí)行并發(fā)。但是這些是遠(yuǎn)遠(yuǎn)不夠的,考拉自研了限流框架NFC,并基于Dubbo filter 的形式,實(shí)現(xiàn)了對Dubbo的支持,同時(shí)也支持對URL等其他資源的限流。通過配置中心動態(tài)獲取流控規(guī)則,對于資源的請求,比如Dubbo調(diào)用會經(jīng)過流控客戶端,進(jìn)行處理并判斷是否觸發(fā)限流,一旦請求超出定義的閾值,就會快速失敗。 同時(shí),這些限流的結(jié)果會上報(bào)到監(jiān)控平臺。上圖中的頁面就是考拉流控平臺的一個(gè)監(jiān)控頁面,我們在頁面上可以對每一個(gè)資源(URL、Dubbo接口)進(jìn)行一個(gè)閾值的配置,并對限流進(jìn)行準(zhǔn)實(shí)時(shí)監(jiān)控,包括流控比率、限流次數(shù)和當(dāng)前的QPS等。限流框架除了實(shí)現(xiàn)基本的并發(fā)限流之外,也基于令牌桶和漏桶算法實(shí)現(xiàn)了QPS限流,并基于Redis實(shí)現(xiàn)了集群級別的限流。這些措施保障系統(tǒng)在高流量的情況下不會被打垮。 考拉在監(jiān)控服務(wù)方面的改造 在服務(wù)化的過程中,系統(tǒng)變得越來越復(fù)雜,服務(wù)數(shù)量變得越來越多,此時(shí)需要引入更多維度的監(jiān)控功能,幫助快速的去定位并解決系統(tǒng)中的各類問題。監(jiān)控主要分為這四個(gè)方面,日志、Metrics、Trace和HealthCheck。 在應(yīng)用程序、操作系統(tǒng)運(yùn)行的時(shí)候,都會產(chǎn)生各種各樣的日志,通過日志平臺對這些日志進(jìn)行采集、分析和展示,并支持查詢和操作。Metrics反映的是系統(tǒng)運(yùn)行的基本狀態(tài),包括瞬時(shí)值或者聚合值,例如系統(tǒng)的CPU使用率、磁盤使用率,以及服務(wù)調(diào)用過程中的平均延時(shí)等。Trace是對服務(wù)調(diào)用鏈的一個(gè)監(jiān)控,例如調(diào)用過程中的耗時(shí)分析、瓶頸分析、依賴分析和異常分析等。Healthcheck可以探測應(yīng)用是否準(zhǔn)備就緒,是否健康,或者是否還存活。
接下來,圍繞Dubbo來介紹一下考拉在監(jiān)控方面的改造實(shí)踐。 第一個(gè)是服務(wù)監(jiān)控。 Dubbo提供了服務(wù)監(jiān)控功能,支持定期上報(bào)服務(wù)監(jiān)控?cái)?shù)據(jù),通過代碼增強(qiáng)的方式,采集Dubbo調(diào)用數(shù)據(jù),存儲到時(shí)序數(shù)據(jù)庫里面,將Dubbo的調(diào)用監(jiān)控功能接入到考拉自己的監(jiān)控平臺。 上圖中的頁面是對Dubbo提供者的服務(wù)監(jiān)控,包括對服務(wù)接口、源集群等不同維度的監(jiān)控,除了全局的調(diào)用監(jiān)控,還包括不同維度的監(jiān)控,例如監(jiān)控項(xiàng)里的調(diào)用次數(shù)。有時(shí)候我們更關(guān)心慢請求的情況,所以會將響應(yīng)時(shí)間分為多個(gè)范圍,比如說從0到10毫秒,或是從10到50毫秒等,這樣就可以看到在各個(gè)范圍內(nèi)請求的數(shù)量,從而更好地了解服務(wù)質(zhì)量。
同時(shí),也可以通過各種報(bào)警規(guī)則,對報(bào)警進(jìn)行定義,當(dāng)服務(wù)調(diào)用出現(xiàn)異常時(shí),通過郵件、短信和電話的形式通知相關(guān)人員。監(jiān)控平臺也會對異常堆棧進(jìn)行采集,例如說這次服務(wù)調(diào)用的異常的原因,是超時(shí)還是線程滿了的,可以在監(jiān)控平臺上直接看到。同時(shí)生成一些監(jiān)控報(bào)表,幫助我們更好地了解服務(wù)的性能,推進(jìn)開發(fā)去改進(jìn)。 第二個(gè)是Trace。 我們參考了Dapper,自研了Trace平臺,并通過代碼增強(qiáng)的方式,實(shí)現(xiàn)了對Dubbo調(diào)用鏈路的采集。相關(guān)調(diào)用鏈參數(shù)如TarceID,SpanID 等是通過Dubbo的隱式傳參來傳遞的。Trace可以了解在服務(wù)調(diào)用鏈路中的一個(gè)耗時(shí)分析和瓶頸分析等。Trace平臺上可以展示一次服務(wù)調(diào)用,經(jīng)歷了哪些節(jié)點(diǎn),最耗時(shí)的那個(gè)節(jié)點(diǎn)是在哪里,從而可以有針對性的去進(jìn)行性能優(yōu)化。Trace還可以進(jìn)行依賴分析,這些依賴是否合理,能否通過一些業(yè)務(wù)手段或者其它手段去減少一些不合理的依賴。
Trace對異常鏈路進(jìn)行監(jiān)控報(bào)警,及時(shí)的探測到系統(tǒng)異常并幫助我們快速的定位問題,同時(shí)和日志平臺做了打通,通過TraceId可以很快的獲取到關(guān)聯(lián)的異常日志。 第三個(gè)是健康檢查。 健康檢查也是監(jiān)控中很重要的一個(gè)方面,以更優(yōu)雅的方式上線應(yīng)用實(shí)例。我們和自動部署平臺結(jié)合,實(shí)現(xiàn)應(yīng)用的健康檢查。服務(wù)啟動的時(shí)候可以通過Readiness接口判斷應(yīng)用依賴的各種資源,包括數(shù)據(jù)庫、消息隊(duì)列等等是否已經(jīng)準(zhǔn)備就緒。只有健康檢查成功的時(shí)候才會觸發(fā)出注冊操作。同時(shí)Agent也會在程序運(yùn)行的過程中定時(shí)的檢查服務(wù)的運(yùn)行狀態(tài)。
同時(shí),也通過這些接口實(shí)現(xiàn)更優(yōu)雅的停機(jī),僅依賴shutdownhook,在某些情況下不一定靠譜,比如會有shutdownhook執(zhí)行先后順序的問題。應(yīng)用發(fā)布的時(shí)候,首先調(diào)用offline接口,將注冊服務(wù)全部從注冊中心反注冊,這時(shí)不再有新的流量進(jìn)來,等到一段時(shí)間后,再執(zhí)行停機(jī)發(fā)布操作,可以實(shí)現(xiàn)更加優(yōu)雅的停機(jī)。 考拉在服務(wù)測試方面的改造 下面來介紹一下考拉在服務(wù)測試方面的實(shí)踐。服務(wù)測試分為接口測試、單鏈路壓測、全鏈路壓測和異常測試四個(gè)維度。 接口測試 通過接口測試,可以來驗(yàn)證對外提供的Dubbo服務(wù)是否正確,因此我們也有接口測試平臺,幫助QA更好的進(jìn)行接口測試,包括對接口的編輯(入?yún)ⅰ?/span>出參),用例的編輯和測試場景的執(zhí)行等, 單鏈路壓測 單鏈路的壓測,主要面對單個(gè)功能的壓測,比如要上線一個(gè)重要功能或者比較重要的接口之前,必須通過性能測試的指標(biāo)才可以上線。 全鏈路壓測 考拉作為電商平臺,在大促前都會做全鏈路壓測,用以探測系統(tǒng)的性能瓶頸,和對系統(tǒng)容量的預(yù)估。例如,探測系統(tǒng)的各類服務(wù)的容量是否夠,需要擴(kuò)容多少,以及限流的閾值要定多少合適,都可以通過全鏈路壓測來給出一些合理的值。 異常測試 對服務(wù)調(diào)用鏈路中的一些節(jié)點(diǎn)進(jìn)行系統(tǒng)異常和服務(wù)異常的注入,也可以獲取他們的強(qiáng)度依賴關(guān)系。比如一個(gè)非常重要的接口,可以從Trace獲取的調(diào)用鏈路,然后對調(diào)用鏈的依賴的各個(gè)服務(wù)節(jié)點(diǎn)進(jìn)行異常注入。通過接口的表現(xiàn),系統(tǒng)就會判斷這個(gè)接口的強(qiáng)度依賴關(guān)系,以改善這些不合理的強(qiáng)依賴關(guān)系。 考拉在API網(wǎng)關(guān)方面的改造 隨著考拉服務(wù)化的發(fā)展,我們自研了API網(wǎng)關(guān),API網(wǎng)關(guān)可以作為外部流量的統(tǒng)一接口,提供了包括路由轉(zhuǎn)發(fā)、流控和日志監(jiān)控等一些公共的功能。 考拉的API網(wǎng)關(guān)是通過泛化調(diào)用的方式來調(diào)用后臺Dubbo的服務(wù)的。Dubbo原生的泛化調(diào)用的性能比普通Api調(diào)用要差一些,所以我們也對泛化調(diào)用性能做了一些優(yōu)化,也就是去掉了泛化調(diào)用在返回結(jié)果時(shí)的一次對象轉(zhuǎn)換。最終壓測的結(jié)果泛化的性能甚至比正常的調(diào)用性能還要好些。 考拉在多語言方面的改造 考拉在業(yè)務(wù)發(fā)展的過程中產(chǎn)生了不少多語言的需求,例如,我們的前端團(tuán)隊(duì)希望可以用Node應(yīng)用調(diào)用Dubbo服務(wù)。對比了易用性,選用了開源的jsonrpc 方案,然后在后端的Dubbo服務(wù)上暴露了雙協(xié)議,包括Dubbo協(xié)議和json rpc協(xié)議。 但在實(shí)施的過程中,也遇到了一些小問題,比如說,對于Dubbo消費(fèi)者來說,不管是什么樣的協(xié)議提供者,都是invoker。通過一個(gè)負(fù)載均衡策略,選取一個(gè)invoker進(jìn)行調(diào)用,這個(gè)時(shí)候就會導(dǎo)致原來的Java客戶端選用一個(gè)jsonrpc協(xié)議的提供者。這樣如果他們的API版本不一致,就有可能導(dǎo)致序列化異常,出現(xiàn)調(diào)用失敗的情況。所以,我們對Dubbo的一些調(diào)用邏輯做了改造,例如在Java客戶端的消費(fèi)者進(jìn)行調(diào)用的時(shí)候,除非顯示的配置,否則默認(rèn)只用Dubbo協(xié)議去調(diào)用。另外,考拉也為社區(qū)的jsonrpc擴(kuò)展了隱式傳參的功能,因?yàn)榭梢杂肈ubbo隱式傳參的功能來傳遞一些全鏈路參數(shù)。 考拉在解決注冊中心性能瓶頸方面的實(shí)踐 注冊中心瓶頸可能是大部分電商企業(yè)都會遇到的問題,考拉也不例外。我們現(xiàn)在線上的Dubbo服務(wù)實(shí)例大概有4000多個(gè),但是在ZooKeeper中注冊的節(jié)點(diǎn)有一百多萬個(gè),包括服務(wù)注冊的URL和消費(fèi)者訂閱的URL。 Dubbo應(yīng)用發(fā)布時(shí)的驚群效應(yīng)、重復(fù)通知和消費(fèi)者拉取帶來的瞬時(shí)流量一下就把ZooKeeper集群的網(wǎng)卡打滿,ZooKeeper還有另外一個(gè)問題,他的強(qiáng)一致性模型導(dǎo)致CPU的利用率不高。 就算擴(kuò)容,也解決不了ZooKeeper寫性能的問題,ZooKeeper寫是不可擴(kuò)展的,并且應(yīng)用發(fā)布時(shí)有大量的請求排隊(duì),從而使得接口性能急劇下降,表現(xiàn)出來的現(xiàn)象就是應(yīng)用啟動十分緩慢。
因此,在今年年初的時(shí)候就我們決定把ZooKeeper注冊中心給替換掉,對比了現(xiàn)有的一些開源的注冊中心,包括Consul、Eruka、etcd等,覺得他們并不適合Dubbo這種單進(jìn)程多服務(wù)的注冊模型,同時(shí)容量能否應(yīng)對未來考拉的發(fā)展,也是一個(gè)問號。于是,我們決定自研注冊中心,目前正在注冊中心的遷移過程當(dāng)中,采用的是雙注冊中心的遷移方案,即服務(wù)會同時(shí)注冊ZooKeeper注冊中心,還有新的注冊中心,這樣對原有的架構(gòu)不會產(chǎn)生太大的影響。
考拉新的注冊中心改造方案和現(xiàn)在社區(qū)的差不多,比如說也做了一個(gè)注冊數(shù)據(jù)的拆分,往注冊中心注冊的數(shù)據(jù)只包含IP, Port 等關(guān)鍵數(shù)據(jù),其它的數(shù)據(jù)都寫到了Redis里面,注冊中心實(shí)現(xiàn)使用了去中心化的一個(gè)架構(gòu),包括使用最終一致性來換取我們接口性能的一個(gè)提升。后面如果接入Dubbo,會考慮使用Nacos而不是ZooKeeper作為注冊中心。 未來規(guī)劃 考拉最近也在進(jìn)行第二機(jī)房的建設(shè),通過兩個(gè)機(jī)房獨(dú)立部署相同的一套系統(tǒng),以實(shí)現(xiàn)同城雙活。針對雙機(jī)房的場景,Dubbo會做一定的改造,例如同機(jī)房優(yōu)先調(diào)用,類似于即將發(fā)布的Dubbo2.7.0中的路由特性。在Dubbo在服務(wù)注冊的時(shí)候,讀取系統(tǒng)環(huán)境變量的環(huán)境標(biāo)或者機(jī)房標(biāo),再將這些機(jī)房標(biāo)注冊到注冊中心,然后消費(fèi)端會做一個(gè)優(yōu)先級路由,優(yōu)先進(jìn)行同機(jī)房的服務(wù)調(diào)用。 容器化也是我們在規(guī)劃的一個(gè)方向。隨著服務(wù)化進(jìn)程的演進(jìn),服務(wù)數(shù)也變得越來越多,通過容器化、DevOps可以提升測試、部署和運(yùn)維效率。
Service Mesh在今年非?;?,通過Service Mesh將服務(wù)框架的的能力比如注冊發(fā),路由和負(fù)載均衡,服務(wù)治理等下沉到Sidecar,使用獨(dú)立進(jìn)程的方式來運(yùn)行。對于業(yè)務(wù)工程的一個(gè)解耦,幫助我們實(shí)現(xiàn)一個(gè)異構(gòu)系統(tǒng),對多語言支持,也可以解決中間件升級推動困難以及各種依賴的沖突,業(yè)務(wù)方也可以更好的關(guān)注于業(yè)務(wù)開發(fā),這也會是未來探索的一個(gè)方向。 |
|