朱曄的網際網路架構實踐心得S1E5:不斷耕耘的基礎中間件

一般而言中間件和框架的區別是,中間件是獨立運行的用於處理某項專門業務的CS程序,會有配套的客戶端和服務端,框架雖然也是處理某個專門業務的但是它不是獨立程序,是寄宿在宿主程序進程內的一套類庫。

朱曄的互聯網架構實踐心得S1E5:不斷耕耘的基礎中間件

圖上綠色部分代表了框架,紅色部分代表了管理系統,紫色部分代表了中間件。本文會著重介紹管理系統和中間件部分。

配置管理

朱曄的互聯網架構實踐心得S1E5:不斷耕耘的基礎中間件

比較知名的分佈式配置服務和管理系統有攜程的https://github.com/ctripcorp/apollo(上圖)以及https://github.com/knightliao/disconf。對於比較大型的互聯網項目來說,因為業務繁雜,需求多變,往往各種系統都會有大量的配置,覆蓋幾個方面:

· 針對系統內部技術層面的各種配置,各種池的大小、 隊列的大小、日誌級別、各種路徑、批次大小、處理間隔、重試次數、超時時間等。

· 針對業務運營層面的各種配置,活動的週期獎勵、黑白名單、彈窗、廣告位等。

· 針對運維和發佈層面的配置,灰度名單、註冊中心地址、數據庫地址、緩存地址、MQ地址等。

因為一些基礎組件比如SOA框架和發佈系統也會用到配置,這個時候就會可能會有雞生蛋的問題,這裡我比較建議把配置系統作為最最底層的系統,其它服務都可以依賴配置系統。一般而言配置管理除了實現最基本的Key-Value的配置讀取和配置之外,還會有下面的一些特性和功能:

· 高性能。配置服務的壓力會是非常嚇人的,在一次服務調用中可能就會有幾十次的配置調用,如果服務的整體QPS在500那麼配置服務的壓力可能在1萬的QPS,這樣的QPS不走緩存基本是不可能的。好在即使是1萬甚至是5萬的QPS也不算一個很誇張的無法解決的壓力。

· 高可用。現在各種開源的配置服務是所謂的分佈式配置服務,由可擴展的配置服務集群來承擔負載均衡和高可用功能,配置服務一旦掛了可能會讓系統癱瘓。你可能會說配置服務一般本地會有緩存,會有本地的配置文件作為後備,會有默認值,但是因為配置是運營運維在實時修改的,如果某個業務的配置沒有使用最新的配置走的是錯誤的默認值的話,系統會處於完全混亂的狀態,所以配置服務的穩定性太重要了。

· 樹形的配置體系。如果只是把所有配置堆在一個列表裡,加上項目和分類的話,當配置多達幾千項的時候還是會有點多。可以支持樹形的層級配置,不拘泥於項目和分類這兩個條件。項目下可以有模塊,模塊下可以有分類,分類下可以有小類,根據自己的需求動態構建配置樹。

· 好用的客戶端。比如可以和SpringBoot以及@Value註解結合起來,非侵入整合配置系統,無需任何代碼的改動。

· 毫秒級粒度的修改實時生效。可以使用長連接推的方式實現,也可以實現緩存失效的方式實現。

· 配置的分層隔離。包括按照環境、集群和項目來提供多套配置相互獨立不影響,包括可以以層級的方式做配置繼承。

· 配置的權限控制。不同類型、環境、集群、項目的配置具有不同的管理權限,比如脫敏只讀、只讀、讀寫、導出。

· 配置的版本管理。配置的每一次修改都是一個版本,可以為單獨的配置或項目進行直接版本回滾。

· 豐富的Value形式。配置的Value如果要保存列表的話,保存一個JSON閱讀和修改都不方便,可以直接提供List方式的Value,在後臺可以單獨增刪改裡面的一項。在比如黑名單的引用上這種方式比較高效,否則更新一個名單每次都要修改整個黑名單。這個功能可以和Redis結合在一起進行實現。Value除了支持字符串可以是JSON和XML形式,系統可以對格式進行格式化,對格式進行校驗。Value也可以是非字符串類型的各種數字格式,系統也會根據類型進行校驗。

