天天看點

微服務架構上雲最佳實踐

<b>摘要:</b>7月27日,雲栖社群、阿裡中間件舉辦了首屆阿裡巴巴中間件技術峰會,揭秘阿裡10年分布式技術幹貨。在首屆阿裡巴巴中間件技術峰會上,具有10年研發經驗的阿裡巴巴中間件技術專家李顔良結合edas團隊上雲兩年多以來積累的經驗為大家分享了如何進行微服務拆分、微服務架構上雲最佳實踐以及微服務架構常用的模式,精彩不容錯過。

<b></b>

以下内容根據演講嘉賓現場視訊以及ppt整理而成。

本次分享主要圍繞以下三個方面:

微服務拆分

服務化案例分享與最佳實踐

一些微服務架構的常用模式

<b>一、微服務拆分</b>

首先從真實的案例開始講起,當一些客戶接觸到了一些微服務之後,他們就會将原本經典的分層架構模式拆分成為微服務。如下圖所示,客戶原本的架構是比較經典的分層架構模型,可以看到每一層的結構都非常清晰,當此時想要做微服務拆分的時候第一步就是将dao層直接拆分出來。而大家都知道,這樣做肯定會有一些不妥之處,那麼不妥之處到底在哪裡呢?在這裡就埋下一個疑問,大家可以先思考一下對于這樣的經典的分層架構究竟應該如何去進行拆分。

微服務架構上雲最佳實踐

微服務的拆分其實要先從ddd開始說起,那麼什麼是ddd呢?ddd其實是一種軟體設計的指導思想,它可以指導我們設計一些高品質的軟體架構,ddd的核心是想讓技術人員和業務人員使用一種共同的語言并且都把關注點聚焦在業務層面,在這樣情況下去讨論、發現和實作業務的價值。ddd中有一些核心的概念,首先其中有“領域”的概念,所謂“領域”這個概念的意思就是團隊所關注的業務。在領域中我們會提煉出一些模型,而這些模型有的是需要口口相傳的;也有的是需要我們寫在開發文檔上面,便于新進入團隊的同學學習開發的;還有的是需要直接寫在給客戶看的文檔上面供客戶去了解的;等等這些我們稱之為為領域模型。在領域模型确定之後,就需要有一個稱之為“通用語言”的東西,這個通用語言就是用于大家進行交流的。大家可能會覺得這個是非常自然的事情,但是事實上并不是這樣的,這裡舉一個例子給大家解釋一下,比如兩個程式員之間進行溝通時,描述轉賬這個場景可能是這樣的:定義一個變量transformmoney接收這個錢這個值,從第一個賬戶上面把這個錢減掉然後加到另一個賬戶上面去,然後使用dao進行save操作就可以了,這是程式員之間很自然的溝通語言。但是如果這個時候有業務人員進來了之後,他可能并不了解這樣的描述,業務人員的描述可能是這樣的:轉賬的這筆錢可能會需要分為系統内部轉賬和系統外部轉賬,根據轉賬金額的大小可能會采用不同的審計政策,根據這些政策每一筆資金可能使不同的系統感覺到不同的事件,而且這些路徑可能需要做沉澱和記錄,可能會有交易的記錄産生等等,這樣描述出來可能技術人員也能夠聽懂,業務人員也能可以聽懂。

微服務架構上雲最佳實踐

這是什麼意思呢?其實大家可以仔細想想,上面的這段對話其實是可以直接翻譯成為代碼的,這就是所謂的通用語言。但是通用語言的産生過程也是不斷地提升的過程,就像代碼需要不斷地進行重構和完善。而上面這段對話其實需要在一定的邊界之内才會有意義,而邊界又是什麼意思呢?還是使用剛才轉賬的例子,轉賬時使用的賬戶和登入時使用的賬戶雖然都叫做賬戶,但是肯定不是同一個東西,這時候就産生了限界,也就是boundedcontext這個概念。

