“分散式”學習與專案總結
所謂“當局者迷,旁觀者清”,當我迷惑於當前知識的時候,證明我正平行於或低於該知識平面高度去學習這些知識,結果只有一個——“混亂”。因為自身沒有一個高層次的“綱”讓自己清晰且邏輯地“收編”和“彙總”這些知識,也許“知識焦慮”就這麼來的。在管理層面上,面對矩陣型組織架構上X軸(專案經理)與Y軸(職能經理)平面上的矛盾,我會通過高層次的“目標共性”來一致性專案經理和職能經理的共同目標,讓“矛盾”轉化“合作”,但面對“知識學習”的時候,我卻忽略知識的“結構性”問題。所以每當自己“情緒”萌生之前,我會立刻讓自己冷靜片刻,儘量讓自己理性地分析物件的抽象所在。例如面對“微服務”一大堆知識焦慮的時候,我知道自身缺乏了對“微服務知識”管理和彙總的“綱”,而我目前思考得出的這個綱就是對“分散式”的一個系統性認知。

知識架構
為什麼要“分”?
分散式的重點在於“分”,而“分”就是人類普遍的正常性思維。當我們面對一個複雜或者龐大的問題是,我們習慣性地會把它分而治之。這種“分”的技巧貫穿於我們的日常之中,如“大事化小,小事化無”。如下圖所示:

分而治之
“分”之前首先要做的是“識別問題”,這個問題的複雜度到底需不需要分解,就算分到底需要分解到什麼程度,就像我們架構一個應用系統的時候,我們會依據各種邊界條件去考慮要不要把系統分解,如何分解等。根據各種經驗彙總,分散式系統主要有兩方面的原因:
增大系統容量(業務複雜度);
加強系統可用(使用者量增長);
“分” 和“不分”有什麼不同?
活到現在,我覺得這個世界上最有“哲理”的一個詞叫“權衡(trade-off)”。任何事情總有兩面,從道教學角度看,這叫“取捨之道”,從經濟學角度看,這叫“機會成本”。不同選擇肯定會帶來不同的成本,首先我們得會識別問題,然後從結果成本反推方案選擇。例如面對“分”和“不分”時,我們先來看看這兩種方案的優和劣:

方案優劣對比
以上分析都是一些“通用維度”,不同問題和場景可能還會有其他的對比維度。但我覺得僅僅從文字分析可能還不夠直觀,如果我嘗試把“分”和“不分”的問題從“二維”換成“三維”可能會更加直觀和深入。

立體分解
從“二維角度”只看到系統的“平分”,真正的分散式是存在“橫向”和“豎向”分離的事實,從以上“立體維度”可以讓我們更加清晰地認識到分散式相對單體的複雜並在未來的實踐中所需要面臨的各種各樣的問題需要去權衡。
分散式系統的發展歷史
分散式系統是基於SOA(面向服務架構)方法的一種架構方式,從20世紀70年代流行模組化程式設計。80年代流行面向事件設計,90年代開始流行基於介面/構件設計(SOA方法的起始)。回顧歷史有助於我們全域性視野的構建,接下來看看分散式系統的三種不同的SAO架構模式:

SOA架構演變
Pre-SOA(1990s): 20世紀90年代的時候,各個服務模組之間是直接相互呼叫的,從而造成了服務與服務之間的緊耦合問題;
Traditional SOA: 21世紀初期為了解決服務緊耦合的問題,各大IT廠商檢視通過“中介軟體”把服務之間的關聯解耦(負責路由、負載、協議轉換等),如ESB,但這個中介軟體會顯得過於臃腫和集中。
Microservice: 從2010年開始,為了讓架構更加輕量,出現了微服務架構,把系統根據業務領域分解成各個獨立的服務,並且資料庫服務等垂直元件跟著分散到不同的服務當中,做到真正的服務獨自執行。從上圖可以看到,跟傳統SOA的不同在於服務之間的整合需要一個服務編排或服務整合“元件引擎”來組織業務。
分散式系統的問題總結
從分散式架構的發展歷程可以看到,系統架構會隨著時間和發展不斷湧現出新的問題,從而去觸發系統架構模式的演變。經過了這麼多年下來的經驗和實踐積累,前輩們大概總結出了分散式系統在技術上大概需要注意的問題有四大類:
① 異構系統不標準問題: 不同系統之間的架構不統一,協議不統一,語言不統一等問題;
② 系統服務依賴性問題: 服務呼叫鏈過長,多米諾骨牌效應,關鍵服務的識別等問題;
③ 系統故障的概率問題: 隨著資源的增加,如何確保故障時長以及故障影響面積問題;
④ 多層架構的運維問題: 如何確保基礎層(LaaS)、平臺層(PaaS)、應用層(SaaS)以及接入層的問題統一運維問題;

