去哪兒旅行微服務架構實踐

你好,我是朱仕智,在去哪兒網負責基礎架構,主要包含後端架構、大前端架構、質量保障、基礎雲平臺等工作,近期主要在公司落地雲原生和數字化管理。

今天我帶來的主題是去哪兒旅行微服務架構實踐。我將從以下幾個方面進行介紹:

  1. 背景介紹

  2. 微服務架構模式的最佳實踐

  3. 微服務開發效率的提升實踐

  4. 微服務治理的實踐

  5. ServiceMesh 嘗試

一、背景介紹

首先介紹一下去哪兒網的業務。去哪兒網是一個典型的在線旅遊平臺,它上面的業務繁多,有機票、酒店、度假、火車票、汽車票等等。

這些業務都有不同的業務流程,其中機票的標準化和線上化是最高的,但是像酒店這樣的業務,在線化和標準化就比較低,同樣的名字可能是不一樣的酒店。這些業務在從商品、庫存到整個交易過程其實都是不一樣的,所以這些業務從背後來看還是相對比較複雜的。

我們爲什麼要選擇微服務,其實有以下幾個方面的原因。第一個就是業務逐漸複雜,最早去哪兒網其實只有機票的比價,而且是一個搜索比價,是沒有交易環節的。後來業務擴展就慢慢地發展出來了包含機票、酒店、火車票、度假、汽車票等等其他的業務。

所以業務是逐漸複雜的一個過程,那按照康威定律大家都知道,業務變化了之後,組織結構要進行相應的調整,組織架構其實也會跟着相應的膨脹,膨脹也會帶來協作上和分工上的一定損耗,這也是我們要選擇微服務的原因之一。

第三個就是開發效率的低下,我們之前開發的時候大部分都是以最早的模式,也就是通過 HTTP 協議,加上 JSON 這樣的數據結構,然後使用 Nginx 作爲網關,把服務治理的這些動作全部耦合在業務代碼裏面,比如重試的邏輯等等。這樣的話就會導致我們每一個服務做對應開發的時候,都需要重複性地去考慮這些問題,開發效率相對就會比較低下。

第四個就是服務質量是比較失控的,因爲這些服務質量很難能在統一的一個地方去得到比較有效、及時地處理,就像剛纔說的治理的邏輯其實是放在了業務代碼裏面,有一些治理邏輯可能會放在 Nginx 裏面,但是 Nginx 是一個大統一的網關,這就意味着當我們想要去對它進行修改的時候,其實是需要非常謹慎的,這就面臨了一個運維和開發訴求不對等的問題。使用微服務我們認爲是可以比較有效地解決這些問題的。

接着介紹一下我們去哪兒網的在線數據。我們現在的應用數據是這樣的:活躍的、在線跑着的應用大概有 3000 多個;提供了 18,000 多個 Dubbo 的 RPC 服務接口;有超過 3500 個 HTTP 域名;13,000 多個 MQ 的主題;公司內部大概有 5 種語言的技術棧,當然主要是以 Java 和 Node 爲主。

二、微服務架構模式的最佳實踐

接下來介紹一下架構模式,架構模式裏面有幾個方面不同的範疇。

  1. 服務發現模式

第一個就是服務發現的模式,服務發現裏面其實有三種模式,這三種模式對應不同的適用場景會有不同的效果。

直聯模式,客戶端從註冊中心發現服務端的列表並緩存在本地,這種模式適合於語言統一的這種內網通信,爲什麼呢?因爲直連模式裏面大部分 RPC 採用的這樣的模式,主要是比較簡單、高效,而且在統一語言的內網通信裏面,這種服務端的實例的變更通知是比較簡單的。

代理模式,服務端註冊到網關上,客戶端對一個服務端其實是無感知的,這種模式比較適合於外網服務,爲什麼呢?是因爲當你的服務端變更的時候,客戶端其實是不需要去感知,也不需要對此進行任何變更,這樣對外網來說,其實用戶側的設備是不需要去關注信息的,這樣通知起來就比較簡單。但是它也會面臨一個問題,它會多一跳的通信,從性能或者效率上來說,肯定是不如直連模式的。