在介紹完這些基本概念之後,我們繼續分享ddd的戰略和戰術。從一個比較标準的解釋來說,戰略就是用于指導軍隊去打赢某場戰争的思想,也就是一種方法和謀略。而ddd的戰略當然不是用于打仗的,而是用于拆分領域等其他方面的。當然ddd的戰略和戰争的戰略有一定相同之處,比方在打仗的時候需要選好一塊什麼樣的地盤,以及在上面用什麼樣子的方式如何發動這場戰争,而這塊地盤在微服務拆分上就是可以了解成選擇一個什麼樣子的領域,這個領域可以分為核心域和子域。對于核心域而言,從我們的業務就能顯而易見地看出來它是差別于其他業務的一塊領域,也就是核心業務。如果這塊領域不複存在的話,那麼整個團隊或者公司就會出現問題。在核心域周圍則會存在支撐子域,比如對于電商業務而言,交易就是核心,核心域的旁邊則會有支撐核心域來區分業務的子域,比如圖中的物流和支付,這兩部分的支撐子域也是整個業務獨有的。當然也會有其他的部分,比如通用子域,這部分就是其他公司可能會有或者其他業務也會存在的領域,這部分的業務邏輯可能可以直接從某些開源軟體或者商業軟體拿過來使用,比如像每個公司都會使用的會員系統以及積分系統等。除此之外還有一些基礎的技術服務,比如像短信服務這樣的技術服務。

微服務架構上雲最佳實踐

當上面這樣整個圖建構完成之後,我們會發現每一塊領域之間,每一塊上下文之間都是存在一定的關系的。這些關系可能是比較簡單的上下遊的關系,除此之外,還可能存在客戶關系或者供應商關系,或者是共享同一塊核心、模型這樣的關系,比方說前台系統和背景系統往往會共享同一種資料模型,還可能存在兩個領域之間需要做很多的擴充卡等等,這些在ddd中也會有一些推薦架構模式去遵循。

微服務架構上雲最佳實踐

除了上述在架構模式上面的東西以外,在一個領域内部還會有一些戰術上的模式讓我們定義一些東西。還是使用剛剛那麼轉賬的例子來分析,每一個賬戶以及每一筆交易的記錄都是需要進行唯一性區分的,這些需要進行唯一性區分存儲的東西可以我們叫做實體,還有說轉賬的時候的那筆錢其實也會是一個對象,因為它可能存在匯率不同、币種等資訊,它也是以一個對象存在的,但是可能這個對象是不變的,因為我的三塊錢和你的三塊錢是一樣的,我們将這樣的對象稱為值對象。在做轉賬的時候,這個轉賬的過程可能會與某些交易機構或者其他的實體之間發生關系,是以需要内聚更加合理的地方,這個時候就出現了領域服務。然後回到剛才提到的需求之一就是每一筆錢都需要能夠被感覺到,要能夠感覺到就需要能夠産生或處理一些事件,這樣就會有一些領域事件出來。還有就是人與賬戶是什麼關系呢?想要拿到賬戶去做轉賬之前首先需要先找到人,此時這個人就會與賬戶出現一種聚合的關系,既然有了這樣聚合的關系;然後發現人對賬戶開通的過程,并不是簡單的new可以實作的,因為可能會需要綁定很多東西,開通很多東西,是以這時候很可能需要工廠來幫你的忙。同樣的道理,當你去做資源池的互動的時候,可能dao幫不了你,因為dao僅僅是對于db的各種動作的轉義,它其實是沒有領域含義的,是以這時候就會需要資源庫。

聊完了ddd這部分之後,我們再來看一下它和我們今天提到的微服務的拆分到底存在什麼樣的關系。對于服務拆分而言,首先可以看到剛才分享的根據公司内部的核心域或者業務内部的核心域或者業務能力,拆分其實很簡單,首先就是“一縱”,縱向拆分就像阿裡巴巴這樣将業務拆分成為淘寶、天貓、聚劃算以及鹹魚等。然後再是“一橫”,橫指的就是拆分成剛才介紹的那些領域,包括核心域、支撐子域以及通用子域等。然後再找一些東西,也就是找其他的一些基礎的通用子域、一些基礎的能力域等。

微服務架構上雲最佳實踐

