朱曄的網際網路架構實踐心得S1E5:不斷耕耘的基礎中介軟體
朱曄的網際網路架構實踐心得S1E5:不斷耕耘的基礎中介軟體
【 ofollow,noindex">下載本文PDF進行閱讀 】
一般而言中介軟體和框架的區別是,中介軟體是獨立執行的用於處理某項專門業務的CS程式,會有配套的客戶端和服務端,框架雖然也是處理某個專門業務的但是它不是獨立程式,是寄宿在宿主程式程序內的一套類庫。
圖上綠色部分代表了框架,紅色部分代表了管理系統,紫色部分代表了中介軟體。本文會著重介紹管理系統和中介軟體部分。
配置管理
比較知名的分散式配置服務和管理系統有攜程的 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那套(開發符合時候需要符合標準+釋出的時候需要有流程)方式來運作。
全鏈路監控
開源的實現有 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實現的 360/Atlas" rel="nofollow,noindex" target="_blank">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的意思?
本文很多地方基於思考和YY,開源元件要實現這個理念需要有大量的修改和整合,很多大公司內部都一定程度做了這些事情,但是也因為框架的各種粘連依賴無法徹底開源,這塊工作要做好需要大量的時間精力,真的需要不斷耕耘和沉澱才能發展出適合自己公司技術棧的各種中介軟體和管理系統體系。