最後一個就是邊車模式,Sidecar 去負責註冊和發現,應用程序是無感知的,這種比較適合於多語言、多協議的這種內網通信,它其實跟直連模式相對來說是比較相似的,但是它其實是由邊車的模式替代了業務程序裏面混入的這種基礎功能,所以簡單來看其實就是直連模式裏面把公共的基礎設施的邏輯下沉到了邊車裏面。這樣的話邊車就可以統一地配合我們的灰度發佈或者是其他的熱更新的機制,能夠做到比較容易地去對這些邊車進行升級。

  1. 服務通信模式

接下來我們說一下服務通信的模式,服務通信模式裏面主要有兩種,大家其實日常裏面比較經常會碰到就是同步的編程模式,這種模式比較簡單易懂,非常符合人類的思考習慣,它比較適用於時間比較敏感的、吞吐量也比較小的這種場景。但是這種通信的方式在吞吐量比較大、QPS 比較高的場景裏面就會有一系列的問題,比如說可能會把你的資源耗盡,但其實這些資源都處於等待中。比如我們在 Java 裏面可能會有線程池的資源,使用起來其實是比較低效的。然後在異步的這種場景裏面,它其實比較適用於高吞吐、削峯填谷的作用。

其實這裏面會有幾種,從我們的實踐上來看的話,比如說搜索系統它其實是一個非常高併發的場景,其實對於這種高吞吐的場景下是必須要用異步的,不然的話其實資源的損耗是非常高的,我們在某些系統上做過改造,由原來的同步改爲異步的話,基本上可以節省掉 80% 左右的機器的資源。除此之外,交易系統的事件驅動也是比較適合異步的一個場景,因爲交易系統的事件其實是非常關鍵的,但是它又不能每個人都去通知,因爲很多人都需要關注這個事件,這個時候利用 MQ 等方式去做這種事件的驅動是比較合適的。

封裝異步 HttpClient

然後在異步的這個場景裏面,去哪兒網其實做了一些自己內部的一些支持,比如說我們封裝了異步的 HttpClient,把公司內部其他的組件類似於 QTrace,還有一些其他基礎的監控、日誌等等之類的組件都做了統一的封裝埋點。

改善 Dubbo 異步通信

第二個我們對 Dubbo 的異步通信進行了改善,Dubbo 裏面原有的幾種通信方式,其實是調用端和被調用端,是會存在一定的耦合邏輯的。比如說像參數回調這樣的方式,其實是調用端需要進行異步,但是被調用端不得不配合這個方式進行改造,所以在這種背景下,我們對 Dubbo 的異步通信進行了魔改,其實現在的最新版的 Dubbo 的模式裏面,跟這個是比較相似的。

自研可靠事務消息隊列 QMQ

第三個就是我們其實內部做了一個自研的消息隊列叫 QMQ,它其實支持可靠的事務消息,廣泛地應用在我們去哪兒網的交易系統裏面。

  1. 協議

第三個主要提一下協議這部分,我們在公司裏面主要有三種協議。第一種私有協議,主要負責 App 和外網網關之間的通信協議;第二個 HTTP 協議,主要是外網網關到 Node、Node 到 Java 之間,甚至有一些 Java 到 Java 之間也會有自己使用的這種 HTTP 協議,不過這種量其實是比較少的;第三個 Dubbo 協議,後端的 Java 服務之間的通信基本上都是用 Dubbo 爲主,只有少量的使用 HTTP。

  1. 設計模式

從設計模式上來說的話,我們其實可以知道在互聯網的架構裏面,特別是在高併發的模式裏面,我們有很多折中,這些折中裏面其實會有不同的模式和它的沉澱。比如說像 BASE 這樣的模式,它其實不追求強一致性,它是有這種基本的可用和軟狀態這樣的優點,進而去避免因爲強一致導致的其他的不可用性。