分散式問題彙總
分散式系統的關鍵技術
所謂“問題觸發需求”,如果站在以上“問題”的角度出發,可能更能讓自己從高維度瞭解、分析和把握我們所面對的各種各樣的開源技術和工具(如K8S,Docker、Spring Cloud等),而不至於產生無從下手的“焦慮”。很多時候,我們往往會被“分”出來的問題所面對的技術細節給迷惑了,而忽略了“分”之前的整體認知。缺少了對問題整體性的認知,我們是無法體會到基礎性知識的魅力和存在價值。面對以上4大類分散式系統問題,前輩們同樣總結出了以下4大塊分散式系統的關鍵技術點:
① 應用整體監控: 包括基礎層監控、平臺(中介軟體)層監控、應用層監控(包括客戶端);
② 資源/服務排程: 包括計算資源排程、服務排程、架構排程;
③ 狀態/資料排程: 包括資料可用性、資料一致性、資料分散式;
④ 流量排程: 包括服務治理、流量管控、流量管理;
作為技術人員,真心覺得自己非常容易陷入“釘子思維”,主要是自身缺乏這個抽象與整體的視野高度而深陷於技術細節當中,不是說技術細節沒用,只不過連一個“主要解決什麼問題”的總體性認知都沒有,技術細節會更難以理解,焦慮感油然而生。

分散式關鍵技術
關鍵技術一:應用整體監控
以上提到了監控的層面主要包括基礎層、平臺層、應用層的三個層面,對於一個分散式系統監控工具來說,至少包含全棧監控、(各層面)關聯分析、實時告警與自動化運維以及系統性能分析等功能。一個完整的監控系統大概如下:

監控概要
監控本身就是一件向“可控”看齊的事情,但如果缺乏一定的總體性認知(例如技能分工型組織),該運維小組可能會以 監控資料量多 為考慮方向(指標),那可能會本末倒置地把原本做簡單的事情複雜化,畢竟監控數量太多等於沒有資料一樣,不知從何入手,所以監控還必須具備一定的大局視野。例如整個系統架構的分佈地圖,每個分佈點(地圖)的關鍵程度以及每個業務流程的走向(服務呼叫鏈)等全域性資訊,才能聚合各種關鍵監控指標,達到一個整體監控的巨集觀效果。並且通過整個業務的拓撲關係快速定位異常和告警,進入更深層次的監控明細層面去排除問題。
■專案總結
作為一個從0到1到並持續做了八年(7*24小時執行)的應用系統,在運維層面感受最深的就是監控資料量從單體應用(兩臺刀片機)到分散式(微服務)應用(數千萬客戶端、上千個應用節點、數百個Redis例項,並對接N個外圍系統等)的指數增長,監控資料種類和量的收集大得驚人。系統從2014年切換到微服務框架後,一致執行良好,當然,我覺得這一切的功勞歸功於我們剛開始堅持的“簡單化”與“標準化”原則。
在系統架構演變的初期,我們在系統架構的建設以及規範上我們下了很大的功夫,始終保持著我們的兩大原則,各種標準化的程式碼開發和日誌輸出為我們後續的發展打下堅實的基礎。在2016年沒有成立專門的“運維組”之前,整個系統(後端)都是靠我們數個後臺同事開發並維護的,你沒有聽錯,是數個,而不是數十個。也因為這個原因,讓我們避免了 “監控任務被隔離” 以及 “監控資料量太多” 的常見運維問題。在我們當時數人開發兼維護的情況下,自動化維護和監控是必不可少的手段。除了定時的關鍵和敏感指標監控外,我們會根據整個系統的 “業務拓撲關係” 做一個 “關聯指標聚合” 的展示頁,可以從整體瞭解系統的整體概況。關鍵的聚合指標包括關鍵服務併發量、關鍵服務容器執行緒數、關鍵服務API耗時分佈、關鍵Redis服務效能分佈關鍵資料來源效能分佈、關鍵外圍系統耗時分佈、關鍵業務錯誤率概況以及各種TOP10的質量監控等。下圖是我們運維繫統初期的監視一角(沒有太華麗的UI)。

初期檢視監控一角
關鍵技術二:資源/服務排程
當單體應用被拆分後,業務場景的完成必須依賴於後端各個服務之間的排程和關聯來完成的。當然,最理想的狀態就是服務之間沒有關聯(最低複雜度),直接可以滿足於業務應用的消費。這不是不可能,如果系統和業務不復雜的話,可以把服務編排的工作直接交給客戶端去做。我們系統的部分功能就選擇了這樣一種折中的方案,這樣會增加了HTTP連線效能消耗,但降低了整個系統服務之間排程的複雜度。

