高併發架構演進之路(下)——一體化架構到微服務

一體化機構問題:

1、在技術層面上,數據庫連接數可能成爲系統的瓶頸;資源極限,擴展困難

2、繼續演進研發成本、共同成本高,業務耦合大,查問題複雜,團隊管理困難

3、運維成本大,編譯、測試上線複雜

解決方案:

按照業務做橫向拆分的方式,解決了數據庫層面的擴展性問題;將與業務無關的公用服務抽取出來,下沉成單獨的服務。

服務拆分時要遵循哪些原則?

服務的邊界如何確定?

服務的粒度是怎樣呢?

在服務化之後,會遇到哪些問題呢?

原則一,做到單一服務內部功能的高內聚,和低耦合

原則二,你需要關注服務拆分的粒度,先粗略拆分,再逐漸細化。

原則三,拆分的過程,要儘量避免影響產品的日常功能迭代,也就是說,要一邊做產品功能迭代,一邊完成服務化拆分。先邊緣獨立,再依賴優先。

原則四,服務接口的定義要具備可擴展性。

微服務化帶來的問題和解決思路

1. 服務接口的調用,不再是同一進程內的方法調用,而是跨進程的網絡調用,這會增加接口響應時間的增加。——私有網絡協議RPC

2. 多個服務之間有着錯綜複雜的依賴關係。——微服務註冊

3. 服務拆分到多個進程後,一條請求的調用鏈路上,涉及多個服務,那麼一旦這個請求的響應時間增長,或者是出現錯誤,我們就很難知道,是哪一個服務出現的問題——分佈式追蹤、監控

如何提升網絡傳輸性能:

選擇好的網絡

網絡參數調優

優化編解碼速度

所謂 I/O 模型,就是我們處理 I/O 的方式。而一般單次 I/O 請求會分爲兩個階段,每個階段對於 I/O 的處理方式是不同的。

首先,I/O 會經歷一個等待資源的階段,比方說,等待網絡傳輸數據可用,在這個過程中我們對 I/O 會有兩種處理方式:

阻塞。指的是在數據不可用時,I/O 請求一直阻塞,直到數據返回;

非阻塞。指的是數據不可用時,I/O 請求立即返回,直到被通知資源可用爲止。

然後是使用資源的階段,比如說從網絡上接收到數據,並且拷貝到應用程序的緩衝區裏面。在這個階段我們也會有兩種處理方式:

同步處理。指的是 I/O 請求在讀取或者寫入數據時會阻塞,直到讀取或者寫入數據完成;

異步處理。指的是 I/O 請求在讀取或者寫入數據時立即返回,當操作系統處理完成 I/O 請求,並且將數據拷貝到用戶提供的緩衝區後,再通知應用 I/O 請求執行完成。

將這兩個階段的四種處理方式,做一些排列組合,再做一些補充,就得到了我們常見的五種 I/O 模型:

同步阻塞 I/O

同步非阻塞 I/O

同步多路 I/O

複用信號驅動 I/O

異步 I/O

1. 選擇高性能的 I/O 模型,這裏我推薦使用同步多路 I/O 複用模型;

2. 調試網絡參數,這裏面有一些經驗值的推薦。比如將 tcp_nodelay 設置爲 true,也有一些參數需要在運行中來調試,比如接受緩衝區和發送緩衝區的大小,客戶端連接請求緩衝隊列的大小(back log)等等;

3. 序列化協議依據具體業務來選擇。如果對性能要求不高,可以選擇 JSON,否則可以從 Thrift 和 Protobuf 中選擇其一。

註冊中心:

其一是提供了服務地址的存儲;

其二是當存儲內容發生變化時,可以將變更的內容推送給客戶端。

客戶端會與註冊中心建立連接,並且告訴註冊中心,它對哪一組服務感興趣;

服務端向註冊中心註冊服務後,註冊中心會將最新的服務註冊信息通知給客戶端;

客戶端拿到服務端的地址之後就可以向服務端發起調用請求了

註冊中心可以讓我們動態地,變更 RPC 服務的節點信息,對於動態擴縮容,故障快速恢復,以及服務的優雅關閉都有重要的意義;心跳機制是一種常見的探測服務狀態的方式,你在實際的項目中也可以考慮使用;我們需要對註冊中心中管理的節點提供一些保護策略,避免節點被過度摘除導致的服務不可用。

分佈式追蹤