第二個就是 CQRS,這個模式其實非常有用,至少我發現很多場景是能夠用上它的,換句話說其實只要是數據異構的這種場景,都是比較適合去使用它的,當然這取決於你的查詢模式。大家都知道查詢模式其實有很多種的,比如說像 KV 的查詢模式、複雜條件的 Query,除此之外,還有 Scan 這種掃描形式,不同的查詢形式會對應着不同的存儲結構是比較合適的。但是我們在對這些數據進行操作的時候,其實它的數據載體是唯一的,那這個數據載體怎麼樣才能支持多種的查詢模式呢?其實這裏面就需要對這些數據進行異構,比如說像我們的訂單、配置等等這些方式都需要去進行一定的異構。

比如說像去哪兒網內部的話,代理商在去哪兒網上就可以進行一定的調價,調價的配置其實就是一個比較適合去做數據異構的場景。代理商去錄入的時候是比較複雜的,但其實是從航空公司拿到的一個配置,當它放到平臺上來的時候,也是用同樣的方式去放,但是對於檢索來說的話,用戶其實關心的是這個城市,到這個城市的時候,你的調價規則是什麼樣子,他並不需要一個大一統的調價規則。所以這裏面就會面臨一個數據異構的過程,我們在這個過程裏面其實也使用了 CQRS 這個模式來解決問題。

三、微服務開發效率提升實踐

然後我來說一下效率提升的這部分,大家都知道業界 Spring Cloud 在近期或者是近幾年來說是一個最佳實踐,特別是在微服務比較火之後,大家亟需一套成型的解決方案。這個裏面包含不同的功能,比如說像分佈式的配置、服務的註冊、發現、通信,還有服務的熔斷、服務調用、負載均衡、分佈式消息等等。其實大家可以看到官方的一個實現,當然實現基本上都是來源於 Netflix 的,這裏面會有不同的這些組件,但這些組件其實很多時候可能有一些已經不再維護了。

對應地可以看到 Spring Cloud Alibaba 也有自己的實現,像 Nacos、Sentinel、Dubbo、RocketMQ 等等。我們其實就在思考着去哪兒網自己有這麼多自研的組件,是否能夠適配 Spring Cloud 這樣的一套標準,進而去達到開發提效、互相串通組件的目的?

1.Spring Cloud Qunar

我們做了一個嘗試,基於 Spring Cloud 做了配置中心、註冊中心、服務治理等等之類的組件的串通,這樣的話能夠做到比較好的開發模式。然後值得一提的是我們在 Spring Cloud Qunar 裏面,其實提供了兩種通信的模式,一種是前面提到的直聯模式,就是由應用本身包含的 SDK 來負責註冊、發現和通信。除此之外,我們還有一個模式是基於 Sidecar 的這種 Mesh 模式,我們也可以由 Mesh 的 Sidecar 去負責註冊、發現和通信,這兩者之間的開啓其實是比較簡單的,只需要有一些特定的註解就可以開啓 Mesh 模式。

大家可以看到這裏面,比如上面的代碼,有 Dubbo Service 這樣的一個服務的提供,下面就會有 Dubbo Reference 這樣的一個服務的引用,並且在註解裏大家可以看到 Qunar Mesh 這樣的一個註解,這個註解就是用於開啓我們的 Mesh 功能的,是對於 Dubbo 這個協議的。對於 HTTP 協議的話,其實跟官方的也是非常類似,我們也是使用了 OpenFeign 這樣的一個組件來進行通信,下面也同樣會有 Qunar Mesh 組件進行 Mesh 化。

  1. 開發插件

下面說一下開發插件,我們爲什麼要做開發插件,以及開發插件爲什麼能夠做到效率上的提升呢?其實這裏面的話,我們分析了大量的業務研發的開發模式,能夠發現存在一些重複性或者是低效的環節,比如說像手動編寫很多的調用代碼,甚至可能會出現要手寫這些反序列化類等等。

第二個就是在交互的過程中大量地去使用類似於文檔,或者是內部的 IM,甚至比如說大家做的比較好的場景下是有 apiDoc 這樣的方式去溝通這些接口的語義和細節。