服務編排
雖然我們可以通過各種方式去規避問題,但作為分散式系統的排程問題,服務治理是不可避免的一個關鍵性問題,其主要的關鍵技術有以下幾點:
服務關鍵程度
從以上監控層面可以看到,我們會區分關鍵服務和非關鍵服務,這種區分並非技術問題,而是業務問題。對服務關鍵程度的劃分可以讓系統在整體層面增加一個“層次感”,讓系統策略可以做得更加精準,如非關鍵服務降級等問題。

服務關鍵度管理
服務依賴關係
要做到無服務依賴確實非常困難,但卻是分散式系統設計的一個重點方向,所謂“沒有依賴就沒有傷害”。傳統的SOA是通過ESB來解決服務間依賴的問題,而 微服務就是分散式系統服務依賴的最優解上限 ,而服務依賴的下限就是千萬不好有閉環依賴,出現依賴環的設計是有問題的設計。在服務依賴管理方面,我們系統對服務關聯做了一定的管理措施,例如,我們服務的開發是分工的,如果服務A呼叫了服務B,而服務B具體又呼叫了什麼服務,服務A的開發小組是不得而知的,所以我們在開發階段做系統服務依賴配置的時候(必須配置,因為我們服務之間呼叫強制驗證,這就是我們付出一定效能開銷的管理成本),各個小組都可以實時看到整個系統服務的呼叫依賴圖,當配置服務依賴的時候系統實時檢查這條服務呼叫鏈的深度,並達到一定的閥值是提出告警或禁止配置,當出現這樣的問題後可以提交給上層設計團隊去評估設計合理性。雖然我們整個系統都是基於微服務理念設計,但開發團隊大了,什麼鳥都會有,這些額外的技術管理成本有時候也是無可奈何。

服務閉環依賴監控
服務發現
有了以上的的“地圖”第一步,接下來需要對這整個系統地圖的服務狀態和生命週期管理的重要工具,畢竟系統是由各種服務組成的,分散式之所以靈活是因為其服務的動態性,在系統執行過程中,有的服務會增加,有的服務會離開,有的服務生效中,有的服務失效了等等。從管理角度,我們需要知道一個服務註冊中心來做以下幾個事:

服務管控內容
在我們的生產系統中,服務的種類、數量數量以及狀態資訊是具備的,但我們並沒有對服務進行版本管理,只對服務的API進行版本管理,並對服務API相容性做了強制性的“規範”要求(只能增加輸出,不能修改或刪除輸出)。
架構版本管理
因為系統被拆分成各種服務單元,而不同的服務單元有一定概率是不同團隊開發,所以會存在服務單元版本依賴問題,超大型系統甚至會存在作業系統層面以及中介軟體層面不同版本的管理問題。如果使用過maven專案模型管理工具的話,就會知道pom.xml檔案,它管理著各個模組以及版本之間的關係,專案的建立也是基於pom.xml之上,我們的服務同樣可以通過這種模式進行管理,但成本就會增加。目前在我們的生產系統中,並沒有這種版本管理,我們是通過規範性手段避免了這種版本管理的問題,目前來說,暫時沒有出現這種服務版本依賴性而導致回退衝突等問題。
服務生命週期管理
當我們有了服務發現中心這個管控工具之後,就需要對服務的狀態進行管理了,嚴格來說,服務的狀態會有一下幾種:
① Provison:代表在供應一個新的服務;
② Ready:表示啟動成功了;
③ Run:表示通過了服務健康檢查;
④ Update:表示在升級中;
⑤ Rollback:表示在回滾中;
⑥ Scale:表示正在伸縮中(其中包括Scale-in和Scale-out)
⑦ Destroy:表示在銷燬中
⑧ Failed:表示失敗狀態
如果需要控制好整個分散式系統架構,就需要對以上服務狀態進行嚴格管理,特別是在多協作團隊中,這寫狀態的把控十分重要,因為服務會出現各種不預期(故障)和預期(上線或更新)的變化。現在整個完整的地圖、服務之間的依賴關係以及服務的狀態管控,整個完整分散式系統的管理手段會讓整個系統清晰起來。在我們生產系統中,對於服務的狀態並沒有這麼嚴格,目前只用到“執行”、“未知”以及“異常”三種狀態,部分其它狀態,我們統一用“未知狀態”代替了。
關鍵技術三:狀態/資料排程
對於單體應用來說,並沒有太多的“可能性”,複雜度和維護成本自然而然不會太高,所以不會存在系統拆分後的種種新問題。因為服務拆分了,並且業務場景需要各種服務之間通過排程協助來完成的,所以各個“點”之間的狀態就格外重要了。這裡的狀態(state)指的是服務自身的資料狀態,而不是執行狀態(status)。一般來說,我們都會通過第三方服務維護系統服務的資料狀態,如Mysql、Redis、ZooKeep或NFS等檔案系統,從而讓服務變成“無狀態服務”,以便於服務的靈活變更和擴充套件,甚至可以往serverless模式發展。所以很多時候,分散式系統架構中出現的問題往往都是這些儲存狀態的服務,並且資料儲存節點的Scale問題也落在了這些儲存狀態服務當中。