· 豐富的配置發佈生效形式。比如可以自然生效、立即生效以及定時生效。定時生效的功能適合於在某個時間點需要開啟某個配置,比如用於面向用戶的推送、活動業務。還有支持灰度自動發佈,以一定的時間間隔來對集群裡的實例進行發佈,避免人工去定期逐一發布單臺的麻煩。

· 審核審計功能。配置的修改可以由管理員進行審核(也就是修改和發佈的權限支持分離),避免配置錯誤修改。所有配置的修改記錄可以查詢到誰什麼時候因為什麼原因修改了什麼配置,事後可以審計審查。

· 配置生效跟蹤和使用率跟蹤。可以看到每一個配置項現在哪些客戶端在使用,生效的值的版本是哪個。通過這個功能還可以排查現在系統中過去一段時間從沒有用過的配置,刪除無用的配置。

· 動態配置。在API設計的時候我們引入上下文的概念,通過傳入一個Map字典作為上下文,比如某個配置按照不同的用戶類型、城市需要有不同的值,這個邏輯我們可以不需要在代碼裡面手工編寫,直接通過在後臺配置上下文的匹配策略來動態讀取到不同的配置值。

· 本地快照。對配置進行快照本地保存,在出現故障無法連接服務端的時候使用本地的配置。

這裡可以看到要實現一個功能完善的配置系統工作量還是相當大的,一個優秀的功能強大的配置系統可以節省很多開發的工作量,因為可配置部分的功能基本就是由配置系統直接實現了,無需在數據庫中在搞大量的XXConfig表(不誇張的說,很多業務系統40%的工作量在這個上面,不但需要做這些配置表還需要配以配置後臺)。

服務管理

微服務的建設中實現遠程調用只是實現了20%的工作量(但是確實滿足了80%的需求)。服務管理治理這塊有大量的工作要做。這也就是實現自己RPC框架的好處,這是第一步,有了這第一步讓數據流過我們自己的框架以後我們接可以做更多的事情,比如:

· 調用鏈跟蹤。能否記錄整個調用的情況,並且查看這個調用鏈。下面一節會再說一下這點。

· 註冊管理。查看服務的註冊情況,服務手動上線下線,集群切換,壓力分配干預。

· 配置管理。配置服務端客戶端線程池和隊列的配置,超時配置等等。當然,這個也可以在配置系統中進行。

· 運維層面的管理。查看和管理方法熔斷,進行併發限流配置,服務權限黑白名單配置,安全方面的配置(信息加密,日誌脫敏等)。

· Service Store的概念。服務發佈需要滿足一定要求,有文檔(比如可以通過註解方式在代碼註釋裡提供),有信息(開發負責人、運維負責人,服務類型,提供的能力),滿足要求後就可以以類似於蘋果App Store發佈程序的方式發佈服務,這樣我們就可以在統一的平臺上查看服務的維護信息和文檔。

· 版本控制調用統計。對服務進行灰度升級,按版本路由,不同版本調用分析等等。類似於一些應用統計平臺提供的功能(友盟、TalkingData)。

這裡我想說的理念是,服務能調用通是第一步,隨著服務數量變多,部署方式的複雜化,依賴關係複雜化,版本的迭代,API的變更,開發人員和架構師其實急需有一套地圖能夠對服務能力的全貌進行整體的瞭解,運維也需要有系統能對服務進行觀察和調配。服務治理的部分完全可以以iOS那套(開發符合時候需要符合標準+發佈的時候需要有流程)方式來運作。

全鏈路監控

朱曄的互聯網架構實踐心得S1E5:不斷耕耘的基礎中間件

開源的實現有https://github.com/dianping/cat以及https://github.com/naver/pinpoint(上圖)等等。對於微服務比較多的(主流程涉及8+微服務)系統,如果沒有服務的全鏈路調用跟蹤那麼排查故障以及性能問題就會很困難了。一般完善的全鏈路監控體系不僅僅覆蓋微服務,而且功能也會更豐富,實現下面的功能:

