《Spring Cloud 微服務架構進階》讀書筆記

前頁
隨着 DevOps 和以 Docker 爲主的容器技術的發展,雲原生應用架構和微服 務變得流行起來。 雲原生包含的內容很多,如 DevOps、持續交付、微服務、敏捷等

第一章,微服務架構介紹

 架構的演進從單體到 SOA,再到微服務架構。 微服務架構是 SOA 思想的一種具體實踐。

微服務架構將早期的單體應用從數據存儲開始垂直拆分成多個不同的服務,每個服務都能獨立部署,獨立維護,獨立擴展.

服務與服務間通過諸如 RESTful API的方式互相調用。只有業務複雜到一定程度的時候才適合採用微服務架構。

1、微服務架構的出現

單體應用架構

單體應用架構,即傳統的Web應用程序中分層架構,按照調用順序,從上到下爲表示層、業務層、數據 訪問( DAO)層、 DB 層。

表示層負責用戶體驗;業務層負責業務邏輯;數據訪問層負責 DB 層的數據存取, 實現增刪改查的功能。 業務層定義了應用的業務邏輯,是整個應用的核心。

在單體應用中,所有這些模塊都集成在一起,或稱爲巨石型應用架構。 

單體應用的缺點:

  1. 可靠性,所有模塊在同一個進程中,任何一個模塊中的bug,都會影響進程
  2. 複雜性高,代碼模塊化不清晰,複雜
  3. 持續部署困難,更新組件需要整體部署。。 這會中斷那些可能與更改無關的後臺任務(例如 Java 應用 中的 Quartz 任務),同時可能引發其他問題。
  4. 擴展能力受限:單體架構只能進行一維擴展。 這個不懂??
  5. 阻礙技術創新: 單體應用往往使用統一的技術平臺或方案解決所有問題,,團隊的每 個成員都必須使用相同的開發語言和架構,想要引人新的框架或技術平臺非常困難。

SOA架構

面向服務的架構(SOA)是 Gartner 於 20 世紀 90 年代中期提出的。

SOA 的核心主體是服務,其目標是通過服務的流程化來實現業務的靈活性。 服務就像 一堆“元器件”,這些元器件通過封裝形成標準服務,它們有相同的接口和語義表達規則。

SOA 治理是將 SOA 的一堆元 器件進行有效組裝

完整的 SOA 架構由五大部分組成:

  • 基礎設施服務、 爲整個 SOA 組件和框架提供一個可靠的運行環境,以及服務組件容器, 它的核心組件是應用服務器等基礎軟件支撐設施
  • 企業服務總線 、 提供可靠消息傳輸、服務接入、協議轉換、數據格式轉換、基於內 容的路由等功能,屏蔽了服務的物理位置、協議和數據格式。
  • 關鍵服務組件、SOA 在各種業務服務組件的分類。
  • 開發工具、管理工具等提供完善的、可視化的服務開發和流程編排工具,包括服務 的設計、開發、 配置、部署、監控、重構等完整的 SOA 項目開發生命週期。

微服務架構

微服務的定義可以概括如下:

  • 微服務架構是一種使用 一系列粒度較小的服務來開發單個應用的方式;每個服務運行在自己的進程中
  • 服務間採用輕量級的方式進行通信(通常是 HTTPAPI);這些服務是基於業務邏輯和範圍,通過自動化部署的機制來獨立部署的
  • 並且服務的集中管理應該是最低限度的,即每個服務可以採用不同的編程語言編寫,使用不同的數據存儲技術。

組成

  1. 服務註冊與發現: 服務提供方將己方調用地址註冊到服務註冊中心,讓服務調用方能夠方便地找到自己;服務調用方從服務註冊中心找到自己需要調用的服務的地址。
  2. 負載均衡:服務提供方一般以多實例的形式提供服務,負載均衡功能能夠讓服務調用方連接到合適的服務節點。 並且,服務節點選擇的過程對服務調用方來說是透明的。
  3. 服務網關:服務網關是服務調用的唯一入口,可以在這個組件中實現用戶鑑權、動態路由、灰度發佈、 A/B 測試、負載限流等功能。
  4. 配置中心:將本地化的配置信息( Properties、 XML、 YAML 等形式)註冊到配置中心,實現程序包在開發、測試、生產環境中的無差別性,方便程序包的遷移。
  5. 集成框架:微服務組件都以職責單一的程序包對外提供服務,集成框架以配置的形式將所有微服務組件(特別是管理端組件)集成到統一的界面框架下,讓用戶能夠在統一的界面中使用系統。
  6. 調用鏈監控:記錄完成一次請求的先後銜接和調用關係,並將這種串行或並行的調 用關係展示出來。 在系統出錯時,可以方便地找到出錯點。
  7. 支撐平臺:系統微服務化後,各個業務模塊經過拆分變得更加細化,系統的部署、 運維、監控等都比單體應用架構更加複雜,這就需要將大部分的工作自動化。 現 在, Docker 等工具可以給微服務架構的部署帶來較多的便利,例如持續集成、藍綠 發佈、健康檢查、性能健康等等。 如果沒有合適的支撐平臺或工具,微服務架構就 無法發揮它最大的功效。

優點

  1. 解決了複雜性問題,單個服務變得很容易開發、理解和維護。
  2. 微服務架構模式使得團隊並行開發得以推進,可以隨便選擇語言
  3. 微服務架構模式中每個微服務獨立都是部署的。 理想情況下,開發者不需要協 調其他服務部署對本服務的影響。 這種改變可以加快部署速度