服務狀態轉移
要解決像資料儲存點這樣的單點問題,就要解決資料的複製(Replication)問題,也就是讓資料服務可以像無狀態的服務一樣在不同的裝置上進行排程,而資料的複製問題又會帶來資料一致性的2問題,從而帶來更多的效能問題。對於解決資料副本間的一致性問題,目前比較常見的方案是有:Master-Slave方案、Master-Master方案、兩階段和三階段提交方案、Paxos方案,而各種方案的優缺點可見下圖:

資料一致性方案對比
在我們生產系統中,核心的業務我們主要還是用到了商業的Master-Master方案(Oracle共享儲存),在非關係資料庫方面(Redis)我們幾乎都是自主研發的2PC方案元件。我們使用Redis的時候,最新版的穩定版本好像只有2.8,並不存在目前3.0+的叢集方案,所以我們自己在應用服務層寫了許多不同場景的2PC方案,為什麼會很多,因為不同場景的業務不一致導致業務補償的方案有很大的不同,許多的場景目前還一直穩定執行著。當然,從分工來看,狀態資料排程應該由分散式儲存層(Laas)來解決,這樣對於應用層來說是透明的,就像使用單點儲存一樣簡單。
關鍵技術四:流量排程
無論對單體應用還是分散式應用,流量都是需要控制的,對於單體應用而言,更多隻是簡單外部流量控制措施,但當系統分解後,因為服務排程的存在,所以流程同樣需要排程。

流量排程
分散式系統可以把流量排程分為內部排程和外部排程,內部是因為服務編排(呼叫鏈)的存在,外部是因為業務被分解隔離的緣故。對於一個流量排程器來說,其主要功能有兩點:
① 可以根據系統的執行情況自動地去調整流量排程,自動化地完成整個系統的穩定性;
② 可以彈性地計算並解決系統各種突發性事件,確保系統始終保持平穩執行;
說白了,以上功能都是為了確保系統的高可用和穩定性。其實,流量元件可以完成的事情可以包含如下:
服務流量: 服務發現、服務路由、服務降級、服務熔斷、服務保護等;
流量控制: 負載均衡、流量分配、流量控制、異地災備(多活)等;
流量管理: 協議轉換、請求校驗、資料快取、資料計算等;
如果從這些功能角度去看,這個閘道器(API Gateway)其實就跟運維繫統一樣,是一個實實在在的流量排程系統。從功能種類來說,這個API閘道器會略顯臃腫,很容易會成為“瓶頸”。所以,作為一個能扛得住的流量的API閘道器,“高效能實現”、“抗大流量”、“簡單的業務邏輯”以及還有“服務化設計”等幾大關鍵技術點必須得認真思考和衡量。
我們生產系統中,流量排程我們分開了三個層面來管理,總API閘道器入口,各服務閘道器入口,以及服務入口,因為我們當初考慮到閘道器作為入口的重要性,我們把以上API閘道器的部分功能(如快取、熔斷)下放到了服務實現層面去實現了。由於業務原因,我們系統並沒有做得很“自動化”,全部靠監控告警以及人工熔斷操作。因為客戶組織複雜的組織架構原因,我們當初各種自動化容災以及降級方案都“政治性地”否定了,只能把方案實現預留Admin API,隨時通過總控制檯人工干預。

星型架構
最後的總結
越學習越發現,有時候努力很重要,但方法更重要。大量的零碎知識等於沒有知識,知識焦慮是必然結果。知識之間是相關聯的,有時候一篇數千字的技術文章,我會看上個把月。因為自身基礎不紮實的原因,缺乏一定的知識高度去消化,所以許多知識點我都必須深入學習才能略懂一二。今天數千字的學習總結,背後承載的學習資料可能數十萬文字的學習和研究,還有大量地反思和實踐總結。有時候明明知道,但又無動於衷,這顯然就是自我突破的關鍵點所在。