· 以Log、Agent、Proxy或整合進框架的方式實現,儘可能少的侵入的情況下實現數據的收集。而且確保數據的收集不會影響到主業務,收集服務端宕機的情況下業務不影響。

· 調用跟蹤。涉及到服務調用、緩存調用、數據庫調用,MQ調用,不僅僅可以以樹的形式呈現每次調用的類型、耗時、結果,還可以呈現完整的根,也就是對於網站請求呈現出請求的完整信息,對於Job任務呈現出Job的信息。

· JVM的信息(比如對於Java)。呈現每一個進程JVM層次的GC、Threads、Memory、CPU的使用情況。可以進行遠程Stack的查看和Heap的快照(沒有進程的內存信息,很多時候基於服務器層面粗粒度的資源使用情況的監控,基本不可能分析出根本原因),並且可以設定策略進行定期的快照。虛擬機的信息查看和調用跟蹤甚至可以通過快照進行關聯,在出現問題的時候能夠了解當時虛擬機的狀態對於排查問題是非常有好處的。

· 依賴關係一覽。有的時候我們做架構方案,第一步就是梳理模塊和服務之間的依賴關係,只有這樣我們才能確定影響範圍重構範圍,對於微服務做的比較複雜的項目來說,每個人可能只是關注自己服務的上下游,對於上游的上游和下游的下游完全不清楚,導致公司也沒有人可以說的清楚架構的全貌。這個時候我們有全鏈路跟蹤系統的話,可以對通過分析過去的調用來繪製出一張依賴關係的架構圖。這個圖如果對QPS做一些熱點的話,還可以幫助我們做一些運維層面的容量規劃。

· 高級分析建議。比如在全鏈路壓測後定位分析瓶頸所在。定時分析所有組件的執行性能,得出性能衰退的趨勢,提早進行問題預警。分析JVM的線程和GC情況,輔助定位High CPU和Memory Leak的問題。退一萬步說,即使沒有這樣的自動化的高級分析,有了調用跟蹤的圖和組件依賴關係圖,至少在出問題的時候我們人能分析出來咋回事。

· Dashboard。非必須,只要數據收集足夠全面,如之前文章所示,我們可以用Grafana來進行各種個性化的圖表配置。

數據訪問中間件

開源的實現有C實現的https://github.com/Qihoo360/Atlas以及Go實現的https://github.com/flike/kingshard 等。數據訪問中間件是獨立部署的數據庫的透明代理,本身需要是以集群方式支持高可用,背後還需要對接多套數據庫作為一個集群,一般而言會提供如下的功能:

· 最常用的功能就是讀寫分離。也包括負載均衡和故障轉移的功能,自動在多個從庫做負載均衡,通過可用性探測,在主庫出現故障的時候配合數據庫的高可用和複製做主庫的切換。

· 隨著數據量的增多需要分片功能。分片也就是Sharding,把數據按照一定的維度均勻分散到不同的表,然後把表分佈在多個物理數據庫中,實現壓力的分散。這裡寫入的Sharding一般而言沒有太多的差異,但是讀取方面因為涉及到歸併彙總的過程,如果要實現複雜功能的話還是比較麻煩的。由於分片的維度往往可能有多個,這方面可以採用多寫多個維度的底層表來實現也可以採用維度索引表方式來實現。

· 其它一些運維方面的功能。比如客戶端權限控制,黑白名單,限流,超時熔斷,和調用鏈搭配起來的調用跟蹤,全量操作的審計搜索,數據遷移輔助等等。

更高級的話可以實現SQL的優化功能。隨時進行SQL的Profiler,然後達到一定閾值後提供索引優化建議。類似https://github.com/Meituan-Dianping/SQLAdvisor。

· 其它。極少的代理實現了分佈式事務的功能(XA)。還可以實現代理層面的分佈式悲觀鎖的功能。其實細想一下,SQL因為並不是直接扔到數據庫執行,這裡的可能性就太多了,想幹啥都可以。