當我們已經将服務拆分好了,那麼是不是這時候就可以真正地開始實作功能了呢?其實在真正開始做之前還需要好好思考一些問題,因為微服務沒有銀彈。首先第一個需要考慮的問題就是我們的服務的内聚和耦合是不是真的合理,因為内聚和耦合難以量化,是以這裡所強調的是合理。還有就是團隊的構成,因為微服務産生的目的就是為了減少團隊成員之間的溝通,那麼團隊構成是什麼意思呢?首先第一個要點就是團隊本身是需要全棧的,即自主性一定要很高。那麼團隊規模究竟應該多大呢?有一個參考的方法,就是“two pizzateam”,也就是正好吃完兩個披薩的團隊。第二點的一個關鍵問題就是問自己準備好組織架構的變化了嗎?這樣的說法來自于康威定律,這個定律說的核心思想可以了解為:一個公司的技術架構可能最終會發展成為公司的組織架構的樣子,當然組織架構也有可能發展成為技術架構的樣子。第三點就是微服務在未來肯定會面臨更多的挑戰,到底是哪些挑戰呢?在後續會為大家揭曉。

<b>二、服務化案例分享與最佳實踐</b>

接下來為大家分享一些實際的案例,這些案例是提取自edas上雲兩年多以來在客戶的環境中存在的實際問題。在正式介紹案例之前需要首先介紹幾個簡單的名詞:

hsf:阿裡巴巴集團所使用的 rpc 架構,全稱為 high speed framework,江湖人稱:“好舒服”。

edas:企業級分布式應用服務,阿裡巴巴中間件提供的雲上商業微服務解決方案。

vpc:virtual private cloud,虛拟私有雲服務。阿裡雲上的一個基礎網絡産品,為隔離使用者的網絡環境而生。

<b>服務化:開發篇</b>

下圖是hsf服務的簡單介紹。首先第一個要分享的案例就是開發的時候遇到的問題,在分享這個案例之前首先簡單地介紹一下hsf。hsf有一個注冊中心,這個注冊中心用來管理釋出和訂閱服務。當一個生産者啟動起來之後可能會對于自己的服務進行釋出,注冊中心監聽了這個服務就會發送給正在監聽的消費者,然後消費者拿到位址之後就可以直接進行rpc的調用了。

微服務架構上雲最佳實踐

那麼如何使用hsf服務呢,在開始一個rpc的使用之前肯定會需要定義一個服務,而定義這個服務是通過一個接口的方式進行的。消費端可以直接使用這個接口在spring容器的xml裡面聲明這是一個消費端,而生産者除了需要實作這個接口之外,也是需要向容器做聲明,表示自己是一個生産者。

微服務架構上雲最佳實踐

當将服務部署完成之後,就可以實作rpc真正的調用了;下圖是一個客戶真實的案例,案例中的請求會牽涉到一個非常重的task,客戶的想法是希望服務端做完這個 task 之後異步地通知給程序。如果在單程序層面下這樣寫代碼一點問題都沒有,甚至可以說是很優雅,但是在分布式環境下這樣寫卻是存在問題的,因為callback是回不來的,是以說我們寫代碼的思維方式需要發生一定的轉變。

微服務架構上雲最佳實踐

那麼除了這樣思維方式的變化還需要有什麼東西呢?在下圖中就為大家列舉了開發過程中的最佳實踐,這些最佳實踐有些是參考的集團規約、有一部分是自己從客戶的案例中整理的,沒有高深的技術,我了解起來完全是平時的一些小的點,大的點我不講、道行不深也講不出什麼感覺、而且大的點肯定是有一幫人和幫你一起考慮的,反倒是一些小的點是我們容易忽略的,因為隻有你一個人在思考,很容易因為偷懶或者着急上個廁所就忘了。小反而是一門更大的學問。這些小點就小到一行日志、一個參數的校驗、一個傳回值、一個命名等等。具體不多說,可以自己參考。

微服務架構上雲最佳實踐

<b>服務化:部署篇</b>

分享完了開發,接下來為大家分享關于部署的問題。曾經有客戶提出了一個工單,就是服務都能夠看到,但是還是會出現時好時壞的情況。我們仔細地審視了客戶部署的架構,發現客戶的架構是這樣的:他們使用了兩個vpc,這兩個vpc之間有生産者和消費者,還有一個在另外一個vpc中,這時候就會發現我們所看到的部署架構肯定是調用不通的,因為他們的網絡本身就是不通的。

微服務架構上雲最佳實踐