第三個就是服務上線之後纔去考慮治理,這個裏面就會面臨開發和運維的不對等。你的服務上線了後,它不出問題時,其實你是很少會去考慮治理的,只有在你開發的時候可能會有一定的考慮,但是這個考慮其實不是基於真實數據的。比如說你設置一個超時時間,大家經常能夠在代碼裏面看到 1 秒、30 秒、60 秒等等之類的數字,這些數據真的有意義嗎?不一定,只是大家習慣性地這麼寫,然後還有成百上千個 HttpClient Wrapper,就是自己不停地去實現這些 HttpClient,這些都是一些開發比較低效的場景,我們怎麼解決這個問題呢?

我們其實做了一個基於 idea 的 IDE 的開發插件。開發插件它可以滿足以下的幾個功能,比如像服務調用的代碼自動生成,這個是一個什麼樣的場景?是說當你在 IDE 裏面打開我這個插件,你就可以選擇對方的應用、對方提供的服務,直接就一鍵生成調用的代碼,甚至包括一些其他 jar 包的引入,比如如果它是 Dubbo 協議的,它會自動引入這些 Dubbo 的 SDK 和對方提供的這些 API 的 jar 包等等。

第二它可以快速地發現這些應用接口方法,集成對應的文檔服務,這個就是剛纔提到的我們其實打開了這個插件,就能快速地去檢索它對應的應用和提供的服務,是比個人溝通要高效很多的。

第三它打通了服務治理。在編碼生成的過程中,你需要去配置這些治理的參數,然後這些治理的參數通過上報的方式,把它統一地註冊到我們的服務治理平臺,然後跟 Mesh 的模式去進行打通。這樣的話有一個非常有效的方式,在你去生成這些調用代碼的時候,你就可以參考一些對應的指標、參數,比如對方提供的接口的監控是什麼樣子的,以及其他人設置的指標是什麼樣的,做一定的智能化推薦,這樣能夠保證我們的這些指標相對來說是配置的比較合理的。

第四個就是代碼規範的最佳實踐是能夠比較好去落地的。我們都知道,很多時候這些代碼規範是需要靠文檔,比如我們出一個什麼樣的規範,什麼樣的標準去保障,或者是類似利用這些代碼檢查工具,比如 Sonar 等等之類的方式去保證我們的代碼規範的落地。但是其實通過這種生成代碼的方式,我們直接就可以把最佳實踐嵌入到生成的過程裏面,來保證它生成的代碼一定是符合最佳實踐的。

除了上面這四個方面之外,我們其實還在插件上做了大量的工作,比如說像 CI/CD 的左移,這個左移包含了我們可以在本地去跟遠程的環境打通,以及它還提供了對應的 CI/CD 流水線的功能,還有代碼覆蓋率的功能等等。通過這樣的一個開發插件,我們可以把日常的一些重複性的、低效性的工作就可以被完成掉,是一個比較好的提效方式,推薦大家去使用。

四、服務治理實踐

然後在服務治理這裏面,我們其實也做了一些自己的思考。首先我們來看一下,常規的這些服務治理的四板斧是什麼樣子。

  1. 常規四板斧

不可避免地,第一,我們一定要設置超時;第二,要在一些場景裏面去考慮重試的邏輯;第三,考慮熔斷的邏輯,不要被下游拖死;第四,一定要有限流的邏輯,不要被上游打死。

  1. 最終目標

這些都是非常普遍,也是非常有效的一些措施,但是有效建立在於你的配置,或者是你的這個動作是有效的場景,但實際上我們很大程度上其實是在濫用這四種技術。我認爲服務治理的一個最終的目標就是穩定可用、可觀測、防腐化,這是什麼意思呢?

穩定可用指的就是我們通過各類的防控手段去達到在可用的容量場景下,提供有效的服務,這樣才能叫穩定可用。第二個可觀測,就是我們從多個維度,比如說像關係、性能、異常、資源等維度對它進行度量並且分析。第三個防腐化,我們的代碼和架構其實不可避免地都是在腐化的一個過程之中,我們不停地往裏面去添加東西的過程中,其實也會缺乏一定的治理。我們服務治理的目標,其中一點就是要做到如何去對它進行防腐,這個裏面有一些考慮的維度,比如服務的層級,你的服務並不是越微越好,也不是層級越多越好,所以服務的層級一定要有所控制。

  1. 保護機制