挑戰:

  1. 運維要求較高。
  2. 分佈式固有的複雜性。
  3. 接口調整成本高。
  4. 重複勞動。
  5. 可測試性的挑戰

2、微服務架構的流派

常見的微服務架構方案有四種,分別是 ZeroC IceGrid、 基於消息隊列 、 Docker Swarm 和 Spring Cloud。 下面分別介紹這四種方案。

ZeroC IceGrid

ZeroC IceGrid 是基於 RPC 框架 Ice 發展而來的一種微服務架構, Ice 不僅僅是一個 RPC 框架,它還爲網絡應用程序提供了一些補充服務。 Ice 是一個全面的 RPC 框架, 支持 C++、 C#、 Java、 JavaScript、 Python 等語言。 IceGrid 具有定位、部署和管理 Ice 服務器的功能, 具有良好的性能與分佈式能力。
Ice 的 DNS。 DNS 用於將域名信息映射到具體的 IP 地址 ,通過域名得到該域名對應的 IP 地址的過程叫做域名解析。

基於消息隊列

在微服務架構的定義中講到,各個微服務之間使用“輕量級”的通信機制。 所謂輕量級,是指通信協議與語言無關、 與平臺無關。 微服務之間的通信方式有兩種:同步和異步。同步方式有 RPC , REST 等;除了標準的基於同步通信方式的微服務架構外,
還有基於消息 隊列異步方式通信的微服務架構。

在基於消息隊列的微服務架構方式中,微服務之間採用發佈消息與監聽消息的方式來實現服務之間的交互

Docker Swarm

Swarm 項目是 Docker 公司發佈的三劍客中的一員,用來提供容器集羣服務 ,目的是更好地幫助用戶管理多個 Docker Engine,方便用戶使用。 通過把多個 Docker Engine 聚集 在一起,形成一個大的 Docker Engine,對外提供容器的集羣服務。 同時這個集羣對外提供 Swarm API,用戶可以像使用 Docker Engine 一樣使用 Docker 集羣。

Swarm 集羣中有兩種角色的節點:

Manager: 負責集羣的管理、集羣狀態的維持及將任務(Task)調度到工作節點上等。
Worker : 承載運行在 Swarm 集羣中的容器實例,每個節點主動彙報其上運行的任 務並維持同步狀態。

Spring Cloud

Spring Cloud 是一個基於 Spring Boot 實現的雲應用開發工具,是一系列框架的集合,當添加這些工具庫到應用後會增強應用的行爲。 Spring Boot 秉持約定優於配置的思想,因此可以利用這些組件基本的默認行爲來快速入門 ,並在需要的時候可以配置或擴展,以創建自定義解決方案。
Spring Cloud 利用 Spring Boot 的開發便利性,巧妙地簡化了分佈式系統基礎設施的開發,如服務發現註冊、配置中心、消息總線、負載均衡、斷路器、數據監控等,都可以基 於 Spring Boot 組件進行開發,做到一鍵啓動和部署。
以下爲 Spring Cloud 的核心功能 :

分佈式/版本化配置
服務註冊和發現
服務路由
服務和服務之間的調用
負載均衡
斷路器
分佈式消息傳遞

3、雲原生於微服務

CNCF(雲原生計算基金會)

雲原生 :

CNCF 憲章中給出了雲原生應用的三大特徵,概括如下: 

  • 容器化封裝:以容器爲基礎,提高整體開發水平,形成代碼和組件重用,簡化雲原生應用程序的維護。 在容器中運行應用程序和進程,並作爲應用程序部署的獨立單 元,實現高水平資源隔離。
  • 動態管理:通過集中式的編排調度系統來動態管理和調度。
  • 面向微服務:明確服務間的依賴,互相解藕。

可以理解爲雲原生應用即面向雲環境的軟件架構,

12原則 :

基準代碼。顯式聲明依賴關係。在環境中存儲配置。把後端服務當作附加資源。 嚴格分離構建、發佈和運行。無狀態進程。 通過端口綁定提供服務。 通過進程模型進行擴展。快速啓動和優雅終止。開發環境與線上環境等價。 日誌作爲事件流。管理進程。&&&& API 聲明管理、認證和授權、 監控與告警。

容器化:

Docker 是一個開源引擎,可以輕鬆地爲任何應用創建一個輕量級的、 可移植的、自給自足的容器。Docker 讓開發工程師可以將他們的應用和依賴封裝到一個可移植的容器中。 Docker 根本的想法是創建軟件程序可移植的輕量容器,讓其可以在任何安裝了 Docker 的機器上運 行, 而不用關心底層操作系統。

優勢:

  • 隔離應用依賴。
  • 創建應用鏡像並進行復制。
  • 創建容易分發的、即啓即用的應用。
  • 允許實例簡單、快速地擴展。
  • 測試應用並隨後銷燬它們。

DevOps

DevOps 是軟件開發人員( Dev)和 IT 運維技術人員( Ops)之間的合作,目標是自動執行軟件交付和基礎架構更改流程,使得構建 、測試、發佈軟件能夠更加地快捷和可靠。 它創造了一種文化和環境,可在其中快速、頻繁且更可靠地構建、 測試和發佈軟件。

微服務

微服務將單體業務系統分解爲多個可獨立部署的服務。 這個服務通常只關注某項業務, 或者最小可提供業務價值的“原子”服務單元。