這裡為大家簡單地介紹一下vpc。vpc其實是為了隔離使用者網絡環境而生的網絡産品,可以簡單地了解成為服務放入到vpc中去會更加安全。大家可能會想如果隻是劃分網絡的話,通過路由器或者交換機這些也能夠做到,那麼為什麼要使用vpc呢?其實vpc除了劃分網絡的基礎功能之外,其實還可以實作跨可用區,可以實作同城容災,另外vpc中還有一些比較基礎的網絡産品,比方說可以使用snat/dnat做源位址和目的位址的轉換,甚至可以做vpc内的統一的安全組管理等等。在什麼樣的場景下面會使用到vpc呢?關于這一點在阿裡雲官網上面有詳細的介紹,在這裡就不再贅述,大概就是做混合雲架構或者有nat需求的時候就可以使用。

微服務架構上雲最佳實踐

接下來介紹hsf路由,剛才提到當消費者啟動的時候就會去做監聽,生産者啟動的時候就會去做釋出,當釋出的時候注冊中心知道這個位址之後就會推送給消費者,這就是一次簡單的服務發現過程。那麼第一次選擇連接配接的時候,假設位址清單中有兩個,一個好的一個壞的,這時候應該怎麼樣呢?首先第一次round robin選擇連一個,與此同時啟動心跳程序,進行心跳檢測,如果此時發現位址有問題就會把這個位址放在一個不可用連接配接位址的清單中,但是心跳還會繼續,當第二次再進行連接配接的時候過程與上述大緻相同,但是不會再去啟動一個心跳程序了。

微服務架構上雲最佳實踐

在edas中的運維層面可能有一些點需要列出來給使用者看的,因為經常會有使用者遇到這些問題。在這裡想要強調的點是 iptables ,很多使用者非常喜歡設定iptables,但是卻不建議大家去設定 iptables,首先它的學習成本比較高,其次它真的非常不好進行維護和管理以及知識的傳遞,而且幾乎不可能去實作批量的運維和管理。除此之外,在 iptables 中用到的那些功能基本上可以在雲上面使用vpc + nat的方式進行處理,除非需要有一些非常進階的特性,比如需要根據資料包中的字元串進行過濾等這樣的需求。

微服務架構上雲最佳實踐

<b>服務化:連調篇</b>

分享完部署我們再聊一聊連調,在連調過程中我們遇到了一些非常怪的事情,就是客戶的回報就是在雲上面部署的一個服務,有的人可以連接配接,但是有的人就是偶爾不可以連接配接,非常不穩定!我們看到這樣的現象之後就去客戶的伺服器上提取了一些關鍵的資訊,其中的就包括這些:客戶開啟了recycle以及timestamps,然後主要說有nat網關,而且辦公網下的開發機器上存在時間戳不一緻的情況,說到了這裡很多有經驗的同學就已經猜出來問題到底是怎麼一回事了,原因就如下圖中左邊所列舉的這樣。

微服務架構上雲最佳實踐

說到這裡可能有同學已經猜出來了是怎麼回事了,這裡我簡單簡述一下造成這個問題的原因,首先要從伺服器端主動關閉連接配接的 tcp timewait 狀态開始說起,顧名思義,這個狀态是在等,等什麼呢?確定 server 的最後一個 ack 可以到達用戶端,因為用戶端在等着這個 ack 來關閉她的 tcp 連接配接,等多久呢? 2msl,這是一個很長的時間了,根據不同的系統不一樣,有的是60s,有的是一百多秒。那麼開啟 recyle 會有什麼好處?加速回收,怎麼加速的?他的原理是基于 rto 的,所謂 rto 就是資料包的逾時重傳的時間,這個時間怎麼來的呢?基于一些值算出來的,但是會很短,最短可以到200 ms,簡單的說起來就是從幾十秒一下降到零點幾秒級别的時延,這是一個很大的改進,我覺得也很科學。但是這樣沒有規避根本問題,畢竟人家一開始等那麼長時間是有道理的,萬一真的有資料包隔了那麼長時間才到咋辦呢?是以這裡還有一種機制,叫做 paws,這兩哥們就會把同一個 host 過來的資料包基于時間戳單調遞增的這麼一個假設,會拒絕掉一些 server 認為不合理的包,且這個時間錯是一個 per host 的紀錄,因為上一個連結都不在了,肯定要 per host 的都行了對吧?