第二就是鏈路的分析,鏈路裏面上下游的超時、串行、並行的調用等等之類的這些東西在編碼的過程中可能會被忽略掉的,這些我們其實可以通過偏後置一點的方式對它進行一個分析和預警,這裏面提一下我們在保護機制上做的一些工作,我們都知道在 RPC 的框架裏面,其實特別是在直連的模式下,調用端 Consumer 端和 Provider 端其實是直連通信的。

對於註冊中心來說,它只負責一個註冊和變更通知的作用,但是在有一些特定的場景裏面並不是這樣子的。舉個例子來說,當一個註冊中心因爲自身的原因處於一個半死不活的狀態,它一會兒能服務、一會兒不能服務的時候,就會發生一個比較恐怖的事情,Provider 端因爲它要跟註冊中心去保持心跳判活的狀態,所以需要和註冊中心保持長期有效的連接。如果是失效的情況,作業中心就會判斷這個 Provider 是不存活了。不存活的時候,註冊中心就會把這個消息通知給 Consumer 端,Consumer 端只要接收過一次下線通知,Consumer 就會從它的列表裏面把這個 Provider 從本地的緩存裏面去移除掉。

如果註冊中心處於一個半死不活的狀態,最後會處於一個什麼狀態呢?Consumer 端慢慢地會把所有的 Provider 都移除掉,這樣就會導致我們的 Consumer 端到 Provider 端其實是不可通信的。對於這個問題,我們其實基於 Dubbo 做了一定的改造,做了一個保護機制。這個保護機制就是當 Provider,特別是註冊中心上的 Provider 數少於一定的閾值的時候,我們的保護機制就會自動地啓用,它的生效是在 Consumer 端的,也就意味着 Consumer 端需要緩存這段時間內所有歷史的 Provider 的列表。

大家可能在這裏會有一點擔心,你緩存的 Provider 如果失效了怎麼辦?它是真的失效了,比如說它被下線了,或者是它本身經過遷移,像我們在容器場景裏面,經過了一定的發佈,其實它對應的信息都變化了,這個時候你再去通信不就有問題嗎?其實我們在保護機制裏面也考慮了這個問題,我們在通信之前還是會做一個直連的檢查,Consumer 到 Provider 的連接存活是否是真正存在,如果不存在,我們會把這一個連接給扔掉,保證通信的時候使用的是一個可用的連接。

當這個信息機制啓用了之後,註冊中心恢復到一定的狀態的,這個 Provider 又能重新註冊到註冊中心裏面了,接着我們又會把保護機制自動關閉掉,這樣的話 Consumer 就只會調用註冊中心上存活的這些 Provider,就可以避免掉因爲註冊中心半死不活,導致所有的這些分佈式的應用裏面的 RPC 調用是不可用的。

這其實是一個比較有效的方式,因爲如果出現了這種場景,其實你內網裏面的大部分應用通信其實是處於一個不可用的狀態,甚至你想讓它恢復都是非常困難的事情。比如你想啓動的時候,其實 Consumer 發現 Provider 都不存活了,這也會導致啓動失敗等等各方面的問題。

  1. 動態限流

接着我來介紹一下限流裏面我們做的一些工作,這裏面我們做的模式我把它叫做動態限流。普通的一個限流裏面,通常來說是這樣的一個方式,我們有 A、B、C 的服務都對 X 這個服務進行了調用,它的來源可能是不一樣的,X 爲了保護自身的狀態是可用的,它不可避免就要對上游 A、B、C 的這些訪問分配固定的一些配額,誰超過了配額就不可用了。

比如說像 A 分配了 100、B 也分配 100、C 分配給了 50。當 A 超過了 100 的時候,其實它的一些請求是會被拒絕掉的,這個是基於容量的考慮,X 不可能具備無限的容量,這時它需要一定的保護措施。但是這地方就會有一個問題,假如 A、B、C 裏面,比如說 B 服務,它其實是從 App 過來的,它的價值不可避免來說的話,要更高一點。比如說第三個服務 C,它是從 Web 裏面來,它的價值相對來說比較低一點。這個價值是基於你的業務形態來的,比如說你的 App 的成單、轉化更高,那就意味着它的請求更珍貴。