優勢:

  1. 能夠將相關的變更週期解藕
  2. 擴展更多的部署組件本身可以加快部署。
  3. 可以加快採用新技術的步伐。
  4. 微服務提供獨立、高效的服務擴展


第二章、Spring Cloud 總覽

Spring Cloud 是一系列框架的有機集合,基於 Spring Boot 實現的雲應用開發工具,爲雲原生應用 開發中的服務發現與註冊、熔斷機制 、 服務路由 、分佈式配置中心 、消息總線、負載均衡 和鏈路監控等功能的實現提供了一種簡單的開發方式。

Spring Cloud 架構

版本說明

Springboot Cloud特點:

  1. 約定優於配置
  2. 適用於各種環境,部署在PC Server或各種雲環境。均可
  3. 隱藏了組件的複雜性,並提供聲明式,無xml配置方式。
  4. 開箱即用,快速啓動。
  5. 輕量級的組件,Spring Cloud 整合的組件大都輕量
  6. 組件豐富,功能齊全。
  7. 選型中立
  8. 靈活,組成部分解耦。

Spring Cloud 組成

  • 服務註冊與發現組件: Eureka、 Zookeeper 和 Consul 等。 本書將會重點講解 Eureka, Eureka 是一個 REST 風格的服務註冊與發現的基礎服務組件。
  • 服務調用組件: Hystrix、 Ribbon 和 OpenFeign ;
    • 其中 Hystrix 能夠使系統在出現依 賴服務失效的情況下,通過隔離系統依賴服務的方式,防止服務級聯失敗, 同時提供失敗回滾機制,使系統能夠更快地從異常中恢復;
    • Ribbon 用於提供客戶端的軟件 負載均衡算法,還提供了一系列完善的配置項如連接超時、 重試等;
    • OpenFeign 是 一個聲明式 RESTful 網絡請求客戶端,它使編寫 Web 服務客戶端變得更加方便和 快捷。
  • 路由和過濾組件:包括 Zuul 和 Spring Cloud Gateway。 Spring Cloud Gateway 提供 了一個構建在 Spring 生態之上的 API 網關, 其旨在提供一種簡單而有效的途徑來 發送 API, 併爲他們提供橫切關注點 ,如: 安全性、 監控指標和彈性。
  • 配置中心組件: Spring Cloud Config 實現了配置集中管理、動態刷新等配置中心的 功能。 配置通過 Git 或者簡單文件來存儲,支持加解密。
  • 消息組件: Spring Cloud Stream 和 Spring Cloud Bus。
    • Spring Cloud Stream 對於分佈式消息的各種需求進行了抽象,包括髮布訂閱 、分組消費和消息分區等功能,實現了微服務之間的異步通信。
    • Spring Cloud Bus 主要提供了服務間的事件通信(如 刷新配置)。
  • 安全控制組件:Spring Cloud Security於 0Auth2.0 開放網絡的安全標準, 提供了 微服務環境下的單點登錄、資源授權和令牌管理等功能。
  • 鏈路監控組件: Spring Cloud Sleuth 提供了全自動、可配置的數據埋點, 以收集微 服務調用鏈路上的性能數據, 並可以結合 Zipkin 進行數據存儲、統計和展示。

Spring Cloud 特性

Spring Cloud 提供了多種方式來促進雲原生開發風格。  Spring Cloud 提供了一系列組件,可 以在分佈式系統中直接使用,這些組件大大降低了分佈式系統的搭建和開發難度。

組件大多數由 Spring Boot 提供, Spring Cloud 在此基礎上添加了分佈式系統的相關特性。 Spring Cloud 依賴於 Spring Cloud Context 和 Spring Cloud Commons 兩個公共庫,  

  •  Spring Cloud Context 爲 Spring Cloud 應用程序上下文( ApplicationContext)提供了大 量的實用工具和特性服務
  •  Spring Cloud Common 是針對不同的 Spring Cloud 實現(如 Spring Cloud Netflix Eureka fl Spring Cloud Consul 兩種不同的服務註冊與發現實現)提供 上層抽象和公共類。 

Spring Cloud Context :應用上下文

 Bootstrap 上下文 :

除了應用上下文配置( application.yml 或者 application.properties)之外, Spring Cloud 應用程序提供與 Bootstrap 上下文配置相關的應用屬性。 Bootstrap 上下文對於主程序來說是一個父級上下文,它支持從外部資源中加載配置文件,和解密本地外部配置文件中 的屬性。 Bootstrap 上下文和應用上下文將共享一個環境( Environment),這是所有 Spring 應用程序的外部屬性來源。 一般來講, Bootstrap 上下文中的屬性優先級較高,所以它們不能被本地配置所覆蓋。 

Bootstrap 上下文使用與主程序不同的規則來加載外部配置。 bootstrap.yml 用於爲 Bootstrap 上下文加載外部配置,區別於應用上下文的 application.yml 或者 application. properties