微服務架構上雲最佳實踐

回到我們目前的這個案例,當兩台機器通過 nat 網關進行通路的時候,伺服器端看到的是一個 host,當有時間不一緻的時候,根據我們剛剛說的這些論斷,有些資料包就自然被 drop 掉了,這個問題我們也引申出來了一些建議的做法。首先無論什麼樣的環境,伺服器上的時間戳一定要 check 是不是一緻,尤其是有 https 和 openapi 調用的場景。還有生産環境我們不怎麼推薦使用 recycle 的,建議設定合理的 timewait buckets 代替。同時盡量避免在生産環境使用 nat,最好走 slb 的轉發。

微服務架構上雲最佳實踐

<b>服務化:壓測</b>

接下來分享一下關于壓測的内容,這裡講的壓測可能會與大家通常了解的場景不太一樣,因為是客戶在做壓測的時候拿着我們的rpc架構和另外一個目前比較流行的restful的架構進行對比,給回來的回報是性能不行。在接到這樣的回報之後,我們大概看了一下客戶的代碼是什麼樣子,客戶的代碼大概是将一個對象使用json序列化之後做rpc,rpc回來之後也是一個string,之後再将這個string反序列化成為一個對象傳回給用戶端,也就是客戶在代碼中做了兩次json的序列化。在hsf中大家都知道它是位元組支援序列化的,這裡的兩次序列化是浪費的;這種寫法很普遍,希望大家注意。

微服務架構上雲最佳實踐

還有一種典型的問題就是将一個request直接轉發出去,這種方式我們知道也是不行的。在hsf中支援的方式是java的native以及hessian等,目前在序列化方面其實是不需要關注太多的性能的,整個架構對于性能而言已經優化的足夠好了,除此之外在做序列化的時候這裡也列出了一些大家比較容易遺漏的點,這些點就包括了為什麼 http 對象不行,因為它其實一個不可序列化的對象。總結而言,就是在每一次序列化之前要好好思考一下這個對象是不是可以序列化的;其次在序列化之前還需要好好想想序列化之後的位元組大小為多少,是不是一個很大的對象。還有就是需要對于一些特殊的場景多留一些心眼,比如說枚舉、單例和一些範型等。

微服務架構上雲最佳實踐

<b>服務化:上線運作</b>

下圖的這個案例就是hsf的線程池滿了,這個問題我相信很多同學都已經遇到過,針對于這個問題這裡首先列出了一個大概的時序圖,也就是可能在自己的業務中需要依賴于外部服務,可能就會影響到内部的情況。

微服務架構上雲最佳實踐

當客戶與我們溝通的時候我們首先解釋了為什麼連接配接池會滿,首先簡單介紹一下hsf三種調用模型,第一種就是很自然的同步調用模式,就是一個一個地來,調用完成之後再回去;第二種情況就是異步調用,就是調用外部服務的請求不在乎調用的請求什麼時候傳回,調用之後就繼續做自己的事情,對下圖中的執行個體而言,我在發送驗證碼的之後就可以繼續執行後續的步驟,這樣的好處就是使用者的體驗将會得到提升,但是這種方式會帶來一些業務的損耗,如果短信真的發送失敗了則可能無法感覺,系統認為使用者拿到驗證碼了,但是事實上并沒有;是以比較推薦的是第三種方式——future的方式,這種方式還是基于異步的,future是什麼意思呢?其實是可以程序可以先去幹自己的活,如果想要結果可以來拿,當自己的任務完成再回頭取得異步調用結果的時候可能結果也就回來了,如果此時結果還沒有傳回回來,那麼在這裡等待也不會影響太大的事情。

微服務架構上雲最佳實踐

在介紹完這三種hsf的調用模型之後,還需要介紹一些線程池的模型,畢竟這個案例是與線程池相關的。其實hsf中有三種線程池,第一個就是io線程,但是這個線程池中線程比較少,因為其所做的工作也很少,基本上就做兩件事情:序列化和處理協定;第二個線程池就是和我們這個案例息息相關的,在server端會有一個很大的線程池,這個線程池預設的最大值可以達到600,當談到hsf的線程池滿了也就是說這個地方滿了。還有一個地方是大家比較容易忽略的,就是在tomcat的入口其實也有一個線程池,這個線程池其實決定了服務的并發數,但是這個并發數并不是單單由這個線程池決定的,理論上還需要加上另外一個字段accept count。