服務的追蹤的需求主要有兩點,一點對代碼要無侵入,你可以使用切面編程的方式來解決;另一點是性能上要低損耗,我建議你採用靜態代理和日誌採樣的方式,來儘量減少追蹤日誌對於系統性能的影響;無論是單體系統還是服務化架構,無論是服務追蹤還是業務問題排查,你都需要在日誌中增加 requestId,這樣可以將你的日誌串起來,給你呈現一個完整的問題場景。如果 requestId 可以在客戶端上生成,在請求業務接口的時候傳遞給服務端,那麼就可以把客戶端的日誌體系也整合進來,對於問題的排查幫助更大。

採用 traceId + spanId 這兩個數據維度來記錄服務之間的調用關係(這裏 traceId 就是 requestId),也就是使用 traceId 串起單次請求,用 spanId 記錄每一次 RPC 調用。

切面編程的實現分爲兩類:一類是靜態代理,典型的代表是 AspectJ,它的特點是在編譯期做切面代碼注入;另一類是動態代理,典型的代表是 Spring AOP,它的特點是在運行期做切面代碼注入。

考慮如何減少日誌的數量。你可以考慮對請求做採樣,採樣的方式也簡單,比如你想採樣 10% 的日誌,那麼你可以只打印“requestId%10==0”的請求。

把日誌不打印到本地文件中,而是發送到消息隊列裏,再由消息處理程序寫入到集中存儲中,比如 Elasticsearch。

1. 在記錄打點日誌時,我們使用 traceId 將日誌串起來,這樣方便比較一次請求中的多個步驟的耗時情況;

2. 我們使用靜態代理的方式做切面編程,避免在業務代碼中,加入大量打印耗時的日誌的代碼,減少了對於代碼的侵入性,同時編譯期的代碼注入可以減少;

3. 我們增加了日誌採樣率,避免全量日誌的打印;

4. 最後爲了避免在排查問題時,需要到多臺服務器上搜索日誌,我們使用消息隊列,將日誌集中起來放在了 Elasticsearch 中。

服務治理:

1)負載均衡

客戶端負載均衡服務,也就是把負載均衡的服務內嵌在 RPC 客戶端中。

它一般和客戶端應用,部署在一個進程中,提供多種選擇節點的策略,最終爲客戶端應用提供一個最佳的,可用的服務端節點。這類服務一般會結合註冊中心來使用,註冊中心提供服務節點的完整列表,客戶端拿到列表之後使用負載均衡服務的策略選取一個合適的節點,然後將請求發到這個節點上

負載均衡策略有哪些負載均衡策略從大體上來看可以分爲兩類:

一類是靜態策略,也就是說負載均衡服務器在選擇服務節點時,不會參考後端服務的實際運行的狀態。

一類是動態策略,也就是說負載均衡服務器會依據後端服務的一些負載特性,來決定要選擇哪一個服務節點。

網站負載均衡服務的部署,是以 LVS 承接入口流量,在應用服務器之前,部署 Nginx 做細化的流量分發,和故障節點檢測。當然,如果你的網站的併發不高,也可以考慮不引入 LVS。

負載均衡的策略可以優先選擇動態策略,保證請求發送到性能最優的節點上;如果沒有合適的動態策略,那麼可以選擇輪詢的策略,讓請求平均分配到所有的服務節點上。

Nginx 可以引入 nginx_upstream_check_module,對後端服務做定期的存活檢測,後端的服務節點在重啓時,也要秉承着“先切流量後重啓”的原則,儘量減少節點重啓對於整體系統的影響。

2)API網關

API 網關(API Gateway)不是一個開源組件,而是一種架構模式,它是將一些服務共有的功能整合在一起,獨立部署爲單獨的一層,用來解決一些服務治理的問題。你可以把它看作系統的邊界,它可以對出入系統的流量做統一的管控。在我看來,API 網關可以分爲兩類:一類叫做入口網關,一類叫做出口網關

1.它提供客戶端一個統一的接入地址,API 網關可以將用戶的請求動態路由到不同的業務服務上,並且做一些必要的協議轉換工作。在你的系統中,你部署的微服務對外暴露的協議可能不同:有些提供的是 HTTP 服務;有些已經完成 RPC 改造,對外暴露 RPC 服務;有些遺留系統可能還暴露的是 Web Service 服務。API 網關可以對客戶端屏蔽這些服務的部署地址,以及協議的細節,給客戶端的調用帶來很大的便捷。

2.另一方面,在 API 網關中,我們可以植入一些服務治理的策略,比如服務的熔斷、降級,流量控制和分流等等(關於服務降級和流量控制的細節,我會在後面的課程中具體講解,在這裏,你只要知道它們可以在 API 網關中實現就可以了)。