spring: 
        application: 
            name: my-application 
        cloud: 
            config: 
                uri: ${CONFIG_SERVER :http://localhost:8080} 

2. 應用上下文層級

Spring 的上下文有一個特性 :子級上下文將從父級中繼承屬性源和配置文件。 

如果通過 bootstrap.yml 來配置 Bootstrap 上下文,且在設定好 父級上下文的情況下, bootstrap.yml 中的屬性會添加到子級的上下文。 它們的優先級低於 application.yml 和其他添加到子級中作爲創建 Spring Boot 應用的屬性源。 

基於屬性源的排序規則, Bootstrap 上下文中的屬性優先,但是需要注意這些屬性並不 包含任何來自 bootstrap.yml 的數據。 bootstrap.yml 中的屬性具備非常低的優先級,因此可 以作爲默認值。 

將父級上下文設置爲應用上下文來擴展上下文的層次結構。 Bootstrap 上下 文將會是最高級別上下文的父級。 每一個在層次結構中的上下文都有它自 己的 Bootstrap 屬 性源(可能爲空),來避免無意中將父級上下文中的屬性傳遞到它的後代中。 

3. 修改 Bootstrap 配置文件的位置 

bootstrap.yml 的位置可以通過在配置屬性中設置 spring.cloud.bootstrap.name (默認是 bootstrap)或者 spring.cloud.bootstrap. location 來修改。

4. 重載遠程屬性

通過 Bootstrap 上下文添加到應用程序的屬性源通常是遠程的,例如來自配置中心,通常本地的配置文件不能覆蓋這些遠程屬性源。一般來說過,啓動命令行參數的優先級高於遠程配置,可以通過設定啓動命令行參數的方式覆蓋遠程配置

應用程序的系統屬性或者配置文件覆蓋遠程屬性,那麼遠程屬性源必須設 置爲 spring.cloud.config.allowOverride=true (這個配置在本地設置不會生效)。 在遠程屬性 源中設定上述配置後,就可以通過更爲細粒度的設置來控制遠程屬性是否能被重載。

5. 自定義 Bootstrap 配置 

 

第四章,服務註冊與發現: Eureka 

通常來說服務註冊與發現包括兩部分一個是服務器端,另一個是客戶端。

Server 是 一個公共服務, 爲 Client 提供服務註冊和發現的功能,維護註冊到自身的 Client 的相關信 息,同時提供接口給 Client 獲取註冊表中其他服務的信息,使得動態變化的 Client 能夠進 行服務間的相互調用。

Client 將自己的服務信息通過一定的方式登記到 Server 上,並在正 常範圍內維護自己信息一致性,方便其他服務發現自己,同時可以通過 Server 獲取到自己 依賴的其他服務信息,完成服務調用。 

Spring Cloud Netflix Eureka 是 Spring Cloud 提供用於服務發現和註冊的基礎組件,是 搭建 Spring Cloud 微服務架構的前提之一。 Eureka 作爲一個開箱即用的基礎組件,屏蔽了 底層 Server 和 Client 交互的細節,使得開發者能夠將精力更多地放在業務邏輯上,加快微 服務架構的實施和項目的開發。

 基礎應用


在 Netflix 中 , Eureka 是一個 RESTful 風格的服務註冊與發現的基礎服務組件。 Eureka 由兩部分組成

  •  Eureka Server, 提供服務註冊和發現功能,即我們上面所說的服務 器端;
  • Eureka Client,它簡化了客戶端與服務端之間的交互。

Eureka Client 會定 時將自己的信息註冊到 Eureka Server 中,並從 Server 中發現其他服務。 Eureka Client 中內 置一個負載均衡器,用來進行基本的負載均衡。 

通常來講,一個 Eureka Server 也是一個 Eureka Client,它會嘗試註冊自己,所以需要至少一個註冊中心 的 URL 來定位對等點 peer。 如果不提供這樣一個註冊端點,註冊中心也能工作,但是會 在日誌中打印無法向 peer 註冊自己的信息。 在獨立( Standalone) Eureka Server 的模式下, Eureka Server 一般會關閉作爲客戶端註冊自己的行爲。 

Eureka Server 與 Eureka Client 之間的聯繫主要通過心跳的方式實現 心跳( Heartbeat) 即 Eureka Client 定時向 Eureka Server 彙報本服務實例當前的狀態,維護本服務實例在註冊 表中租約的有效性。 

啓動 Eureka Server 後,應用會有一個主頁面用來展示當前註冊表中的服務實例信息並 同時暴露一些基於 HTTP 協議的端點在/eureka 路徑下,這些端點將由 Eureka Client 用於注 冊自 身、獲取註冊表信息以及發送心跳等。

  1. 搭建 Eureka 服務註冊中心
  2. 搭建 Eureka 服務提供者
  3. 搭建 Eureka 服務調用者
  4. Eureka 服務註冊和發現
    與服務註冊中心交換信息 :DiscoveryC!ient 來源於 spring-cloud-client-discove可, 是 Spring Cloud 中定義用來服務發 現的公共接口,在 Spring Cloud 的各類服務發現組件中(如 Netflix Eureka 或 Consul)都有相應的實現。 它提供從服務註冊中心根據 serviceld 獲取到對應服務實例信息的能力。 當一個 服務實例擁有 DiscoveryClient 的具體實現時,就可以從服務註冊中心中發現其他的服務實例。

服務發現原理
 


  1.  

 首先對 AWS 上的區域(Regin )和可用區(Availability Zone )進行簡單的介紹。

  1.  區域: AWS 根據地理位置把某個地區的基礎設施服務集合稱爲一個區域,區域之 間相對獨立。 在架構圖上, us-east- 1c、 us-east-1 d 、 us-east-1巳表示 AWS 中的三個 設施服務區域,這些區域中分別部署了一個 Eureka 集羣。
  2. 可用區 : AWS 的每個區域都是由多個可用區組成的,而一個可用區一般都是由多 個數據中心(簡單理解成一個原子服務設施)組成的。 可用區與可用區之間是相互 獨立的,有獨立的網絡和供電等,保證了應用程序的高可用性。 在上述的架構圖 中, 一個可用區中可能部署了多個 Eureka, 一個區域中有多個可用區,這些 Eureka 共同組成了一個 Eureka 集羣

組件與行爲 :

  • Application Service:是一個 Eureka Client,扮演服務提供者的角色,提供業務服務, 向 Eureka Server 註冊和更新自己的信息,同時能從 Eureka Server 註冊表中獲取到其他服務的信息。
  • Eureka Server :扮演服務註冊中心的角色,提供服務註冊和發現的功能。 每個 Eureka Cient 向 Eureka Server 註冊自己的信息,也可以通過 Eureka Server 獲取到其 他服務的信息達到發現和調用其他服務的目的。
  • Application Client :是一個 Eureka Client,扮演了服務消費者的角色,通過 Eureka Server 獲取註冊到其上其他服務的信息,從而根據信息找到所需的服務發起遠程調用。
  • Replicate: Eureka Server 之間註冊表信息的同步複製,使 Eureka Server 集羣中不同 註冊表中服務實例信息保持一致。
  • Make Remote Call :服務之間的遠程調用。
  • Register:註冊服務實例, Client 端向 Server 端註冊自身的元數據以供服務發現。
  • Renew :續約,通過發送心跳到 Server 以維持和更新註冊表中服務實例元數據的有 效性。 當在一定時長內, Server 沒有收到 Client 的心跳信息,將默認服務下線,會 把服務實例的信息從註冊表中刪除。
  • Cancel :服務下線, Client 在關閉時主動向 Server 註銷服務實例元數據,這時 Client 的服務實例數據將從 Server 的註冊表中刪除。
  • Get Registry : 獲取註冊表, Client 向 Server 請求註冊表信息,用於服務發現,從而 發起服務間遠程調用。
     

Eureka Client 源碼解析:

DiscoveryClient 是 Spring Cloud 中用來進行服務發現的頂級接 口,在 Netflix Eureka 或者 Consul 中都有相應的具體實現類 , 該接口提供的方法如下:
 

//DiscoveryClient. java 
public interface DiscoveryClient { 
//獲取實現類的描述 
String description() ; 
//通過服務工d獲取服務實例的信息 
List<Serviceinstance> getInstatces(String serviceid) ; //獲取所有的服務實例Id List<String> getServices() ; 

EurekaClient 來 自於 com.netflix.discovery 包中,其默認實現爲 com.netflix.discovery. DiscoveryClient,屬於 eureka-client 的源代碼, 它提供了 Eureka Client 註冊到 Server 上、續 租、 下線以及獲取 Server 中註冊表信息等諸多 關鍵功能。 Spring Cloud 通過組合方式調用了Eureka 中的服務發現方法。

服務發現客戶端

爲了對 Eureka Client 的執行原理進行講解, 首先需要對服務發現客戶端 com.netflix. discover.DiscoveryClient 職能以及相關類進行講解,它負責了與 Eureka Seever 交互的關鍵邏輯。

 DiscoveryClient 職責

DiscoveryClient 是 Eureka Client 的核心類,包括與 Eureka Server 交互的關鍵邏輯,具 備了以下職能:

  1. 註冊服務實例到 Eureka Server 中;
  2. 發送心跳更新與 Eureka Server 的租約;
  3. 在服務關閉時從 Eureka Server 中取消租約,服務下線;
  4. 查詢在 Eureka Server 中註冊的服務實例列表。

2. DiscoveryClient 類結構:

DiscoveryClient 繼承了 LookupService 接口, LookupService 作用是發現活躍的服務實例

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.netflix.discovery.shared;

import com.netflix.appinfo.InstanceInfo;
import java.util.List;

public interface LookupService<T> {

    Application getApplication(String var1);

    Applications getApplications();

    List<InstanceInfo> getInstancesById(String var1);

    InstanceInfo getNextServerFromEureka(String var1, boolean var2);
}

Application 持有服務實例信息列表 ,它可以理解成同一個服務的集羣信息,這些服務 實例都掛在同一個服務名 appName 下。 InstanceInfo 代表一個服務實例信息。 Application 部分代碼如下:

public class Applications {
    private static final String STATUS_DELIMITER = "_";
    private String appsHashCode;
    private Long versionDelta;
    @XStreamImplicit
    private final AbstractQueue<Application> applications;
    private final Map<String, Application> appNameApplicationMap;
    private final Map<String, Applications.VipIndexSupport> virtualHostNameAppMap;
    private final Map<String, Applications.VipIndexSupport> secureVirtualHostNameAppMap;

爲了保證原子性操作 Application 中對 Instancelnfo 的操作都是同步操作。

Applications 是註冊表中所有服務實例信息的集合, 裏面的操作大多也是同步操作EurekaC!ient 繼承了 LookupService 接口,爲 DiscoveryClient 提供了一個上層接 口,

EurekaCient 在 LookupService 的基礎上擴充了更多的接 口 ,提供了更豐富的獲取服務 實例的方式 , 主要有:

  1. 提供了多種方式獲取 Instancelnfo, 例如根據區域、 Eureka Server 地址等獲取。
  2. 提供了本地客戶端(所處的區域、 可用區等) 的數據,這部分與 AWS 密切相關。 
  3. 提供了爲客戶端註冊和獲取健康檢查處理器的能力。 

服務拉取客戶端

一般來講,在 Eureka 客戶端,除了第一次拉取註冊表信息,之後的信息拉取都會嘗試 只進行增量拉取第一次拉取註冊表信息爲全量拉取), 下面將分別介紹拉取註冊表信息的 兩種實現,全量拉取註冊表信息 DiscoveryCli en t#getAn d S toreFu I !Regis try 和增量式拉取注 冊表信息 DiscoveryC!ient#getAndUpdateDelta。

  • 1. 全量拉取註冊表信息 :一般只有在第一次拉取的時候,纔會進行註冊表信息的全量拉取,主要在 DiscoveryCIient# getAndStoreFul!Registry 方法中進行
  • 2. 增量式拉取註冊表信息: 增量式的拉取方式, 一般發生在第一次拉取註冊表信息之後,拉取的信息定義 爲從某一段時間之後發生的所有變更信息,通常來講是 3 分鐘之內註冊表的信息變化。 在獲取到更新的 delta 後,會根據 delta 中的增量更新對本地的數據進行更新。 與 getAndStoreFullRegistry 方法一樣,也通過 fetchRegistryGen巳ration 對更新的版本進行控 制。 增量式拉取是爲了維護 Eureka Client 本地的註冊表信息與 Eureka Server 註冊表信息 的一致性,防止數據過久而失效,採用增量式拉取的方式減少了拉取註冊表信息的通信 量。 Client 中有一個註冊表緩存刷新定時器專門負責維護兩者之間信息的同步性。 但是當增 量式拉取出現意外時,定時器將執行全量拉取以更新本地緩存的註冊表信息。

服務註冊:

在拉取完 Eureka Server 中的 註冊表信息並將其緩存在本地後 , Eureka Client 將向 Eureka Server 註冊自身服務實例元數據,主要邏輯位於 Discovery#register 方法中。 

Eureka Client 會將自身服務實例元數據(封裝在 Instancelnfo 中)發送到 Eureka Server 中請求服務註冊,當 Eureka Server 返回 204 狀態碼時,說明服務註冊成功。 

初始化定時任務:

Eureka Client 通過定時發送心跳的方式與 Eureka Server 進行通信,維持自己在 Server 註冊表上的租約。 同時 Eureka Server 註冊表中 的服務實例信息是動態變化的,

爲了保持 Eureka Client 與 Eureka Server 的註冊表信息的一致性, Eureka Client 需要定時向 Eureka Server 拉取註冊表信息並更新本地緩存。

爲了監控 Eureka Client 應用信息和狀態的變化, Eureka Client設置了一個按需註冊定時器定時檢查應用信息或者狀態的變化, 並在發生變化時向 Eureka Server 重新註冊,避免註冊表中的 本服務實例信息不可用。

DiscoveryClient#initScheduledTasks 方法中初始化了三個定時器任務,

  • 一個用於向 Eureka Server 拉取註冊表信息刷新本地緩存;
  • 一個用於向 Eureka Server 發送心跳;
  • 一個用 於進行按需註冊的操作。 

1. 緩存刷新定時任務與發送心跳定時任務。

2. 按需註冊定時任務:按需註冊定時任務的作用是當 Eureka Client 中的 Instancelnfo 或者 status 發生變化時, 重新向 Eureka Server 發起註冊請求,更新註冊表中的服務實例信息,保證 Eureka Server 注 冊表中服務實例信息有效和可用。 

服務下線

應用服務在關閉的時候, Eureka Client 會主動向 Eureka Server 註銷自身在註冊表中的信息。

 

第六章,斷路器:Hystrix

Hystrix 是 Netflix 的一個開源項目,它能夠在依賴服務失效的情況下,通過隔離系統依 賴服務的方式,防止服務級聯失敗;同時 Hystrix 提供失敗回滾機制,使系統能夠更快地從 異常中恢復。 

基礎應用

spring-cloud-netflix-hystrix 對 Hystrix 進行封裝和適配,使 Hystrix 能夠更好地運行於 Spring Cloud 環境中,爲微服務間的調用提供強有力的容錯機制。 

Hystrix 具有如下的功能:

  • 在通過第三方客戶端訪問(通常是通過網絡)依賴服務出現高延遲或者失敗時,爲 系統提供保護和控制。
  • 在複雜的分佈式系統中防止級聯失敗(服務雪崩效應)
  • 快速失敗 (Fail fast) 同時能快速恢復。
  • 提供失敗回滾 (Fallback) 和優雅的服務降級機制。
  • 提供近實時的監控、 報警和運維控制手段。

服務雪崩效應是一種因服務提供者的不 可用導致服務調用者的不可用,並將不可用 逐漸放大的過程

其中, A 作爲基礎的服務提供者,爲 B 和 C 提供服務, D、 E、 F 是 B 和 C 服務的調用 者,當 A 不可用時,將引起 B 和 C 的不可用,並將這種不可用放大到 D、 E、 F,從而可能 導致整個系統的不可用,服務雪崩的產生可能導致分佈式系統的癱瘓。

服務雪崩效應的產生一般有三個流程;

  1. 首先是服務提供者不可用,
  2. 然後重試會導致網 絡流量加大,
  3. 最後導致服務調用者不可用。 

 原因

  • 服務器的宕機或者網絡戰障;
  • 程序存在的缺陷;
  • 大量的請求導致服務提供者的資源受限無法及時響 應;
  • 緩存擊穿造成服務提供者超負荷運行等等 

服務調用者因爲服務提供者不可用導致了自身的崩潰。 當服務調用者使用同 步調用的時候,大量的等待線程將會耗盡線程池中的資源, 最終導致服務調用者的若機, 無法響應用戶的請求,服務雪崩效應就此發生了。
 

斷路器

在分佈式系統中,不同服務之間的調用非常常見,當服務提供者不可用時就很有可能 發生服務雪崩效應,導致整個系統的不可用。 所以爲了預防這種情況的發生,可以使用斷路器模式進行預防(類比電路中的斷路器,在電路過大的時候自動斷開,防止電線過熱損害 整條電路)。 

斷路器將遠程方法調用包裝到一個斷路器對象中,用於監控方法調用過程的失敗 一 旦該方法調用發生的失敗次數在一段時間內達到一定的閥值,那麼這個斷路器將會跳閘, 

在接下來時間裏再次調用該方法將會被斷路器直接返回異常,而不再發生該方法的真實調 用。 這樣就避免了服務調用者在服務提供者不可用時發送請求,從而減少線程池中資源的 消耗,保護了服務調用者。

雖然斷路器在打開的時候避免了被保護方法的無效調用,但是當情況 恢復正常時,需要外部干預來重置斷路器,使得方法調用可以重新發生。 所以合理的斷路器應該具備一定的開關轉化邏輯,它需要一個機制來控制它的重新閉合

過重置時間來決定斷路器的重新閉合

  • 關閉狀態:斷路器處於關閉狀態,統計調用失敗次數,在一段時間內達到達定的閥值後斷路器打開。
  • 打開狀態:斷路器處於打開狀態,對方法調用直接返回失敗錯誤,不發生真正的方 法調用。 設置了一個重置時·間,在重置時間結束後,斷路器來到半開狀態。
  • 半開狀態: 斷路器處於半開狀態,此時允許進行方法調用,當調用都成功了(或者 成功到達一定的比例),關閉斷路器,否則認爲服務沒有恢復,重新打開斷路器。 

斷路器的打開能保證服務調用者在調用異常服務時,快速返回結果,避免大量的同步等待,減少服務調用者的資源消耗。 並且斷路器能在打開一段時間後繼續偵測請求執行結 果,判斷斷路器是否能關閉,恢復服務的正常調用

服務降級操作 

  • 斷路器:隔斷服務調用者和異常服務提供者防止服務雪崩的現象,提供了一種保護措施
  • 服務降級:是爲了在整體資源不夠的時候,適當放棄部分服務,將主要的資源投放到核心服務中,待渡過難關之後,再重啓已關閉的服務,保證了系統核心服務的穩定。
     

 

資源隔離

在 Hystrix 中,也採用了艙壁模式,將系統中的服務中供者隔離起來, 一個服務提供者延遲升高或者失敗,並不會導致整個系統的失敗,同時也 能夠控制調用這些服務的併發度。(在貨船中,爲了防止漏水和火災的擴散,一般會將貨倉進行分割)

 在 Hystrix 中,當服務間調用發生問題時,它將採用備用的 Fallback 方法代替主方法 執行並返回結果對失敗服務進行了服務降級。 當調用服務失敗次數在一段時間內超過了 斷路器的閥值時,斷路器將打開,不再進行真正的方法調用,而是快速失敗,直接執行 Fall back 邏輯,服務降級,減少服務調用者的資源消耗,保護服務調用者中的線程資源

1,線程與線程池 :Hystrix 通過將調用服務線程與服務訪問的執行線程分隔開來,調用線程能夠空出來去 做其他的工作而不至於因爲服務調用的執行阻塞過長時間。 在 Hystrix 中,將使用獨立的線程池對應每一個服務提供者,用於隔離和限制這些服務。

信號量 : Hystrix 還可以通過信號量(計數器)來限制單個服務提供者的併發量。 如果通過信號量來控制系統負載,將不再允許設置超時控制和異步化調用,這就表示在服 務提供者出現高延遲時,其調用線程將會被阻塞,直至服務提供者的網絡請求超時。 如果 對服務提供者的穩定性有足夠的信心,可以通過信號量來控制系統的負載。

 Hystrix 實現思路

  1. 它將所有的遠程調用邏輯封裝到 HystrixCommand 或者 HystrixObservableCommand 對象中,這些遠程調用將會在獨立的線程中執行(資源隔離),這裏使用了設計模式 中的命令模式。
  2. Hystrix 對訪問耗時超過設置閥值的請求採用自動超時的策略。 該策略對所有的命 令都有效(如果資源隔離的方式爲信號量,該特性將失效),超時的閥值可以通過命 令配置進行自定義。
  3. 爲每一個服務提供者維護一個線程池(或者信號量),當線程池被佔滿時,對於該 服務提供者的請求將會被直接拒絕(快速失敗)而不是排隊等待,減少系統的資源 等待。
  4.  針對請求服務提供者劃分出成功、失效、超時和線程池被佔滿等四種可能出現的情況。
  5. 斷路器機制將在請求服務提供者失敗次數超過一定閥值後手動或者自動切斷服務一 段時間。
  6. 當請求服務提供者出現服務拒絕、 超時和短路(多個服務提供者依次順序請求,前 面的服務提供者請求失敗,後面的請求將不會發出) 等情況時,執行其 Fallback 方 法, 服務降級。
  7. 提供接近實時的監控和配置變更服務。

源碼解析

簡單的流程如下 :

  • 1 )構建 HystrixCommand 或者 HystrixObservableCommand 對象。
  • 2 )執行命令。
  • 3 )檢查是否有相同命令執行的緩存。
  • 4 )檢查斷路器是否打開。
  • 5 )檢查線程池或者信號量是否被消耗完。
  • 6 )調用 HystrixObservableCommand#construct 或 HystrixCommand#run 執行被封裝的 遠程調用邏輯。
  • 7 )計算鏈路的健康情況。
  • 8 )在命令執行失敗時獲取 Fallback 邏輯。
  • 9 )返回成功的 Observable。 

 封裝 HystrixCommand

 @HystrixCommand 註解 