這個裏面就會出現一個問題,服務 B 和服務 C 自己都得到了一定數量的配額,但是假如 App 的流量上漲了,Web 的流量沒有上漲,這時就會面臨一個問題,服務 C 的配額沒用完,但是服務 B 的配額又不夠用,這個場景下怎麼解決呢?就需要靠人工來不停地去調整它,而且這個調整需要相當實時纔可以,我們有沒有辦法能夠相對統一地解決這個問題呢,其實我們做了一個探索,這個探索從實踐結果來看的話是比較有效的。

我們對這些服務進行配額分配的時候,其實不是一個固定的配額,而是一個動態的分配。動態的分配意思就是,我只有一個總的容量,並不給每一個服務進行分配,總的容量我分配給所有人。但是我要對所有的調用方進行一個排序,也就是說誰的價值高誰就排在前面,這樣的話就能得到一個比較有效的結果。你的限流模型是基於你的業務邏輯來的,也是基於你的業務價值來的,當你發生限流的時候,優先丟掉的一定是最沒有價值的那部分的業務請求。

當然這裏面也會有一個前提,你的請求來源是需要有差異化的。還有第二個點,你的這些 trace 連通性一定要高,也就意味着,你的這些標誌要能夠一路暢通地攜帶下去,如果只是基於某一層去做限流邏輯,其實是沒有意義的。

  1. 防腐化

接着就是防腐化,這裏面其實我們需要對架構、應用的分佈、應用的關係去做大量的分析,得出改進的措施,我們在這上面改進的措施其實有很多。比如我們會分析哪些應用是頻繁修改的,這些頻繁修改的意思是不是所有的需求,這些應用都相關地需要去做修改,那就意味着說它的業務域是一樣的。如果這些業務域一樣的情況下,你把它的微服務劃分得很細,實際上它是一一綁定的話,其實並不符合微服務化的原則。

第二個是否存在重複的調用,這條鏈路裏面,這些重複的調用是否能夠去緩存化,或者是避免它重複調用。

第三個大量的串行調用是不是能夠把它異步化,比如常見的,從數據庫裏面拿出一批記錄,這一批記錄通過循環的方式,挨個去對它發起遠程調用,這些過程裏面其實比較有效的方式就是通過異步化、並行化的方式去把速度給提上來。

第四個異步的整個鏈路的這些超時配置裏面,其實會有一定的相關的關係。比如上游的超時是不應該比下游短的,如果下游的超時比上游的還長,那意味着說下游還在計算,上游可能已經超時了,這個計算的結果其實有可能返回不了上游,這些就是無用的配置。除了這之外其實整個鏈路裏面大量的超時可能是不合理的,比如剛纔提到的大量重複的調用,這些重複的調用或者循環的調用,再乘以同樣的超時時間,可能就會比整個終端的操作時間要長很多,這些都需要去做一定的分析和考慮,才能達到它防腐化的目的。

五、ServiceMesh 嘗試

最後一個介紹一下我們在 ServiceMesh 上的嘗試。

  1. 背景

先簡單介紹一下背景,我們公司內部其實還是存在多語言、多協議的這樣一個場景。

第二個它在多語言、多協議的場景裏面不可避免地就會出現治理平臺比較分散,比如像 Dubbo 的話,我們其實會有一個 RPC 的服務治理平臺;HTTP 的話我們其實有類似於網關 Nginx 或者是 OpenResty 去對它進行治理;其他的也會相應的治理,甚至可能是在配置中心去對它進行治理等等。

第三個組件的新功能迭代是相對比較慢的,因爲這些組件都是嵌入在應用代碼裏面,因此它的迭代就需要跟隨着業務代碼去迭代,才能夠去比較好地迭代,而且這些迭代裏面其實需要付出一定的人工成本,其實業務的開發是不太願意去主動地做這種組件的迭代的,在 ServiceMesh 的選型裏面,我們也考量了一下當時業界裏的選擇。

  1. 技術選型

其實從數據面上來看,envoy 還是佔大頭的,但是我們最終其實沒有選擇 envoy,主要是因爲我們在 C++ 技術棧裏面儲備的人才是不夠多的。第二個在控制面上,大家基本上都是基於 Istio 模式去做的,當然也大部分都做了二次的開源,我們最終也是選擇這樣的一個模式。

  1. 整體架構