實現上一般需要做下面幾件事情:

· 有一個高性能的網絡模型,一般基於高性能的網絡框架實現,畢竟Proxy的網絡方面的性能不能成為瓶頸。

· 有一個MySQL協議的解析器,開源實現很多,拿過來直接用即可。

· 有一個SQL語法的解析器,Sharding以及讀寫分離免不了需要解析SQL,一般流程為SQL解析、查詢優化、SQL路由、SQL重寫,在把SQL提交到多臺數據庫執行後進行結果歸併。

· Proxy本身最好是無狀態節點,以集群方式實現高可用。

這些功能除了Proxy方式的實現還有和數據訪問標準結合起來的實現,比如改寫JDBC的框架方式實現,兩種實現方式各有優缺點。框架方式的實現不侷限於數據庫類型,性能略高,Proxy方式的實現支持任意的語言更透明,功能也可以做的更強大一些。最近還出現了邊車Sidecard方式實現的理念,類似於ServiceMesh的概念,網上有一些資料,但是這種方式到目前為止還沒看到成熟的實現。

分佈式緩存中間件

類似於數據庫的Proxy,這裡是以緩存服務作為後端,提供一些集群化的功能。比如以Redis為後端的開源的實現有https://github.com/CodisLabs/codis以及餓了麼的https://github.com/eleme/corvus 等等。其實不採用Proxy方式做,開發一個緩存客戶端在框架層面做也是完全可以的,但是之前也說了這兩種方式各有優劣。代理方式的話更透明,如果有Java、Python、Go都需要鏈接Redis,我們無需開發多套客戶端了。一般實現下面的功能:

· 分佈式。這是最基本的,通過各種算法把Key分散到各個節點,提供一定的容量規劃和容量報警功能。

· 高可用。配合Redis的一些高可用方案實現一定程度的高可用。

· 運維方面的功能。比如客戶端權限控制,黑白名單,限流,超時熔斷,全量操作的審計搜索,數據遷移輔助等等。

· 跟蹤和問題分析。配合全鏈路監控實現一體化的緩存訪問跟蹤。以及更智能的分析使用的情況,結合緩存的命中率,Value的大小,壓力平衡性提供一些優化建議和報警,儘早發現問題,緩存的崩盤往往是有前兆的。

· 完善的管理後臺,可以一覽集群的用量、性能,以及做容量規劃和遷移方案。

如果Redis集群特別大的話的確是有一套的自己的Proxy體系會更方便,小型項目一般用不到。

任務(Job)管理

之前有提高過,Job是我認為的互聯網架構體系中三馬車的三分之一,扮演了重要的角色。開源實現有http://elasticjob.io/。Job的管理的實現有兩種方式,一種是類似於框架的方式,也就是Job的進程是一直啟動著的,由框架在合適的時候調用方法去執行。一種是類似於外部服務的方式,也就是Job的進程是按需要在合適的機器啟動的。在本文一開始的圖中,我畫了一個任務調度的中間件,對於後一種方式的實現,我們需要有一套中間件或獨立的服務來複雜Job進程的拉起。整個過程如下:

· 找一些機器加入集群作為我們的底層服務器資源。

· Job編譯後打包部署到統一的地方。Job可以是各個語言實現的,這沒有關係。可以是裸程序,也可以使用Docker來實現。

· 在允許Job前我們需要對資源進行分配,估算一下Job大概需要怎麼樣的資源,然後根據執行頻次統一計算得出一個合適的資源分配。

· 由中間件根據每一個Job的時間配置在合適的時候把進程(或Docker)拉起執行,執行前根據當前的情況計算分配一個合適的機器,完成後釋放資源,下一次執行不一定在同一臺機器執行。

這樣的中間件是更底層的一套服務,一般而言任務框架會提供如下的功能:

· 分佈式。Job不會受限於單機,可以由集群來提供運行支持,可以隨著壓力的上升進行集群擴容,任何一臺機器的宕機不會成為問題。如果我們採用中間件方式的話,這個功能由底層的中間件來支持了。