3.再有,客戶端的認證和授權的實現,也可以放在 API 網關中。你要知道,不同類型的客戶端使用的認證方式是不同的。在我之前項目中,手機 APP 使用 Oauth 協議認證,HTML5 端和 Web 端使用 Cookie 認證,內部服務使用自研的 Token 認證方式。這些認證方式在 API 網關上,可以得到統一處理,應用服務不需要了解認證的細節。

4.另外,API 網關還可以做一些與黑白名單相關的事情,比如針對設備 ID、用戶 IP、用戶 ID 等維度的黑白名單。

5. 最後,在 API 網關中也可以做一些日誌記錄的事情,比如記錄 HTTP 請求的訪問日誌,分佈式追蹤系統時,提到的標記一次請求的 requestId,也可以在網關中來生成。

出口網關就沒有這麼豐富的功能和作用了。我們在系統開發中,會依賴很多外部的第三方系統,比如典型的例子:第三方賬戶登錄、使用第三方工具支付等等。我們可以在應用服務器和第三方系統之間,部署出口網關,在出口網關中,對調用外部的 API 做統一的認證、授權,審計以及訪問控制。

針對服務接口數據聚合的操作,一般有兩種解決思路:

再獨立出一組網關專門做服務聚合、超時控制方面的事情,我們一般把前一種網關叫做流量網關,

後一種可以叫做業務網關;抽取獨立的服務層,專門做接口聚合的操作。這樣服務層就大概分爲原子服務層和聚合服務層。

API 網關分爲入口網關和出口網關兩類,入口網關作用很多,可以隔離客戶端和微服務,從中提供協議轉換、安全策略、認證、限流、熔斷等功能。出口網關主要是爲調用第三方服務提供統一的出口,在其中可以對調用外部的 API 做統一的認證、授權,審計以及訪問控制;

API 網關的實現重點在於性能和擴展性,你可以使用多路 I/O 複用模型和線程池併發處理,來提升網關性能,使用責任鏈模式來提升網關的擴展性;

API 網關中的線程池,可以針對不同的接口或者服務做隔離和保護,這樣可以提升網關的可用性;

API 網關可以替代原本系統中的 Web 層,將 Web 層中的協議轉換、認證、限流等功能挪入到 API 網關中,將服務聚合的邏輯下沉到服務層。

3)多機房部署

1. 同城雙活

制定多機房部署的方案不是一蹴而就的,而是不斷迭代發展的。我在上面提到,同城機房之間的延時在 1ms~3ms 左右,對於跨機房調用的容忍度比較高,所以,這種同城雙活的方案複雜度會比較低。

2. 異地多活

不同機房的數據傳輸延遲,是造成多機房部署困難的主要原因,你需要知道,同城多機房的延遲一般在 1ms~3ms,異地機房的延遲在 50ms 以下,而跨國機房的延遲在 200ms 以下。

同城多機房方案可以允許有跨機房數據寫入的發生,但是數據的讀取,和服務的調用應該儘量保證在同一個機房中。

異地多活方案則應該避免跨機房同步的數據寫入和讀取,而是採取異步的方式,將數據從一個機房同步到另一個機房。

4)server mesh

Service Mesh 主要處理服務之間的通信,它的主要實現形式就是在應用程序同主機上部署一個代理程序,一般來講,我們將這個代理程序稱爲“Sidecar(邊車)”,服務之間的通信也從之前的,客戶端和服務端直連,變成了下面這種形式:

在這種形式下,RPC 客戶端將數據包先發送給,與自身同主機部署的 Sidecar,在 Sidecar 中經過服務發現、負載均衡、服務路由、流量控制之後,再將數據發往指定服務節點的 Sidecar,在服務節點的 Sidecar 中,經過記錄訪問日誌、記錄分佈式追蹤日誌、限流之後,再將數據發送給 RPC 服務端。

這種方式,可以把業務代碼和服務治理的策略隔離開,將服務治理策略下沉,讓它成爲獨立的基礎模塊。這樣一來,不僅可以實現跨語言,服務治理策略的複用,還能對這些 Sidecar 做統一的管理。

目前,業界提及最多的 Service Mesh 方案當屬istio, 它的玩法是這樣的:

輕量客戶端:

1.Service Mesh 分爲數據平面和控制平面。數據平面主要負責數據的傳輸;控制平面用來控制服務治理策略的植入。出於性能的考慮,一般會把服務治理策略植入到數據平面中,控制平面負責服務治理策略數據的下發。

2.Sidecar 的植入方式目前主要有兩種實現方式,一種是使用 iptables 實現流量的劫持;另一種是通過輕量級客戶端來實現流量轉發。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章