我們最終的選擇是,數據面上我們選擇了 MOSN,而不是 envoy,MOSN 是基於 Go 開發的一個阿里巴巴官方出品的組件,這個組件其實是一個偏網關代理型的一個組件,但是在上面去實現 Mesh 的邏輯,其實是比較方便的,特別是針對基於 Dubbo 這個協議的 Mesh,MOSN 支持得是比較好的;在控制面上,我們也是基於 Istio 去做了二次開發,也有一定的自研組件,比如說 mcpServer、配置中心、註冊中心這些都是我們自研的。在運維面的話,我們也是自研了一套運維相關的組件,比如 Sidecar 的部署、灰度的升級等等,還有一些規則治理、監控報警等。

  1. 註冊模型:

ServiceMesh 裏面我主要介紹一下幾個關鍵點:第一個就是註冊模型,因爲它是一個多協議、多語言的方式,其實比如 Dubbo 或者 HTTP,它在服務層面其實是不統一的,在註冊中心我們想要以一個統一的註冊中心去服務發現的時候,不可避免地就需要把它的維度統一掉,我們是怎麼做到的呢?我們其實是參考了業界現在比較火的,或者基本上應該是事實上的標準,通過服務 - 實例這樣的維度去抹除掉了類似 RPC 這種 Dubbo,這種接口的維度,與原來的註冊中心去進行雙寫,來保證 Mesh 化的和非 Mesh 化的都能支持。

  1. 配置模型

然後第二個就是配置的模型,這裏面就是服務治理平臺,我們其實自定義了一些存儲的格式,然後通過 MCP 的方式,Server 的組件去轉換 Istio 需要的數據格式。Istio 拿到了之後,通過標準的 XDS 的數據格式下發到 MOSN 裏面,這一段我們基本上就是依賴原有的一個功能,主要是在左側這部分,我們自定義的這部分組件的數據格式是比較關鍵的。

  1. 路由模型

第三個說一下路由的模型,路由模型裏面,大家其實見過非常多,但是我對這些治理的功能或者路由的功能,其實偏保守一點的觀點。因爲在我看來越靈活越可能會用錯,這裏面就需要我們去抽象一定的業務模式,把業務模式落地到或者固化到組件裏面來。通過這個方式,我們其實發現只需要以應用和環境集羣爲主體,並且在這個場景上支持 trace 匹配的控制,就可以保證滿足我們絕大部分的業務場景。

因爲我們線上經常會出現應用不同的環境集羣,其實是爲了不同的訴求去用的,比如像搜索集羣和交易集羣,它們需要進行物理隔離,然後比如上線的時候,可能需要做一定的灰度驗證等等。這樣的話我們就可以基於 trace 的參數匹配去控制它,只要以這樣兩種方式作爲路由模型的支持,是滿足絕大部分的業務訴求的。

  1. 控制面和運維面

在控制面與運維面上,我們做了什麼樣的方式呢?其實我們當時也並不想要在這上面做自研,而我們參考了業界很多的解決方案,其實發現在配置中心和 MCP 的 Server 裏面,是缺少開源方案的,特別是配置中心,我們發現基本上很少有可用的配置,基本上就是一個查看可觀測的方式而已,但其實你想要對它進行一些服務的治理是不夠用的。

第二個 Sidecar 運維,這裏面無損的升級和切換非常關鍵,會涉及到不同組件之間的依賴關係和它的檢測,比如 Consumer 對 MOSN Sidecar 的檢測,和 MOSN 逆過來對 Consumer 的檢測,這些邏輯都是不一樣的,而且細節會比較多,有興趣的話大家可以線下溝通一下。

第三個就是可觀測性,參考了非 Mesh 化需要的一些指標,我們可以比較好地去把 Mesh 化的過程裏面大量的可觀性指標都內置地埋點進去。但是在 trace 鏈路裏面,最好把 Mesh 的 Sidecar 的 span 給精簡掉,不然你會發現所有的節點都比原來多了兩跳,這樣無疑會把 trace 因爲中間件的邏輯,把它複雜化掉了。