在基礎應用中我們使用@HystrixCommand 註解來包裝需要保護的遠程調用方法。 首先 查看該註解的相關屬性

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {
//默認爲被註解方法的運行時類名 
    String groupKey() default "";
// hystrix的命令鍵,用於區分不同的註解方法 ,默認爲註解方法的名稱 
    String commandKey() default "";

    String threadPoolKey() default "";
//指定Fallback方法名, Fallback方法也可以被HystrixCommand註解 
    String fallbackMethod() default "";
//自定義命令的相關配置 
    HystrixProperty[] commandProperties() default {};
//自定義線程池的相關配置 
    HystrixProperty[] threadPoolProperties() default {};
//定義忽略哪些異常 
    Class<? extends Throwable>[] ignoreExceptions() default {};

    ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
//默認的 fallback 
    HystrixException[] raiseHystrixExceptions() default {};

    String defaultFallback() default "";

@HystrixCommand 的配置,僅需要關注 fallbackMethod 方法,當然如果 對命令和線程池有特定需要,可以進行額外的配置。

@Hystrix Collapser 註解用於請求合併操作,但是需 要與@HystrixCommand 結合使用, 批量操作的方法必須被@HystrixCommand 註解

進階應用

異步與異步回調執行命令:Hystrix 除了同步執行命令 ,還可以異步以及異步回調執行命令。 異步執行命令需要定 義函數的返回方式爲 Future。

繼承 HystrixCommand除了通過註解的方式聲 明 Hystrix 包裝函數,還可 以通過繼承 HystrixCommand 以及 HystrixObservableCommand 抽象類接口來包裝需要保護的遠程調用函數。

  • run 方法中是需要進行包裝的遠程調用函數 , 是必須要實現的抽象方法,
  • getFallback 方法是該命令執行失敗後的失敗回滾方法,屬於可選實現。 
  • 在構造 HystrixCommand 時-至少要爲它指定一個 HystrixCommandGroupKey,在通過註解的方式生 成 HystrixCommand 時,該值一般是註解方法所在類的運行時類名 。
  • 在使用 CustomHystrixCommand 時,會發現無法在#run 方法中傳遞參數,所以需要在 構造器中攜帶#run 方法的相關參數。
  • 創建一個 HystrixCommand , 並調用它的 execute 方法, 即可按照 Hystrix 的邏 輯執行命令。 如果想要以異步方式執行命令,可以調用它的 queue 方法。
  • 一個HystrixCommand 只能執行一次( execute 方法或者 queue

繼承 HystrixObservableCommand :以繼承 HystrixObservableCommand 來構建以異步回調執 行命令的 Commando

  1. 和 CustomHystrixCommand 一樣,每個 CustomHystrixObservableCommand 只能執行一 次(observe 方法或者 toObservable 方法),所以每次使用都要創建一個新的命令。 
package com.liruilong.hystrix;

import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixObservableCommand;
import org.apache.logging.log4j.CloseableThreadContext;
import rx.Observable;
import sun.security.jca.GetInstance;

/**
 * @Description :
 * @Author: Liruilong
 * @Date: 2020/3/21 22:58
 */
public class CustorrHystrixObservableCommand extends HystrixObservableCommand<CloseableThreadContext.Instance> {
    protected CustorrHystrixObservableCommand(HystrixCommandGroupKey group) {
        super(group);
    }

    protected CustorrHystrixObservableCommand(Setter setter) {
        super(setter);
    }

    @Override
    protected Observable<CloseableThreadContext.Instance> construct() {
        return null;
    }

    @Override
    protected String getFallbackMethodName() {
        return super.getFallbackMethodName();
    }
}

請求合併:

Hystrix 多個請求被合併爲一個請求進行一次性處理,可以有效減少網絡通信和線程池資源。 請求合併之後 一個請求原本可能在 6 毫秒之內能夠結 束, 現在必須等待請求合併週期後( I 0 毫秒)才能發送請求,增加了請求的時間 ( 16 毫 秒)。 但是請求合併在處理高併發和高延遲命令上效果極佳。

它提供兩種方式進行請求合併 :

  1. request-scoped 收集一個 HystrixRequestContext 中的請求集合成一個批次;
  2. globally-scoped 將多個 HystrixRequestContext 中的請求集合成一個 批次,這需要應用的下游依賴能夠支持在一個命令調用中處理多個 HystrixRequestContext
  • 1.通過註解方式進行請求合:單個請求需要使用@HyStrixCollapser註解修飾,並指明batchMethod方法, 由於請求合併中不能同步等待結果,所以單個請求返回的 結果爲 Future,即需要異步等待結果。
  • 2. 繼承 HystrixCol lapser 

Hystrix 爲 Spring Cloud 中微服務間的相互調用提供了強大的容錯保護。 它通過將服務 調用者和服務提供者隔離的方式,在服務提供者失效的情況下,保護服務調用者的線程資 源,保證系統的整體穩定性,防止服務雪崩效應的發生。

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