微服務架構上雲最佳實踐

當我們向客戶介紹完了以上兩種的内部實作的時候,就開始進行優化了,優化的過程也是根據剛才的思路進行的,第一種方式主要是将調用的方法進行了修改,第二種方式則是将一些線程池的參數調高了,把tomcat線程池入口的并發數以及hsf的線程池的值調高了,然而這樣的調整隻是緩解了一些問題,但是并沒有解決最根本的問題。然後使用者下意識地采取了在雲上進行擴容的方式,然後“奇迹”出現了,整個服務全挂掉了。

微服務架構上雲最佳實踐

為什麼服務會挂掉呢?後來經過了解發現客戶的架構是這樣的:使用redis特别容易忽略掉一些東西,這些東西從運維的層面上講會有一些規格,這些規格主要分為兩種,包括連接配接數和帶寬。很多運維人員誤以為在雲上面的環境走内網沒有帶寬限制,其實完全錯了,是以對于雲上規格模型一定要特别清楚。還有就是在開發層面的問題,在開發時,大家都喜歡使用common-pool,并且預設喜歡設定成為一個很大的值,不管使不使用這些個連接配接都可能會預設起來這麼多連接配接,比如所有的機器都用50,機器規模擴容到百級别的時候就可能會撐死。還有就是在開發時很喜歡使用一些list,把這些list序列化成json然後存儲為string,再從list傳回僅有的幾個元素,這些也是會引起問題的原因,以上這些就是我們用 redis 的時候會遇到的一些問題。

微服務架構上雲最佳實踐

<b>三、一些微服務架構的常用模式</b>

<b>服務化:模式</b>

在這裡大家可能就會希望有一種東西可以動态地調整redis這部分的連接配接數,其實這就是微服務中的一種模式叫做externalized configuration——外部配置動态推送,也就是通過外部的某些配置批量化地更新某些東西。

微服務架構上雲最佳實踐

剛才提到的是因為某些東西不行了導緻網站會怎麼樣,這時候可以選擇考慮當發生故障的時候可以選擇将這個服務進行限流降級或者甚至将整個鍊路都進行降級,這就是微服務推薦的第二種模式,circuit breaker——限流降級。

微服務架構上雲最佳實踐

之前介紹的兩個應用比較簡單,隻有兩個服務之間的模型。當面對如下圖所示的這樣的情況下就需要使用第三個模式distributed tracing——分布式追蹤。

微服務架構上雲最佳實踐

當然其實微服務的模式不止上面提到的這三種,下圖就展現了整個微服務這部分能夠想到的一些模式。首先我們需要這樣的一個基礎元件,這個基礎元件包含了一些東西,包括了一些架構的思想,以及服務之間的架構如何進行編排互動,還有采用什麼樣的部署模式,包括應該采用單個容器級别的部署還是單個機器級别的部署,甚至是單個vm級别的部署。還需要制定某一種服務發現政策等;當在真正決定好了架構之後,可能需要真正的微服務的底座來支撐起整個微服務的架構,底座規劃好之後就需要選擇服務之間的通訊模型。那麼通訊模型選擇好之後,就需要開始考慮服務高可用方面的事情了。當這方面規劃之後就需要考慮到一些日志、審計以及分布式追蹤等等這些資料化營運之類的東西。還有一點比較重要的就是當要對于服務進行拆分就需要考慮資料庫到底是該分還是該合。如果需要分的話,那麼問題來了,資料的一緻性今後應該如何保證,當然這裡也有一些模式可尋的;還有分開之前資料可能是從一個地方取,現在需要從各個地方取了、測試也是一樣,以前可能是測一塊,現在可能會需要測試一打,這些都是在微服務拆分完之後需要面對的挑戰,這也是本次最想傳遞給大家的東西。

微服務架構上雲最佳實踐

當然挑戰之下必有陪伴,edas就是陪伴大家的解決方案。

微服務架構上雲最佳實踐