第四個就是健康檢查,這裏面剛纔提到的 Consumer 對 Sidecar 的可用性的檢查,其實是一個非常關鍵的重點,因爲取決於它需要怎麼降級以及它能不能降級。

  1. 性能優化

最後一個就是性能的優化,這裏面主要有兩點,在業界大部分的方案裏面其實都會面臨一個問題,因爲這些調用關係是動態化的,就意味着運行時才能知道我需要調用哪一些服務,它對應的規則是什麼,也就是說我需要把所有的服務信息都下發到 Sidecar 裏面,這不可避免就會佔用大量的內存,它的匹配效率都是非常低的,我們在這上面怎麼去做優化呢?

其實配合前面 Spring Cloud Qunar 能夠做到比較友好的方式,當它做了 Spring Cloud Qunar 這樣的 Qunar Mesh 註解之後,我們其實可以把這部分在編譯期就採集上來,或者在啓動的時候去把這些信息都給它上報上來,這樣我們就只需要訂閱我們需要的一些部分數據就好了,能夠做到大量的數據減少。

第二個就是在服務通信裏面,因爲多了 Sidecar 的兩跳,那就意味着說 Sidecar 的通信是帶來一定時間、效率和性能損耗的,這裏面的關鍵點就在於應用程序和 Sidecar 的通信是否能夠存在優化空間。我們經過實驗發現,使用 UDS 的通信來替代原有的這種要經過網卡的通信其實要高效不少的,把它在這兩跳上帶來的損耗降到足夠低。

六、總結

總結來看的話,整個微服務的過程裏面,我們最佳的實踐其實存在好幾個方面。

第一個是在發現模式、通信模式上的,我們需要去因地制宜做一定的最佳實踐;在架構模式裏面,比如說像 BASE 模式和 CQRS 模式,我們都可以在合適的場景裏面放心大膽,或是儘可能去啓用它們的。

開發效率先行,微服務的初衷其實是提效,那問題複雜化了以後,就需要有這些有力的配套,比如開發插件等來解決我們開發的問題,否則微服務可能只會帶來一地的雞毛。

第三個就是有效的服務治理,簡單的管控手段意義是不大的,它的手段雖然有效,但真實業務的意義是不大的,類似於動態限流這樣的模式才能真正解決業務問題。

第四,ServiceMesh 不可避免地,或者說現在基本上已經成爲事實上的下一代微服務通信的架構模式,這個裏面模型的設計和性能優化就非常關鍵。

最後對於微服務裏面的一些要點再進行一下簡單的總結

業務的拆分就是借鑑業界成熟的模型,本地化爲最適合公司現狀的業務結構。比如剛纔提到的去哪兒網,它其實也是一個線上的電商系統結構,但是它又有旅遊、民航或者酒店領域的特殊性,就不可避免地要本地化。

還有就是架構模式裏面,不同場景下的架構模式的支持是不一樣的,交易系統的事件驅動,異構數據的 CQRS 都是比較有效的方式。然後開發模式、開發支撐裏面需要對微服務進行完善的工具支持。

在服務度量裏面,我們關係、性能、異常、資源,還有剛纔提到的防腐都需要比較有效。第五個就是治理的管控,限流、熔斷這種方式需要實時生效,最好是把它統一化而且進行業務有效化。最後一個就是演進式,架構的演進需要平滑有序,避免大量的應用改造。

最後送給你一句話:架構演進,以提升效率爲目標。

作者介紹

朱仕智  去哪兒旅行 基礎架構部高級技術總監

去哪兒網高級總監。負責過公共業務、國際機票、基礎技術等團隊,擅長複雜實時業務的高併發、高可用、高性能的系統設計和落地。目前負責基礎架構團隊,包含後端架構、大前端架構、質量保障、基礎雲平臺等領域。近期主要投入在公司整體技術演進和數字化技術運營方向。


人生就是這樣,在失去中擁有,在得到中失去。一個人只要善於利用每個機會,就會將不利轉化爲有利,讓人們在失去中尋求機遇,把握良機。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/FDxPamR5x6opob0rsPF-Sw