· API層面提供豐富的Job執行方式。比如任務式的Job,拉數據和處理分開的Job。拉數據和處理分開的話,我們可以對數據的處理進行分片執行,實現類似Map-Reduce的效果。

· 執行依賴。我們可以配置Job的依賴關係實現自動化的Job執行流程分析。業務只管實現拆散的業務Job,Job的編排通過規則由框架分析出來。

· 整合到全鏈路監控體系的監控跟蹤。

· 豐富的管理後臺,提供統一的執行時間、數據取量配置,提供Job執行狀態和依賴分析一覽,查看執行歷史,運行、暫停、停止Job等等管理功能。

發佈管理

發佈管理其實和開發沒有太大的關聯,但是我覺得這也是整個體系閉環中的一個環節。發佈管理可以使用Jenkins等開源實現,在後期可能還是需要有自己的發佈系統。可以基於Jenkins再包一層,也可以如最開始的圖所示,直接基於通用的任務調度中間件實現底層的部署。一般而言,發佈管理有下面的功能:

· 豐富的任務類型和插件,支持各種語言程序的構建和發佈。有最基本的發佈、回滾、重啟、停止功能。

· 支持項目的依賴關係設置,實現自動化的依賴路徑上的程序自動發佈。

· 一些運維層面的控制。比如和CMDB結合做權限控制,做發佈窗口控制。

· 用於集群的發佈流程。比如可以一覽集群的分組,設置自動的灰度發佈方案。

· 適合自己公司的發佈流程。比如在流程控制上,我們是Dev環境到QA到Stage到Live。其中,QA環境經過QA的確認後可以進入Stage環境,經過開發主管的確認後可以到Stage環境,經過產品經理的確認後可以進入Live環境進行發佈。在發佈系統上我們可以結合OA做好這個流程的控制。

· 在構建的時候,集成單元測試,集成編碼規範檢查等等,在後臺可以方便的看到每一次發佈的代碼變更,測試執行情況以及代碼規範違例。

Jenkins等系統在對於1和2做的比較好,對於和公司層面其它系統的結合無能力為,往往處於這個原因我們需要在Jenkins之上包裝出來自己的發佈系統。

總結一下,之所以標題說不斷耕耘的基礎中間件,是指中間件也好框架也好,往往也需要一個小團隊來獨立維護,而且功能是不斷迭代增加,這套體系如果結合的好,就不僅僅是實現功能這個最基本的標準了,而是:

· 運維自動化API化和AI化的很重要的構成。把控是因為我們掌握了數據流,數據都是從我們的中間件穿越過去到達底層的服務、數據庫、緩存,有了把控就有了自動化的可能,有了智能監控一體化報警的可能。

· 也因為數據流的經過,通過對數據進行分析,我們可以給到開發很多建議,我們可以在這上面做很多標準。這些事情都可以由框架架構團隊默默去做,不需要業務研發的配合。

· 因為底層數據源的屏蔽,加上服務框架一起,我們實現的是業務系統被框架包圍而不是業務系統在使用框架和中間件這麼一個形態,那麼對於公司層面的一些大型架構改造,比如多活架構,我們可以實現業務系統的改造最小。數據+服務+流程都已經被中間件所包圍和感知,業務系統只是在實現業務功能而已,我們可以在業務系統無感知的情況下對數據做動態路由,對服務做動態調用,對流程做動態控制。如下圖,是不是有點Mesh的意思?

朱曄的互聯網架構實踐心得S1E5:不斷耕耘的基礎中間件

本文很多地方基於思考和YY,開源組件要實現這個理念需要有大量的修改和整合,很多大公司內部都一定程度做了這些事情,但是也因為框架的各種粘連依賴無法徹底開源,這塊工作要做好需要大量的時間精力,真的需要不斷耕耘和沉澱才能發展出適合自己公司技術棧的各種中間件和管理系統體系。


分享到:


相關文章: