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