spring cloud基礎教程

Srping cloud

[toc]html


基礎知識

在進行Spring Cloud 的具體內容介紹以前, 咱們先經過本章學習一些關於微服務架構以及Spring Cloud 的基礎知識。對Spring Cloud 可以解決的具體問題有一個大體的瞭解,以幫助咱們更好地理解後續章節對各個組件的介紹。前端

什麼是微服務架構

「微服務」一詞源於Martin Fowler 的名爲Microservices 的博文, 能夠在他的官方博客上找到:http://martinfowler.com/articles/microservices.html。java

簡單地說, 微服務是系統架構上的一種設計風格,它的主旨是將一個本來獨立的系統拆分紅多個小型服務,這些小型服務都在各自獨立的進程中運行,服務之間經過基於HTTP的RESTful API進行通訊協做。被拆分紅的每個小型服務都圍繞着系統中的某一項或一些耦合度較高的業務功能進行構建,而且每一個服務都維護着自身的數據存儲、業務開發、自動化測試案例以及獨立部署機制。由千有了輕量級的通訊協做基礎, 因此這些微服務能夠使用不一樣的語言來編寫。git

與單體系統的區別

在以往傳統的企業系統架構中,咱們針對一個複雜的業務需求一般使用對象或業務類型來構建一個單體項目。在項目中咱們一般將需求分爲三個主要部分: 數據庫、服務端處理、前端展示。在業務發展初期,因爲全部的業務邏輯在一個應用中, 開發、測試、部署都還比較容易且方便。可是,隨着企業的發展, 系統爲了應對不一樣的業務需求會不斷爲該單體項目增長不一樣的業務模塊; 同時隨着移動端設備的進步,前端展示模塊已經不只僅侷限於Web的形式,這對千系統後端向前端的支持須要更多的接口模塊。單體應用由千面對的業務需求更爲寬泛,不斷擴大的需求會使得單體應用變得愈來愈腕腫。單體應用的問題就逐漸凸顯出來, 因爲單體系統部署在一個進程內,每每咱們修改了一個很小的功能, 爲了部署上線會影響其餘功能的運行。而且, 單體應用中的這些功能模塊的使用場景、併發量、消耗的資源類型都各有不一樣, 對於資源的利用又互相影響, 這樣使得咱們對各個業務模塊的系統容量很難給出較爲準確的評估。因此, 單體系統在初期雖然能夠很是方便地進行開發和使用, 可是隨着系統的發展, 維護成本會變得愈來愈大, 且難以控制。web

爲了解決單體系統變得龐大脯腫以後產生的難以維護的問題, 微服務架構誕生了並被你們所關注。咱們將系統中的不一樣功能模塊拆分紅多個不一樣的服務,這些服務都可以獨立部署和擴展。因爲每一個服務都運行在本身的進程內, 在部署上有穩固的邊界, 這樣每一個服務的更新都不會影響其餘服務的運行。同時, 由千是獨立部署的, 咱們能夠更準確地爲每一個服務評估性能容量, 經過配合服務間的協做流程也能夠更容易地發現系統的瓶頸位置,以及給出較爲準確的系統級性能容量評估。算法

如何實施微服務

在實施微服務以前, 咱們必需要知道, 微服務雖然有很是多吸引人的優勢, 可是也由於服務的拆分引起了諸多本來在單體應用中沒有的問題。spring

  • **運維的新挑戰:**在微服務架構中, 運維人員須要維護的進程數量會大大增長。有條不紊地將這些進程編排和組織起來不是一件容易的事, 傳統的運維人員每每很難適應這樣的改變。咱們須要運維人員有更多的技能來應對這樣的挑戰,運維過程須要更多的自動化, 這就要求運維人員具有必定的開發能力來編排運維過程並讓它們能自動運行起來。
  • **接口的一致性:**雖然咱們拆分了服務, 可是業務邏輯上的依賴並不會消除, 只是從單體應用中的代碼依賴變爲了服務間的通訊依賴。而當咱們對原有接口進行了一些修改, 那麼交互方也須要協調這樣的改變來進行發佈, 以保證接口的正確調用。咱們須要更完善的接口和版本管理, 或是嚴格地遵循開閉原則。
  • **分佈式的複雜性:**因爲拆分後的各個微服務都是獨立部署並運行在各自的進程內,它們只能經過通訊來進行協做, 因此分佈式環境的問題都將是微服務架構系統設計時須要考慮的重要因素,好比網絡延遲、分佈式事務、異步消息等。

儘管微服務架構有不少缺點和問題, 可是其實現的敏捷開發和自動化部署等優勢依然被廣大優秀架構師和開發者所青眯,因此解決這些問題就是這幾年諸多架構大師努力的目標。數據庫

微服務架構的九大特性

在架構師對於一個大型系統架構的設計與實施的過程當中, 面對環境、資源、團隊等各類因素的影響, 幾乎不會出現徹底相同的架構設計。對於微服務架構而言更是如此, 因爲並無一個標準或正式的定義, 每位架構師都根據自身理解與實際狀況來進行設計, 並在發展的過程當中不斷演化與完善。通過多年的發展, Martin Fowler 在Microservices 一文中,提煉出了微服務架構的九大特性, 用於指導你們設計架構。apache

服務組件化

組件, 是一個能夠獨立更換和升級的單元。就像PC 中的CPU、內存、顯卡、硬盤同樣, 獨立且能夠更換升級而不影響其餘單元。編程

在微服務架構中, 須要咱們對服務進行組件化分解。服務, 是一種進程外的組件, 它經過HTTP 等通訊協議進行協做,而不是像傳統組件那樣以嵌入的方式協同工做。每個服務都獨立開發、部署, 能夠有效避免一個服務的修改引發整個系統的從新部署。

打一個不恰當的比喻, 若是咱們的PC 組件以服務的方式構建, 那麼只維護主板和一些必要外設以後, 計算能力經過一組外部服務實現, 咱們只須要告訴PC 從哪一個地址來得到計算能力, 經過服務定義的計算接口來實現咱們使用過程當中的計算需求, 從而實現CPU組件的服務化。這樣本來複雜的PC 服務獲得了輕量化的實現, 咱們甚至只須要更換服務 地址就能升級PC 的計算能力。

按業務組織團隊

當決定如何劃分微服務時, 一般也意味着咱們要開始對團隊進行從新規劃與組織。按以往的方式, 咱們每每會從技術的層面將團隊劃分爲多個,好比DBA團隊、運維團隊、後端團隊、前端團隊、設計師團隊等。若咱們繼續按這種方式組織團隊來實施微服務架構開發, 當有一個服務出現問題須要更改時, 多是一個很是簡單的變更, 好比對人物描述增長一個字段, 這須要從數據存儲開始考慮一直到設計和前端, 雖然你們的修改都很是小,但這會引發跨團隊的時間耗費和預算審批。

在實施微服務架構時, 須要採用不一樣的團隊分割方法。因爲每個微服務都是針對特定業務的寬棧或是全棧實現, 既要負責數據的持久化存儲, 又要負責用戶的接口定義等各類跨專業領域的職能。所以,面對大型項目的時候, 對於微服務團隊的拆分更加建議按業務線的方式進行拆分, 一方面能夠有效減小服務內部修改所產生的內耗; 另外一方面, 團隊邊界能夠變得更爲清晰。

作「 產品」 的態度

在實施微服務架構的團隊中, 每一個小團隊都應該以作產品的方式, 對其產品的整個生命週期負責。而不是以項目的模式,以完成開發與交付並將成果交接給維護者爲最終目標。

開發團隊經過了解服務在具體生產環境中的狀況, 能夠增長他們對具體業務的理解,好比, 不少時候, 一些業務中發生的特殊或異常狀況, 極可能產品經理都並不知曉, 但細心的開發者很容易經過生產環境發現這些特殊的潛在問題或需求。

因此, 咱們須要用作「產品」的態度來對待每個微服務, 持續關注服務的運做狀況,並不斷分析以幫助用戶來改善業務功能。

智能端點與啞管道

在單體應用中,組件間直接經過函數調用的方式進行交互協做。而在微服務架構中,因爲服務不在一個進程中, 組件間的通訊模式發生了改變, 若僅僅將本來在進程內的方法調用改爲RPC 方式的調用,會致使微服務之間產生煩瑣的通訊, 使得系統表現更爲糟糕,因此, 咱們須要更粗粒度的通訊協議。

在微服務架構中, 一般會使用如下兩種服務調用方式:

  • 第一種, 使用HTTP 的RESTful API 或輕量級的消息發送協議, 實現信息傳遞與服務調用的觸發。
  • 第二種, 經過在輕量級消息總線上傳遞消息, 相似RabbitMQ 等一些提供可靠異步交換的中間件。

在極度強調性能的狀況下, 有些團隊會使用二進制的消息發送協議, 例如protobuf。即便是這樣, 這些系統仍然會呈現出「 智能瑞點和啞管道」 的特色, 這是爲了在易讀性與高效性之間取得平衡。固然大多數Web 應用或企業系統並不須要在這二者間作出選擇, 可以荻得易讀性已是一個極大的勝利了。 <p align="right">一Martin Fowler</p>

去中心化治理

當咱們採用集中化的架構治理方案時, 一般在技術平臺上都會制定統一的標準, 可是每一種技術平臺都有其短板, 這會致使在碰到短板時, 不得不花費大力氣去解決, 而且可能由於其底層緣由解決得不是很好, 最終成爲系統的瓶頸。

在實施微服務架構時, 經過採用輕量級的契約定義接口, 使得咱們對於服務自己的具體技術平臺再也不那麼敏感,這樣整個微服務架構系統中的各個組件就能針對其不一樣的業務特色選擇不一樣的技術平臺, 終千不會出現殺雞用牛刀或是殺牛用指甲鉗的尷尬處境了。

不是每個問題都是釘子, 不是每個解決方案都是錘子。

去中心化管理數據

咱們在實施微服務架構時, 都但願讓每個服務來管理其自有的數據庫, 這就是數據管理的去中心化。

在去中心化過程當中, 咱們除了將原數據庫中的存儲內容拆分到新的同平臺的其餘數據庫實例中以外(如把本來存儲在MySQL 中的表拆分後,存儲到多個不一樣的MySQL 實例中),也能夠將一些具備特殊結構或業務特性的數據存儲到一些其餘技術的數據庫實例中(如把日誌信息存儲到MongoDB 中或把用戶登陸信息存儲到Redis 中)。

雖然數據管理的去中心化可讓數據管理更加細緻化, 經過採用更合適的技術可以讓數據存儲和性能達到最優。可是, 因爲數據存儲於不一樣的數據庫實例中後, 數據一致性也成爲微服務架構中亟待解決的問題之一。分佈式事務自己的實現難度就很是大, 因此在微服務架構中, 咱們更強調在各服務之間進行「 無事務」 的調用, 而對於數據一致性, 只要求數據在最後的處理狀態是一致的便可;若在過程當中發現錯誤, 經過補償機制來進行處理,使得錯誤數據可以達到最終的一致性。

基礎設施自動化

近年來雲計算服務與容器化技術的不斷成熟, 運維基礎設施的工做變得愈來愈容易。可是,當咱們實施微服務架構時,數據庫、應用程序的個頭雖然都變小了, 可是由於拆分的緣由, 數量成倍增加。這使得運維人員須要關注的內容也成倍增加, 而且操做性任務也會成倍增加, 這些問題若沒有獲得妥善解決, 必將成爲運維人員的噩夢。

因此,在微服務架構中, 務必從一開始就構建起「待續交付」平臺來支撐整個實施過程, 該平臺須要兩大內容, 缺一不可。

  • 自動化測試:每次部署前的強心劑, 儘量地得到對正在運行的軟件的信心。
  • 自動化部署:解放煩瑣枯燥的重複操做以及對多環境的配置管理。

容錯設計

在單體應用中,通常不存在單個組件故障而其餘部件還在運行的狀況, 一般是一掛全掛。而在微服務架構中, 因爲服務都運行在獨立的進程中, 因此存在部分服務出現故障,而其餘服務正常運行的狀況。好比,當正常運做的服務B調用到故障服務A時, 因故障服務A 沒有返回, 線程掛起開始等待, 直到超時才能釋放, 而此時若觸發服務B 調用服務A的請求來自服務C, 而服務C 頻繁調用服務B 時, 由千其依賴服務A, 大量線程被掛起等待, 最後致使服務A也不能正常服務, 這時就會出現故障的荽延。

因此, 在微服務架構中,快速檢測出故障源並儘量地自動恢復服務是必須被設計和考慮的。一般, 咱們都但願在每一個服務中實現監控和日誌記錄的組件, 好比服務狀態、斷路器狀態、吞吐量、網絡延遲等關鍵數據的儀表盤等。

演進式設計

經過上面的幾點特徵, 咱們已經可以體會到, 要實施一個完美的微服務架構, 須要考慮的設計與成本並不小, 對於沒有足夠經驗的團隊來講, 甚至要比單體應用付出更多的代價。

因此, 在不少狀況下, 架構師都會以演進的方式進行系統的構建。在初期, 以單體系統的方式來設計和實施, 一方面系統體量初期並不會很大, 構建和維護成本都不高。另外一方面,初期的核心業務在後期一般也不會發生巨大的改變。隨着系統的發展或者業務的須要, 架構師會將一些常常變更或是有必定時間效應的內容進行微服務處理, 並逐漸將原來在單體系統中多變的模塊逐步拆分出來, 而穩定不太變化的模塊就造成一個核心微服務存在於整個架構之中。

爲何選擇Spring Cloud

近幾年不少入對於微服務架構的熱情很是高, 可是回頭看「微服務」 被說起也有不少年了。無數的架構師和開發者在實際項目中實踐該設計理念併爲此付出了諸多努力, 同時 也分享了他們在微服務架構中針對不一樣應用場景出現的各類問題的各類解決方案和開源框架, 其中也不乏國內互聯網企業的傑出貢獻。

  • 服務註冊:阿里巴巴開源的Dubbo和噹噹網在其基礎上擴展的DubboX、Netflix的 Eureka、Apache的Consul等。
  • 分佈式配置管理:百度的Disconf、Netflix的Archaius、360的QConf、SpringCloud 的Config、淘寶的Diamond等。
  • 批量任務:噹噹網的Elastic-Job、Linkedln的Azkaban、SpringCloud的Task等。
  • 服務跟蹤:京東的Hydra、SpringCloud的Sleuth、Twitter的Zipkin等。

上面列舉了一些在實施微服務架構初期, 就須要被咱們考慮進去的問題,以及針對這些間題的開源解決方案。能夠看到國內、國外的技術公司都在貢獻着他們的智慧。咱們搜索微服務架構的實施方案時會發現,幾乎大部分的分享主要以理論或是一個粗輪廓框架爲主, 整合了來自不一樣公司或組織的諸多開源框架, 並加入針對自身業務的一些優化, 因此 找不到一個徹底相同的架構方案。

前面咱們介紹了一些關於微服務的理念以及特性, 分析了實施微服務的優勢和缺點,而這些缺點一般就是這些框架出現的源頭,你們都是爲了解決或彌補業務拆分後所引出的諸多詞題來設計出這些解決方案。而當咱們做爲一個新手, 準備實施微服務架構時, 爲了不踩前輩們踩過的坑, 咱們不得不在這些核心問題上作出選擇, 而選擇又是如此之多,這必然會致使在作技術選型的初期, 須要花費巨大的調研、分析與實驗精力。 Spring Cloud的出現,能夠說是對微服務架構的巨大支持和強有力的技術後盾。它不像咱們以前所列舉的框架那樣, 只是解決微服務中的某一個問題, 而是一個解決微服務架構實施的綜合性解決框架, 它整合了諸多被普遍實踐和證實過的框架做爲實施的基礎部件,又在該體系基礎上建立了一些很是優秀的邊緣組件。

打個不太恰當的比喻:咱們本身對各個問題選擇框架來實施微服務架構就像在DIY電腦同樣, 咱們對各環節的選擇自由度很高, 可是最終結果頗有可能由於一條內存質量不行就點不亮了, 老是讓人不怎麼放心。固然, 若是你是一名高手, 這些天然都不是問題, 然而千軍易得、良將難求。而使用Spring Cloud來實施就像直接購買品牌機同樣, 在Spring社區的整合之下, 作了大量的兼容性測試, 保證了其擁有更好的穩定性, 若是要在Spring Cloud架構下使用非原裝組件時, 就須要對其基礎有足夠的瞭解。

Spring Cloud也許對不少已經實施微服務並自成體系的團隊不具有足夠的吸引力,可是對於還未實施微服務或是未成體系的團隊, 這必將是一個很是有吸引力的框架選擇。不論其項目的發展目標, 仍是Spring的強大背景, 亦或其極高的社區活躍度, 都是將來企業架構師必須瞭解和接觸的重要框架, 有一天成爲微服務架構的標準解決方案也並不是不可能。

Spring Cloud簡介

Spring Cloud是一個基千Spring Boot實現的微服務架構開發工具。它爲微服務架構中涉及的配置管理、服務註冊、斷路器、智能路由、微代理、控制總線、全局鎖、決策競選、分佈式會話和集羣狀態管理等操做提供了一種簡單的開發方式。

Spring Cloud包含了多個子項目(針對分佈式系統中涉及的多個不一樣開源產品,還可能會新增), 以下所述。

  • Spring Cloud Config: 配置管理工具, 支持使用Git存儲配置內容, 能夠使用它實現應用配置的外部化存儲, 並支持客戶端配置信息刷新、加密/解密配置內容等。
  • Spring CloudN etflix: 核心組件,對多個Netflix OSS開源套件進行整合。
    • Eureka: 服務註冊組件, 包含服務註冊中心、服務註冊與發現機制的實現。
    • Hystrix: 容錯管理組件,實現斷路器模式, 幫助服務依賴中出現的延遲和爲故障提供強大的容錯能力。
    • Ribbon: 客戶端負載均衡的服務調用組件。
    • Feign: 基於伈bbon 和Hystrix 的聲明式服務調用組件。
    • Zuul: 網關組件, 提供智能路由、訪問過濾等功能。
    • Archaius: 外部化配置組件。
  • Spring Cloud Bus: 事件、消息總線, 用於傳播集羣中的狀態變化或事件, 以觸發後續的處理, 好比用來動態刷新配置等。
  • Spring Cloud Cluster: 針對ZooKeeperRedisHazelcastConsul 的選舉算法和通用狀態模式的實現。
  • Spring Cloud Cloudfoundry: 與Pivotal Cloudfoundry 的整合支持。
  • Spring Cloud Consul: 服務發現與配置管理工具。
  • Spring Cloud Stream: 經過Redis、Rabbit 或者Kafka 實現的消費微服務, 能夠經過 簡單的聲明式模型來發送和接收消息。
  • Spring Cloud A WS: 用千簡化整合Amazon Web Service 的組件。
  • Spring Cloud Security: 安全工具包, 提供在Zuul 代理中對0Auth2 客戶端請求的中 繼器。
  • Spring Cloud Sleuth: Spring Cloud 應用的分佈式跟蹤實現, 能夠完美整合Zip虹n。
  • Spring Cloud ZooKeeper: 基於ZooKeeper 的服務發現與配置管理組件。
  • Spring Cloud Starters: Spring Cloud 的基礎組件, 它是基於Spring Boot 風格項目的 基礎依賴模塊。
  • Spring Cloud CLI: 用於在Groovy 中快速建立Spring Cloud 應用的Spring Boot CLI 插件。

本教程將對其中一些較爲經常使用的組件進行介紹、分析, 並演示其使用方法。

###版本說明 當咱們經過搜索引擎查找一些Spring Cloud 的文章或示例時, 每每能夠在依賴中看到不少不一樣的版本名字, 好比Angel.SR六、Brix ton.SR5 等, 爲何Spring Cloud 沒有像其餘Spring 的項目使用相似l.x.x 的版本命名規則呢?這些版本之間又有什麼區別呢?在學習之 初,很是有必要弄清楚這些版本的意義和內容, 這樣才能在咱們使用Spring Cloud 時, 指導咱們選擇更爲合適的版本進行架構與開發。

版本名與版本號

因爲Spring Cloud 不像Spring 社區其餘一些項目那樣相對獨立, 它是一個擁有諸多子項目的大型綜合項目, 能夠說是對微服務架構解決方案的綜合套件組合, 其包含的各個子項目也都獨立進行着內容更新與迭代,各自都維護着本身的發佈版本號。所以每個Spring Cloud 的版本都會包含多個不一樣版本的子項目, 爲了管理每一個版本的子項目清單, 避免Spring Cloud的版本號與其子項目的版本號相混淆,沒有采用版本號的方式,而是經過命名的方式。

這些版本的名字採用了倫敦地鐵站的名字, 根據字母表的順序來對應版本時間順序,好比最先的Release版本爲Angel, 第二個Release版本爲Brixton……

通過上面的解釋, 不難猜出, 以前所提到的AngelS.SR六、BrixtonS.SR5中的SR六、SR5就是版本號了。 當一個版本的Spring Cloud項目的發佈內容積累到臨界點或者一個嚴重bug解決可用後, 就會發佈一個"service releases"版本,簡稱SRX版本, 其中X是一個遞增的數字, 因此Brixton.SR5就是Brixton的第5個Release版本。

使用spring cloud實現微服務

Spring Cloud Eureka 是Spring Cloud Netflix 微服務套件中的一部分,它基於Netflix Eureka作了二次封裝,主要負責完成微服務架構中的服務註冊功能。Spring Cloud 經過爲Eureka 增長了Spring Boot 風格的自動化配置,咱們只需經過簡單引入依賴和註解配置就能讓Spring Boot 構建的微服務應用輕鬆地與Eureka 服務註冊體系進行整合。

在本章中, 咱們將學習下面這些核心內容, 並構建起用於服務註冊的基礎設施。

  • 構建服務註冊中心
  • 服務註冊與服務發現
  • Eureka 的基礎架構
  • Eureka 的服務註冊機制
  • Eureka 的配置服務

在一開始,咱們須要先了解微服務中的參與者,他們分別是:服務提供者服務消費者服務註冊中心

服務提供者與服務消費者

使用微服務構建的是分佈式系統,微服務之間經過網絡進行通訊。咱們使用服務提供者與服務消費者來描述微服務之間的調用關係,下表解釋了服務提供者與服務消費者。
名詞 定義
服務提供者 服務的被調用方(即:爲其餘服務提供服務的服務
服務消費者 服務的調用方,即依賴其餘服務的服務

服務註冊中心

服務註冊能夠說是微服務架構中最爲核心和基礎的模塊, 它主要用來實現各個微服務實例的自動化註冊與發現。爲何咱們在微服務架構中那麼須要服務註冊模塊呢?微服務系統沒有它會有什麼很差的地方嗎?

在最初開始構建微服務系統的時候可能服務並很少, 咱們能夠經過作一些靜態配置來完成服務的調用。好比,有兩個服務A 和B, 其中服務A 須要調用服務B 來完成一個業務操做時, 爲了實現服務B 的高可用, 不論採用服務端負載均衡仍是客戶端負載均衡, 都須要手工維護服務B 的具體實例清單。可是隨着業務的發展, 系統功能愈來愈複雜, 相應的微服務應用也不斷增長, 咱們的靜態配置就會變得愈來愈難以維護。而且面對不斷髮展的業務, 咱們的集羣規模、服務的位置、服務的命名等都有可能發生變化, 若是仍是經過手工維護的方式,那麼極易發生錯誤或是命名衝突等問題。同時,對於這類靜態內容的維護 也必將消耗大量的人力。

爲了解決微服務架構中的服務實例維護問題, 產生了大量的服務註冊框架和產品。這些框架和產品的實現都圍繞着服務註冊與服務發現機制來完成對微服務應用實例的自動化管理。

使用服務註冊中心後的架構以下圖所示:

mark

服務提供者、服務消費者、服務註冊組件三者的關係大體以下:

  • 各微服務在啓動時,將本身的網絡地址等信息註冊到服務註冊組件中,服務註冊組件會存儲這些信息。
  • 服務消費者能夠從服務註冊組件中查詢服務提供者的網絡地址,並使用該地址調用服務提供者所提供的接口。
  • 各微服務與服務註冊組件使用必定機制(例如心跳)通訊,服務註冊組件如長時間沒法與某服務實例通訊,就會註銷該實例。
  • 微服務網絡地址發生變動時,會從新註冊到服務註冊組件。使用這種方式,服務消費者就無需人工維護提供者的網絡地址了。

綜上,服務註冊組件應該具有如下功能:

  • 服務註冊表:是服務註冊組件的核心,它用來記錄各微服務的信息,例如微服務的名稱、IP、端口等。服務註冊表提供查詢API和管理API,查詢API用於查詢可用的微服務實例,管理API用於服務的註冊和註銷。
  • 服務註冊於服務發現:服務註冊指的是微服務在啓動時,將本身的信息註冊到服務發現組件上的過程。服務發現是指查詢可用微服務列表及其網絡地址的機制。
  • 服務檢查: 服務註冊組件使用必定機制定時檢測已註冊的服務,如發現某實例長時間沒法訪問,就會從服務註冊表中移除該實例。

綜上,使用服務註冊組件的好處顯而易見。spring cloud提供了多種服務註冊組件的支持,例如:Eureka,Consul,Zookeeper等,在本教材內咱們均使用Eureka爲例。

服務註冊組件在市面上也可能叫作服務註冊組件、服務發現組件、註冊中心等名詞。

Eureka簡介

Eureka是Netflix開發的服務發現組件,自己是一個基於REST的服務。Spring Cloud將它集成在其子項目spring-cloud-netflix中,以實現Spring Cloud的服務發現功能。目前Eureka 項目至關活躍,代碼更新至關頻繁。

Region、Zone解析

Eureka的官方文檔對regin、zone幾乎沒有說起,因爲概念抽象,新手很難理解。所以,在分析Eureka原理以前,咱們先來了解一下region、zone、Eureka集羣三者的關係,如圖:

image_1c91473tt5mn1ctfmv77ov1skq16.png-5.6kB

region和zone(或者Availability Zone)均是AWS的概念。在非AWS環境下,咱們能夠簡單地將region理解爲Eureka集羣,zone理解成機房。這樣圖4-2就很好理解了——一個Eureka集羣被部署在了zone1機房和zone2機房中。

Eureka架構

Eureka架構圖以下

image_1c9147u9f5ej68n5v3n3g19901j.png-45.4kB

這是來自Eureka官方的架構圖,大體描述了Eureka集羣的工做過程。圖中包含的組件很是多,可能比較難以理解,咱們用通俗易懂的語言解釋一下:

  • Application Service 至關於服務提供者,Application Client至關於服務消費者;
  • Make Remote Call,能夠簡單理解爲調用RESTful API;
  • us-east-1c、us-east-1d等都是zone,它們都屬於us-east-1這個region;

由圖可知,Eureka包含兩個組件:Eureka ServerEureka Client,它們的做用以下:

  • Eureka Client是一個Java客戶端,用於簡化與Eureka Server的交互;
  • Eureka Server提供服務發現的能力,各個微服務啓動時,會經過Eureka Client向Eureka Server進行註冊本身的信息(例如網絡信息),Eureka Server會存儲該服務的信息;
  • 微服務啓動後,會週期性地向Eureka Server發送心跳(默認週期爲30秒)以續約本身的信息。若是Eureka Server在必定時間內沒有接收到某個微服務節點的心跳,Eureka Server將會註銷該微服務節點(默認90秒);
  • 每一個Eureka Server同時也是Eureka Client,多個Eureka Server之間經過複製的方式完成服務註冊表的同步;
  • Eureka Client會緩存Eureka Server中的信息。即便全部的Eureka Server節點都宕掉,服務消費者依然能夠使用緩存中的信息找到服務提供者。

綜上,Eureka經過心跳檢測、健康檢查和客戶端緩存等機制,提升了系統的靈活性、可伸縮性和可用性。

建立一個Eureka Server

在Spring Cloud實現一個Eureka Server是一件很是簡單的事情。下面咱們來寫一個Eureka Server 。

由於在spring cloud應用中會涉及到多個項目模塊,接下來咱們使用IDEA工具在一個windows下實現多個項目。

**第一步 ** 建立一個普通的java項目,存儲在一個空的目錄下,這裏具體步驟省略。 **第二步 ** 在上一步建立的項目的基礎上,新建一個Module file -> new -> Module 打開以下窗口,選擇Spring Initilizr ,點擊next image_1c914sdvu122k1us11gqs19961lk020.png-60.3kB

注意,在咱們的示例中採用目前最新的版本的spring cloud,其依賴的spring boot版本爲2.0,對JDK的需求是必須JDK1.8或者1.9,再也不支持1.7及如下版本。

填寫Module的相關信息,主要是groupArtifact,而後點擊next

image_1c91528vi1gfp19fc2651k2biog30.png-40.9kB

選擇Cloud Discover中的Eureka Server,點擊next,而後點擊finish,以下圖所示。

image_1c9155hik7pb13841e2a1m851k6p3d.png-55kB

注意,儘可能不要改變Module的存儲路徑,直接將其放在第一步創建的普通java項目目錄下。

創建好的Eureka Server目錄結構以下圖所示:

image_1c915a6hf2bpogo1e7b1kfl1bkj4q.png-27.6kB

說明:pom.xml是本Module的maven配置文件,EurekaServerApplication.java是入口程序,EurekaServerApplicationTests.java是測試入口。 application.properties是spring cloud的屬性配置文件,其也能夠是application.yml,這裏咱們用application.yml

其實細心的咱們已經發現了,spring cloud的項目結構和spring boot基本同樣。區別是在pom.xml中增長了spring cloud的依賴管理以及spring cloud Eureka server依賴,具體以下:

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

配置啓動類

在`EurekaServerApplication.java`入口類加上一個註解`@EnableEurekaServer`,聲明這是一個Eureka Server。
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}
}

編寫配置文件application.yml

server:
  port: 8761
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

這樣就完成了一個簡單的Eureka Server。簡要說明一下application.yml中的配置項: eureka.client.registerWithEureka :表示是否將本身註冊到Eureka Server,默認爲true。因爲當前這個應用就是Eureka Server,故而設爲false。 eureka.client.fetchRegistry :表示是否從Eureka Server獲取註冊信息,默認爲true。由於這是一個單點的Eureka Server,不須要同步其餘的Eureka Server節點的數據,故而設爲false。

使用maven打包

在經過IDEA的Terminal,進入Eureka-Server目錄,執行maven打包命令mvn clean package,打包成功後,再進入target目錄,經過java -jar命令執行

image_1c9165gko12j4k59iov1ls2107j77.png-4.8kB

出現以下所示信息時,表示服務註冊中心已經啓動成功。 image_1c9166nvk1n6u1og3sjr487187e7k.png-11.7kB

打開瀏覽器,輸入地址:http://localhost:8761訪問,咱們會發現此時尚未服務註冊到Eureka上面,以下圖:

image_1c9169bitu0dbk0707ed1mub81.png-107.5kB

該頁面展現了Eureka的系統狀態、當前註冊到Eureka Server上的服務實例、通常信息、實例信息等。咱們能夠看到,當前尚未任何服務被註冊到Eureka Server上。

建立一個服務提供者,提供服務

下面咱們建立提供服務的客戶端,並向服務註冊中心註冊本身。

建立服務提供者項目

首先,和建立Eureka Server應用基本同樣,惟一不一樣的地方在於,在Cloud Discovery再也不選擇Eureka Server,而是選擇Eureka Discover。命名爲eureka-provider,在pom.xml中,變動的配置信息是去掉了server的依賴,而增長了client依賴:

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

注意:在spring cloud的Finchley版本以前的版本,添加Eureka client依賴的artifactId爲spring-cloud-starter-eureka

修改程序啓動類

在啓動類加上一個註解@EnableDiscoveryClient,聲明這是一個Eureka client。

@EnableDiscoveryClient
@SpringBootApplication
public class UserProviderApplication {

	public static void main(String[] args) {
		SpringApplication.run(UserProviderApplication.class, args);
	}
}

編寫配置文件application.yml

server:
  port: 9000
spring:
  application:
    name: user-provider
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

經過spring.application.name屬性,咱們能夠指定微服務的名稱後續在調用的時候只須要使用該名稱就能夠進行服務的訪問。eureka.client.serviceUrl.defaultZone屬性對應服務註冊中心的配置內容,指定服務註冊中心的位置。爲了在本機上測試區分服務提供方和服務註冊中心,使用server.port屬性設置不一樣的端口。

編寫具體的服務邏輯

在spring cloud中,具體的服務表現爲Rest服務,經過spring mvc實現,固然咱們徹底能夠經過spring boot簡化它,具體代碼以下:

@RestController
public class HelloProviderController {

    @Value("${server.port}")
    private String port;

    @GetMapping("/hello")
    public String say() {
        return String.format("你好,我是一個服務提供者。個人對外的端口是:%s",port);
    }

}

運行程序服務提供者,註冊服務

能夠經過IDE工具直接運行入口程序,也能夠經過maven打包以後運行,在控制檯中輸出以下內容:

image_1c934ctcs1api166u1m7a1887178sm.png-15kB

且在eureka-server端的控制檯上有日誌消息以下

image_1c934hjfuhp2pa8i411jto1qrf2j.png-4.6kB

則表示服務提供者正確在服務註冊中心註冊了。如今咱們再次經過http://localhost:8761來訪問服務註冊中心,以下圖所示: image_1c934jm0s1tt9hnge48a2q3k33.png-102.7kB

Eureka的自我保護模式

若是在Eureka Server的首頁看到如下這段提示,則說明Eureka已經進入了保護模式。

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

保護模式主要用於一組客戶端和Eureka Server之間存在網絡分區場景下的保護。一旦進入保護模式,Eureka Server將會嘗試保護其服務註冊表中的信息,再也不刪除服務註冊表中的數據(也就是不會註銷任何微服務)。

如何解決Eureka Server不踢出已關停的節點的問題

在開發過程當中,咱們經常但願Eureka Server可以迅速有效地踢出已關停的節點,可是因爲Eureka自我保護模式,以及心跳週期長的緣由,經常會遇到Eureka Server不踢出已關停的節點的問題。解決方法以下:

(1) Eureka Server端:配置關閉自我保護,並按需配置Eureka Server清理無效節點的時間間隔

eureka.server.enable-self-preservation			# 設爲false,關閉自我保護
eureka.server.eviction-interval-timer-in-ms     # 清理間隔(單位毫秒,默認是60*1000)

(2) Eureka Client端:配置開啓健康檢查,並按需配置續約更新時間和到期時間。

eureka.client.healthcheck.enabled			# 開啓健康檢查(須要spring-boot-starter-actuator依賴)
eureka.instance.lease-renewal-interval-in-seconds		# 續約更新時間間隔(默認30秒)
eureka.instance.lease-expiration-duration-in-seconds 	# 續約到期時間(默認90秒)

示例: 服務器端配置:

eureka:
  server:
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 4000

客戶端配置:

eureka:
  client:
    healthcheck:
      enabled: true
  instance:
    lease-expiration-duration-in-seconds: 30 
    lease-renewal-interval-in-seconds: 10

注意: 更改Eureka更新頻率將打破服務器的自我保護功能,生產環境下不建議自定義這些配置。

建立一個服務消費者,消費服務

下面咱們建立消費服務的客戶端,從服務中心查詢一個服務,並調用該服務。

建立服務消費者項目

服務消費者項目和本質上也是一個Eureka server的客戶端,因此和建立服務提供者的方式一致。

修改啓動類

在啓動類加上一個註解@EnableDiscoveryClient,聲明這是一個Eureka client。

@EnableDiscoveryClient
@SpringBootApplication
public class UserConsumerApplication {

	@Bean
    @LoadBalanced
	public RestTemplate create(){
		return new RestTemplate();
	}


	public static void main(String[] args) {
		SpringApplication.run(UserConsumerApplication.class, args);
	}
}

在啓動類中定義了一個RestTemplate的bean,併爲它添加了@LoadBalanced註解,該註解具體含義後面介紹

編寫配置文件application.yml

server:
  port: 8000
spring:
  application:
    name: user-consumer
eureka:
  client:
    serviceUrl:
      myZone: http://localhost:8761/eureka/

編寫Controller,使用RestTemplate來消費服務

@RestController
public class HelloController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/call")
    public String call(){
       return restTemplate.getForEntity("http://user-provider/hello",String.class).getBody();
    }

}

說明:http://user-provider/hello 其中user-provider是服務提供者的應用名稱,即spring.application.name,它不區分大小寫,而hello爲咱們要消費的具體服務。 這個應用能夠理解爲服務消費者在服務註冊中心經過應用名稱user-provider找到具體提供服務的應用,而後再調用其具體的服務。

啓動應用,測試

啓動應用,經過http://localhost:8000/call 訪問,結果以下:

image_1c938gfh618be1hhruu31diiatm3g.png-9.4kB

高可用服務註冊中心

在微服務架構這樣的分佈式環境中咱們須要充分考慮發生故障的狀況, 因此在生產環境中必須對各個組件進行高可用部署, 對於微服務如此,對於服務註冊中心也同樣。可是到本節爲止,咱們一直都在使用單節點的服務註冊中心,這在生產環境中顯然並不合適,咱們須要構建高可用的服務註冊中心以加強系統的可用性。EurekaS erver的設計一開始就考慮了高可用問題, 在Eureka的服務註冊設計中, 全部節點便是服務提供方, 也是服務消費方, 服務註冊中心也不例外。是否還記得在單節點的配置中, 咱們設置過下面這兩個參數, 讓服務註冊中心不註冊本身:

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

Eureka Server的高可用實際上就是將本身做爲服務向其餘服務註冊中心註冊本身,這樣就能夠造成一組互相註冊的服務註冊中心, 以實現服務清單的互相同步,達到高可用的效果。下面咱們就來嘗試搭建高可用服務註冊中心的集羣。咱們在前面的服務註冊中心的基礎之上進行擴展, 構建一個雙節點的服務註冊中心集羣。

注意:若須要實現eureka server集羣,須要將以上兩個參數設置爲true,不然將形成不可用的服務分片unavailable-replicas

  • 建立application-peerl.yml, 做爲peerl服務中心的配置,並將serviceUri指向peer2:
spring:
  application:
    name: eureka-server  #服務名稱,Eureka server集羣的服務名稱必須一致
server:
  port: 1111  # 端口號
eureka:
  instance:
    hostname: peer1  #主機名稱
  client:
    serviceUrl:
      defaultZone: http://peer2:1112/eureka/  # 將本身做爲一個微服務應用註冊到另一個Eureka server
    registerWithEureka: true   #必須設置爲true
    fetchRegistry: true        #必須設置爲true
  server:
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 4000
  • 建立application-peer2.yml,做爲peer2服務中心的配置,並將serviceUrl指向peer1:
spring:
  application:
    name: eureka-server  #服務名稱,Eureka server集羣的服務名稱必須一致
server:
  port: 1112  # 端口號
eureka:
  instance:
    hostname: peer2  #主機名稱
  client:
    serviceUrl:
      defaultZone: http://peer1:1111/eureka/  # 將本身做爲一個微服務應用註冊到另一個Eureka server
    registerWithEureka: true   #必須設置爲true
    fetchRegistry: true        #必須設置爲true
  server:
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 4000
  • 配置系統的hosts,Windows系統的hosts文件路徑是:C:\Windows\System32\drivers\etc\hosts,Linux及Mac Os等系統的文件路徑是/etc/hosts,在這個文件的最後添加:
127.0.0.1 peer1 
127.0.0.1 peer2
  • 經過spring.profiles.active屬性來分別啓動peer1和peer2

此時訪問peer1的註冊中心:http://localhost:1111/,以下圖所示,咱們能夠看到registered-replicas中已經有peer2節點的eureka-server了。一樣地,訪問peer2的註冊中心:http://localhost:1112/,能看到registered-replicas中已經有peer1節點,而且這些節點在可用分片(available-replicase)之中。咱們也能夠嘗試關閉peer1,刷新http://localhost:1112/,能夠看到peer1的節點變爲了避免可用分片(unavailable-replicas)。

image_1c93l1a1o1gi7chisnf1ik1q8c54.png-121.8kB

服務註冊與發現

在設置了多節點的服務註冊中心以後,咱們只須要簡單的服務配置,就能將服務註冊到Eureka Server集羣中。咱們之前面的user-provider爲基礎,修改application.yml配置文件:

server:
  port: 9000
spring:
  application:
    name: user-provider
eureka:
  client:
    healthcheck: true
    serviceUrl:
      deafultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/

上面的配置主要對eureka.client.serviceUrl.defaultZone屬性作了改動,將註冊中心指向了以前咱們搭建的peer1與peer2,中間以英文逗號分隔。

下面,咱們啓動該服務,經過訪問http://localhost:1111/和http://localhost:1112/,能夠觀察到user-provider同時被註冊到了peer1和peer2上。若此時斷開peer1,因爲user-provider同時也向peer2註冊,所以在peer2上其餘服務依然能訪問到compute-service,從而實現了高可用的服務註冊中心。

說明:其實eureka.client.serviceUrl.defaultZone能夠不用指定全部的Eureka Server節點,僅指peer1的話,Eureka Server集羣會自動將該服務同步註冊到peer2,可是不推薦這麼作,由於這麼作沒法解決Eureka Server集羣的單點故障。

深刻理解

雖然上面咱們以雙節點做爲例子,可是實際上因負載等緣由,咱們每每可能須要在生產環境構建多於兩個的Eureka Server節點。那麼對於如何配置serviceUrl來讓集羣中的服務進行同步,須要咱們更深刻的理解節點間的同步機制來作出決策。

Eureka Server的同步遵循着一個很是簡單的原則:只要有一條邊將節點鏈接,就能夠進行信息傳播與同步。什麼意思呢?不妨咱們經過下面的實驗來看看會發生什麼。

  • 場景一:假設咱們有3個註冊中心,咱們將peer一、peer二、peer3各自都將serviceUrl指向另外兩個節點。換言之,peer一、peer二、peer3是兩兩互相註冊的。啓動三個服務註冊中心,並將user-provider的serviceUrl指向peer1並啓動,能夠得到以下圖所示的集羣效果。 image_1c93a5co9cdk16dcp011bp31rkc4n.png-20.1kB

訪問http://localhost:1112/,能夠看到3個註冊中心組成了集羣,user-provider服務經過peer1同步給了與之互相註冊的peer2和peer3。

經過上面的實驗,咱們能夠得出下面的結論來指導咱們搭建服務註冊中心的高可用集羣: 兩兩註冊的方式能夠實現集羣中節點徹底對等的效果,實現最高可用性集羣,任何一臺註冊中心故障都不會影響服務的註冊與發現

高可用的服務提供者

在上面的示例中,咱們經過Eureka server集羣的方式實現了高可用的服務註冊中心,接下來咱們須要實現高可用的服務提供者。

在生產環境下,單點的服務提供每每會存在性能不足、可用性不高等缺陷,在spring cloud中能夠很是方便的實現高可用的服務提供者,即將某服務應用以集羣的形式註冊到服務註冊中心,不用修改任何的user-provider的代碼,僅僅經過下列方式啓動多個user-provider的實例便可。

java -jar user-provider-0.0.1-SNAPSHOT.jar --server.port=2001
java -jar user-provider-0.0.1-SNAPSHOT.jar --server.port=2002
java -jar user-provider-0.0.1-SNAPSHOT.jar --server.port=2003

再次訪問http://peer1:1111或者http://peer2:1112如圖所示,可見註冊了3個user-provider服務到Eureka server。

image_1c93lsq4kgp6vfd1v2e9km1net5h.png-45.6kB

使用ribbon實現客戶端負載均衡

在前面,咱們部署了3個user-provider的實例,那麼對於服務消費者來講,是如何將強求分攤到多個服務提供者身上呢?

Ribbon

Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端負載均衡的工具。它是一個基於HTTP和TCP的客戶端負載均衡器。當爲Ribbon配置服務提供者地址列表後,Ribbon就能夠基於某種負載均衡算法,自動地幫助服務消費者去請求。Ribbon默認爲咱們提供了不少的負載均衡算法,例如輪詢、隨機等。固然咱們也能夠爲Ribbon實現自定義的負載均衡算法。

在spring cloud中,當ribbon與Eureka配合使用時,Ribbon能夠自動從Eureka server獲取服務提供者地址列表,並給予負載均衡算法,請求其中一個服務提供者實例。下圖展現了Ribbon與Eureka配合使用時的大體架構。

image_1c93mmthd1tip10ku1ngn8hvbhc5u.png-55.1kB

Ribbon工做時分爲兩步:第一步先選擇 Eureka Server, 它優先選擇在同一個Zone且負載較少的Server;第二步再根據用戶指定的策略,在從Server取到的服務註冊列表中選擇一個地址。其中Ribbon提供了多種策略,例如輪詢round robin、隨機Random、根據響應時間加權等。

動手試一試

其實咱們不用對服務消費者進行任何的變動,便可實現客戶端基於Ribbon的負載均衡。核心緣由在:

  1. spring-cloud-starter-netflix-eureka-client依賴中已經包含有spring-cloud-starter-netflix-eureka-Ribbon,全部不用再添加依賴
  2. 還記得前面在user-consumer項目的啓動類中,定義的RestTemplate嗎?咱們爲它增長了一個@LoadBalanced註解,即表明RestTemplate已經整合了Ribbon,無需咱們再進行其餘的變動了。

如今咱們再次啓動user-consumer,並屢次訪問http://localhost:3000/call,結果執行爲如下一些數據

你好,我是一個服務提供者。個人對外的端口是:2000
你好,我是一個服務提供者。個人對外的端口是:2001
你好,我是一個服務提供者。個人對外的端口是:2002
你好,我是一個服務提供者。個人對外的端口是:2000
你好,我是一個服務提供者。個人對外的端口是:2001
你好,我是一個服務提供者。個人對外的端口是:2002
你好,我是一個服務提供者。個人對外的端口是:2000
...

能夠看到,此時請求均勻分佈在3個微服務節點上,說明實現了負載均衡。

在默認狀況下,Ribbon和Eureka server集成後,所採用的負載均衡算法爲輪詢算法。但在生成環境,輪詢算法並不是適用於全部的場景,此時咱們就須要修改其負載均衡策略。固然除此以外還有不少Ribbon配置能夠修改,此處不作講解。

服務容錯保護: Spring Cloud Hystrix

在微服務架構中,咱們將系統拆分紅了一個個的服務單元,各單元間經過服務註冊與訂閱的方式互相依賴。因爲每一個單元都在不一樣的進程中運行,依賴經過遠程調用的方式執行,這樣就有可能由於網絡緣由或是依賴服務自身問題出現調用故障或延遲,而這些問題會直接致使調用方的對外服務也出現延遲,若此時調用方的請求不斷增長,最後就會出現因等待出現故障的依賴方響應而造成任務積壓,最終致使自身服務的癱瘓。

舉個例子,在一個電商網站中,咱們可能會將系統拆分紅,用戶、訂單、庫存、積分、評論等一系列的服務單元。用戶建立一個訂單的時候,在調用訂單服務建立訂單的時候,會向庫存服務來請求出貨(判斷是否有足夠庫存來出貨)。此時若庫存服務因網絡緣由沒法被訪問到,致使建立訂單服務的線程進入等待庫存申請服務的響應,在漫長的等待以後用戶會由於請求庫存失敗而獲得建立訂單失敗的結果。若是在高併發狀況之下,因這些等待線程在等待庫存服務的響應而未能釋放,使得後續到來的建立訂單請求被阻塞,最終致使訂單服務也不可用。

在微服務架構中,存在着那麼多的服務單元,若一個單元出現故障,就會因依賴關係造成故障蔓延,最終致使整個系統的癱瘓,這樣的架構相較傳統架構就更加的不穩定。爲了解決這樣的問題,所以產生了斷路器模式。

經過前邊的學習,服務註冊中心、服務提供者和服務消費者都成功創建並運行起來,並且經過默認的配置RestTemplate@Loadbalanced註解開啓了負載均衡。 在默認的狀況下,負載均衡策略是線性輪詢的方式,也就是說在客戶端獲取到的服務列表中依次交替,例如開啓了三個服務server一、server二、server3,那麼在線性輪詢時,就會按這個順序來調用。 我以前是開啓了三個服務,一個端口是2000,2001和2002,那麼在以前的這種狀況下,若是我關閉其中一個服務,就好比這裏關閉2001端口的服務,當再次訪問的時候,每訪問三次,就會有一次是以下的error page,直到我掛掉的這個服務被服務註冊中心剔除前均會存在。

若是服務提供者響應很是緩慢,name消費者對提供者的請求就會被強制等待,知道提供者響應或超時。在高負載場景下,若是不作任何處理,此類問題可能會致使服務消費者的資源耗盡甚至整個系統的崩潰。例如,曾經發生過一個案例----某電子商務網站在一個黑色星期五發生過過載。過多的併發請求,致使用戶支付的請求延遲好久都沒有響應,在等待很長時間後最終失敗。支付失敗又致使用戶從新刷新頁面並再次嘗試支付,進一步增長了服務器的負載,最終致使整個系統都崩潰了。

當依賴的服務不可用時,服務自身會不會被拖垮,這是咱們在構建分佈式應用時須要考慮的問題。

雪崩效應

微服務架構的應用系統一般包含多個服務層。微服務之間經過網絡進行通訊,從而支撐整個應用系統,所以,微服務之間不免存在依賴關係。咱們知道任何微服務都並不是100%可用,網絡每每也很脆弱,所以不免有些請求會失敗。

咱們常把「基礎服務故障」致使「級聯故障」的現象稱爲雪崩效應。雪崩效應描述的是提供者不可用致使消費者不可用,並將不可用逐漸放大的過程。

以下圖,A做爲服務提供者(基礎服務),B爲A的服務消費者,C和D都是B的消費者。當A不可用引發B的不可用,並將不可用像滾雪球同樣放大到C和D時,雪崩效應就造成了。

image_1c93tti5r1mufe5r1fmj1g241c146o.png-106.7kB

如何容錯

要想防止雪崩效應,必須有一個強大的容錯機制。該容錯機制需事先如下兩點:

  • 爲網絡請求設置超時 必須爲網絡請求設置超時。正常狀況下,一個遠程調用通常在幾十毫秒內就能獲得響應了。若是依賴的服務不可用或者網絡有問題,那麼響應時間就會變得很長(幾十秒)。 一般狀況下, 一次遠程調用對應着一個線程/進程。若是響應太慢,這個線程/進程就得不到釋放。而線程/進程又對應着系統資源,若是得不到釋放的線程/進程越積越多,資源就會逐漸被耗盡,最終致使服務的不可用。所以,必須爲每一個網絡請求設置超時,讓資源儘快釋放。
  • 使用斷路器模式 試想一下,若是家裏沒有斷路器,當電流過載時(例如功率過大、短路等),電路不斷開,電路就會升溫,甚至可能燒斷電路、引起火災。使用斷路器,電路一旦過載就會跳閘,從而能夠保護電路的安全。在電路超載的問題被解決後,只須關閉斷路器,電路就能夠恢復正常。同理,若是對某個微服務的請求有大量超時(經常說明該微服務不可用),再去讓新的請求訪問該服務已經沒有任何意義,只會無謂消耗資源。例如,設置了超時時間爲1秒,若是短期內有大量的請求沒法在1秒內獲得響應,就沒有必要再去請求依賴的服務了。 斷路器可理解爲對容易致使錯誤的操做的代理。這種代理可以統計一段時間內調用失敗的次數,並決定是正常請求依賴的服務仍是直接返回。 斷路器能夠實現快速失敗,若是它在一段時間內檢測到許多相似的錯誤(例如超時),就會在以後的一段時間內,強迫對該服務的調用快速失敗,即再也不請求所依賴的服務。這樣,應用程序就無須再浪費CPU時間去等待長時間的超時。 斷路器也可自動診斷依賴的服務是否已經恢復正常。若是發現依賴的服務已經恢復正常,那麼就會恢復請求該服務。使用這種方式,就能夠實現微服務的「自我修復」——當依賴的服務不正常時打開斷路器時快速失敗,從而防止雪崩效應;當發現依賴的服務恢復正常時,又會恢復請求。 斷路器狀態轉換的邏輯以下圖所示,簡單來講:
    • 正常狀況下,斷路器關閉,可正常請求依賴的服務。
    • 當一段時間內,請求失敗率達到必定闊值(如錯誤率達到50%,或100次/分鐘等),斷路器就會打開。此時,不會再去請求依賴的服務。
    • 斷路器打開一段時間後,會自動進入「半開」狀態。此時,斷路器可容許一個請求訪問依賴的服務。若是該請求可以成功調用,則關閉斷路器;不然繼續保持打開狀態。

image_1c93vj469uqnl42h1p1vdt3j882.png-96.6kB

Hystrix簡介

hystrix是一個實現了超時機制和斷路器模式的工具類庫。

簡介

hystrix是由Netflix開源的一個延遲和容錯庫,用於隔離訪問遠程系統、服務、或者第三方庫,防止級聯失敗,從而提高系統的可用性與容錯性。

hystrix主要經過如下幾點實現容錯和延遲:

包裹請求

使用hystrixCommand(或hystrixObservableCommand)包裹對依賴的調用邏輯,每一個命令在獨立線程中執行。這使用到了設計模式中的「命令模式」。

跳閘機制

當某服務的錯誤率超過必定闊值時,hystrix能夠自動或手動跳閘,中止請求該服務一段時間。

資料隔離

hystrix爲每一個依賴都維護了一個小型的線程池(或信號量)。若是該線程池已滿,發往該依賴的請求就會被當即拒絕,而不是排隊等待,從而加速失敗斷定。

監控

hystrix能夠近乎實時的監控運行指標和配置的變化,例如成功、失敗、超時以及被拒絕的請求等

回退機制

當請求失敗、超時、被拒絕,或當斷路器打開時,執行回退邏輯。回退邏輯可由開發人員自行提供,例如返回一個缺省值。

自我修復

斷路器打開一段時間後,會自動進入「半開」狀態。

使用hystrix實現容錯

第一步 : 在服務消費者的pom.xml中增長hystrix的依賴

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

注意,在springcloud的Finchley版本以前的starter是

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

第二步: 在服務消費者的啓動類上加上註解@EnableHystrix

說明:

  1. @EnableHystrix 註解能夠使用@EnableCircuitBreaker註解來替代,代碼以下:

    @EnableCircuitBreaker
    @EnableDiscoveryClient
    @SpringBootApplication
    public class UserConsumerApplication {...}
  2. 在springboot中提供了@SpringCloudApplication來定義spring cloud應用,他整合了多個註解,主要包含服務發現和斷路器這兩個註解,代碼以下:

    @SpringCloudApplication
    public class UserConsumerApplication{...}

第三步: 修改Controller

@RestController
public class HelloController {


    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/call")
    @HystrixCommand(fallbackMethod = "whenCallError")
    public String call(){
       return restTemplate.getForEntity("http://user-provider/hello",String.class).getBody();
    }
    
    public String whenCallError(){
        return "遠程服務發生錯誤了,該功能暫時不可用.";
    }

}

代碼中,call方法增長了註解@HystrixCommand(fallbackMethod = "whenCallError"),表示若遠程微服務消費不成功,則執行fallbackMethod所指定的方法,這叫服務回退,也叫服務的降級

注意

fallbackMethod所指定的方法的返回類型必須是call方法的返回類型兼容。

第四步: 啓動消費者應用,訪問http://localhost:3000/call

發如今正常狀況下未有任何影響,可是若服務提供者因某些緣由沒法正常被消費,好比服務提供者宕機不可訪問時,直接響應的是 image_1c95khlsd17ss11rs14cvcco1r09.png-8.8kB

使用Feign實現聲明式REST調用

咱們在使用Spring Cloud 伈bbon 時, 一般都會利用它對RestTemplate 的請求攔截來實現對依賴服務的接口調用, 而RestTemplate 已經實現了對HTTP 請求的封裝處理, 造成了一套模板化的調用方法。在以前的例子中,咱們只是簡單介紹了RestTemplate 調用的實現,可是在實際開發中,因爲對服務依賴的調用可能不止於一處,每每一個接口會被多處調用,因此咱們一般都會針對各個微服務自行封裝一些客戶端類來包裝這些依賴服務的調用。這個時候咱們會發現, 因爲RestTemplate 的封裝, 幾乎每個調用都是簡單的模板化內容。綜合上述這些狀況, Spring Cloud Feign 在此基礎上作了進一步封裝, 由它來幫助咱們定義和實現依賴服務接口的定義。在Spring Cloud Feign 的實現下, 咱們只需建立一個接口並用註解的方式來配置它, 便可完成對服務提供方的接口綁定, 簡化了在使用Spring Cloud伈bbon 時自行封裝服務調用客戶端的開發量。

Spring Cloud Feign是一套基於Netflix Feign實現的聲明式服務調用客戶端。它使得編寫Web服務客戶端變得更加簡單。咱們只須要經過建立接口並用註解來配置它就可完成對Web服務接口的綁定。它具有可插拔的註解支持,包括Feign註解、JAX-RS註解。它也支持可插拔的編碼器和解碼器。Spring Cloud Feign還擴展了對Spring MVC註解的支持,同時還整合了Ribbon和Eureka來提供均衡負載的HTTP客戶端實現。

下面,咱們經過一個例子來展示Feign如何方便的聲明對eureka-client服務的定義和調用。

下面的例子,咱們將利用以前構建的eureka-server做爲服務註冊中心、user-provider做爲服務提供者做爲基礎。而基於Spring Cloud Ribbon實現的消費者,咱們能夠根據user-consumer實現的內容進行簡單改在就能完成,具體步驟以下:

添加Feign依賴

user-consumer中添加Feign依賴:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Finchley以前版本,Fenign的artifactId爲: <artifactId>spring-cloud-starter-feign</artifactId>

修改啓動類

修改應用啓動類。經過@EnableFeignClients註解開啓掃描Spring Cloud Feign客戶端的功能,同時取消掉RestTEmplate的bean定義,固然若是在你的應用中仍然想用的話也能夠繼續保留。

@EnableFeignClients
@SpringCloudApplication
public class UserConsumerApplication {

	public static void main(String[] args) {
		SpringApplication.run(UserConsumerApplication.class, args);
	}
}

建立Feign的客戶端接口定義。

建立一個Feign的客戶端接口定義,使用@FeignClient註解來指定這個接口所要調用的服務名稱,接口中定義的各個函數使用Spring MVC的註解就能夠來綁定服務提供方的REST接口,好比下面就是綁定user-provider服務的/hello接口的例子:

@FeignClient(name = "user-provider")
public interface HelloClient {

    @GetMapping("/hello")
    String hello();

}

修改HelloController

修改Controller。經過定義的feign客戶端來調用服務提供方的接口:

@RestController
public class HelloController {



    @Autowired
    private HelloClient helloClient;

    @GetMapping("/call")
    @HystrixCommand(fallbackMethod = "whenCallError")
    public String call(){
        return helloClient.hello();
    }

    public String whenCallError(){
        return "遠程服務發生錯誤了,該功能暫時不可用.";
    }

}

能夠看到經過Spring Cloud Feign來實現服務調用的方式更加簡單了,經過@FeignClient定義的接口來統一的聲明咱們須要依賴的微服務接口。而在具體使用的時候就跟調用本地方法一點的進行調用便可。因爲Feign是基於Ribbon實現的,因此它自帶了客戶端負載均衡功能,也能夠經過Ribbon的IRule進行策略擴展。另外,Feign還整合的Hystrix來實現服務的容錯保護,在Finchley版本中,Feign的Hystrix默認是打開的的。可是在在Dalston版本中,Feign的Hystrix默認是關閉的。

在完成了上面的代碼編寫以後,讀者能夠將eureka-server、user-provider、user-consumer都啓動起來,而後訪問http://localhost:3000/call ,來跟蹤觀察user-consumer服務是如何消費user-provider服務的/hello接口的,而且也能夠經過啓動多個user-provider服務來觀察其負載均衡的效果。

示例:實現用戶的完整RESTFul API

在前面的示例中,服務提供者都是提供的get方式的請求,而且未攜帶參數,但在實際應用場景中,這是徹底不能知足業務需求的,接下來咱們經過一個對用戶進行CRUD的示例來看看如何實現其餘的請求方法,以及參數的傳遞。

前置工做

由於在user-provider和user-consumer中,均須要使用到相同的POJO對象User,因此咱們創建一個公共的maven模塊,在這裏面定義User.java,並在user-provider和user-consumer中進行引用。

其實不只是User.java,在分佈式微服務應用領域,有不少通用的java類,咱們均可以將其從各個子模塊中抽象出來,創建成一個單獨的模塊,而後在須要使用的地方引入便可。

  1. 新建一個空的maven項目,其pom.xml內容以下:
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.dengcl</groupId>
        <artifactId>springcloud-base</artifactId>
        <version>1.0-SNAPSHOT</version>
    
    </project>
  2. 新建java類User.java,其代碼以下:
    package com.dengcl.springcloud.pojo;
    /**
     * Description:
     * User: tangbak
     * Date: 2018-03-15
     * Time: 16:07
     */
    public class User {
        private Long id;
        private String name;
        private Integer age;
        public User() {
        }
        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
        public Long getId() {
            return id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
    }
  3. 經過mvn install發佈springcloudbase到本地maven資源庫
  4. 在user-provider和user-comsumer中的'pom.xml'中添加依賴
<dependency>
 <groupId>com.dengcl</groupId>
 <artifactId>springcloud-base</artifactId>
 <version>1.0-SNAPSHOT</version>
</dependency>

服務提供者提供用戶相關的服務

在user-provider中添加UserController.java,其代碼以下:

package com.dengcl.userprovider.controller;
import com.dengcl.springcloud.pojo.User;
import org.springframework.web.bind.annotation.*;

import java.util.*;
@RestController
@RequestMapping(value="/users")     // 經過這裏配置使下面的映射都在/users下
public class UserController {
 
    // 建立線程安全的Map 
    static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());

    @RequestMapping(value={""}, method=RequestMethod.GET)
    public List<User> getUserList() {
        List<User> r = new ArrayList<User>(users.values());
        return r;
    }

    @RequestMapping(value="", method= RequestMethod.POST)
    public String postUser(@RequestBody User user) {
        users.put(user.getId(), user);
        return "success";
    }

    @RequestMapping(value="/{id}", method=RequestMethod.GET)
    public User getUser(@PathVariable Long id) {
        return users.get(id);
    }

    @RequestMapping(value="/{id}", method=RequestMethod.PUT)
    public String putUser(@PathVariable Long id, @RequestBody User user) {
        User u = users.get(id);
        u.setName(user.getName());
        u.setAge(user.getAge());
        users.put(id, u);
        return "success";
    }

    @RequestMapping(value="/{id}", method=RequestMethod.DELETE)
    public String deleteUser(@PathVariable Long id) {
        users.remove(id);
        return "success";
    }
}

在這個示例中,咱們經過一個靜態的線程安全的Map來存儲用戶信息,這種存儲方式僅適用於服務提供者不採用集羣的時候。固然徹底能知足咱們如今要實現的目標。

實現消費者功能

編寫UserClient,代碼以下:
@FeignClient("user-provider")
public interface UserClient {

    @GetMapping("/users")
    List<User> getUserList() ;

    @PostMapping("/users")
    public String postUser(@RequestBody User user) ;

    @GetMapping("/users/{id}")
    public User getUser(@PathVariable("id") Long id) ;

    @PutMapping("/users/{id}")
    public String putUser(@PathVariable("id") Long id, @RequestBody User user) ;

    @DeleteMapping("/users/{id}")
    public String deleteUser(@PathVariable("id") Long id) ;

}

特別注意:

  1. 雖然Feign兼容SpringMVC的註解,可是有一點特別要注意:FeignClient接口中,若是使用到@PathVariable ,必須指定其value,不能省略,必須指定。
  2. 若get請求方法的參數是複雜對象,請求不會成功,只要參數是複雜對象,即便指定了是GET方法,feign依然會以POST方法進行發送請求。
編寫UserConsumerController,代碼以下:
@RestController
@RequestMapping("/userconsumer")
public class UserConsumerController {

    @Autowired
    private UserClient userClient;


    @RequestMapping(value={""}, method= RequestMethod.GET)
    public List<User> getUserList() {
        return userClient.getUserList();
    }

    @RequestMapping(value="", method= RequestMethod.POST)
    public String postUser(@RequestBody User user) {
        return userClient.postUser(user);
    }

    @RequestMapping(value="/{id}", method=RequestMethod.GET)
    public User getUser(@PathVariable Long id) {
        return userClient.getUser(id);
    }

    @RequestMapping(value="/{id}", method=RequestMethod.PUT)
    public String putUser(@PathVariable Long id, @RequestBody User user) {
       return userClient.putUser(id,user);
    }

    @RequestMapping(value="/{id}", method=RequestMethod.DELETE)
    public String deleteUser(@PathVariable Long id) {
        return userClient.deleteUser(id);
    }
}

OK,接下來能夠經過PostMan來測試這些接口是否正確了。

Feign整合Hystrix實現服務降級

在前面介紹Hystrix時,咱們經過@HystrixCommand(fallbackMethod = "whenCallError")實現了服務的降級處理,可是若是用Feign客戶端的話,那麼又如何來實現服務降級呢?

定義Feign client接口的實現類

代碼以下:

@Component
public class UserClientFallback implements UserClient {
    @Override
    public List<User> getUserList() {
        return new ArrayList<>();
    }
    @Override
    public String postUser(User user) {
        return "error";
    }
    @Override
    public User getUser(Long id) {
        User user = new User();
        user.setId(id);
        user.setName("unknown");
        return user;
    }
    @Override
    public String putUser(Long id, User user) {
        return "error";
    }
    @Override
    public String deleteUser(Long id) {
        return "error";
    }
}

注意這個類須要@Component註解把它加入到spring 容器。

修改Feign client接口

其實只須要修改這個接口的@FeignClient註解就能夠了。具體以下:

@FeignClient(name = "user-provider",fallback = UserClientFallback.class)
public interface UserClient {

}

開啓Hystrix

在Spring Cloud Feign中,除了引入了用於客戶端負載均衡的Spring Cloud Ribbon以外,還引入了服務保護與容錯的工具Hystrix。默認狀況下,Spring Cloud Feign會爲將全部Feign客戶端的方法都封裝到Hystrix命令中進行服務保護。

**默認狀況下,Spring Cloud Feign會爲將全部Feign客戶端的方法都封裝到Hystrix命令中進行服務保護。**這個說法在Finchley版本以前沒有錯,可是在Finchley版本中正好相反,在該版本中,Feign客戶端的Hystrix熔斷器是默認關閉的,須要咱們手動開啓。

開啓的方式爲在application.yml中增長:

###  開啓全局的hystrix熔斷器,在Finchley版本中是默認關閉的,其餘版本默認打開
feign:
  hystrix:
    enabled: true

測試

如今咱們關閉user-provider應用,而後測試user-consumer的User相關接口,發現熔斷器已經工做。



服務網關

經過以前Spring Cloud中幾個核心組件的介紹,咱們已經能夠構建一個簡略的(不夠完善)微服務架構了。好比下圖所示:

image_1c986bjo7p8i14cua109egaja9.png-79.3kB

咱們使用Spring Cloud Netflix中的Eureka實現了服務註冊中心以及服務註冊與發現;而服務間經過Ribbon或Feign實現服務的消費以及均衡負載;經過Spring Cloud Config實現了應用多環境的外部化配置以及版本管理。爲了使得服務集羣更爲健壯,使用Hystrix的融斷機制來避免在微服務架構中個別服務出現異常時引發的故障蔓延。

在該架構中,咱們的服務集羣包含:內部服務Service A和Service B,他們都會註冊與訂閱服務至Eureka Server,而Open Service是一個對外的服務,經過均衡負載公開至服務調用方。本文咱們把焦點彙集在對外服務這塊,這樣的實現是否合理,或者是否有更好的實現方式呢?

先來講說這樣架構須要作的一些事兒以及存在的不足:

  • 首先,破壞了服務無狀態特色。爲了保證對外服務的安全性,咱們須要實現對服務訪問的權限控制,而開放服務的權限控制機制將會貫穿並污染整個開放服務的業務邏輯,這會帶來的最直接問題是,破壞了服務集羣中REST API無狀態的特色。從具體開發和測試的角度來講,在工做中除了要考慮實際的業務邏輯以外,還須要額外可續對接口訪問的控制處理。
  • 其次,沒法直接複用既有接口。當咱們須要對一個即有的集羣內訪問接口,實現外部服務訪問時,咱們不得不經過在原有接口上增長校驗邏輯,或增長一個代理調用來實現權限控制,沒法直接複用原有的接口。

面對相似上面的問題,咱們要如何解決呢?下面進入正題:服務網關!

爲了解決上面這些問題,咱們須要將權限控制這樣的東西從咱們的服務單元中抽離出去,而最適合這些邏輯的地方就是處於對外訪問最前端的地方,咱們須要一個更強大一些的均衡負載器,它就是本文未來介紹的:服務網關。

服務網關是微服務架構中一個不可或缺的部分。經過服務網關統一貫外系統提供REST API的過程當中,除了具有服務路由、均衡負載功能以外,它還具有了權限控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,爲微服務架構提供了前門保護的做用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集羣主體可以具有更高的可複用性和可測試性。

下面咱們經過實例例子來使用一下Zuul來做爲服務的路有功能。

準備工做

在構建服務網關以前,咱們先準備一下網關內部的微服務,咱們直接使用前幾篇編寫的內容:

  • eureka-server
  • user-provider
  • user-consumer

啓動以上3個服務,其中eureka-server和user-provider以集羣的方式啓動,此處再也不累述。

全部的準備工做就以就緒,下面咱們來試試使用Spring Cloud Zuul來實現服務網關的功能。

構建服務網關

**第一步:**使用Spring Cloud Zuul來構建服務網關的基礎步驟很是簡單,咱們能夠直接使用IDEA工具的Spring Initilizr嚮導創建,其餘步驟省略,僅展現選取zuul的截圖以下:

image_1c98786oo1paf17toc16g241274m.png-58kB

**第二步:**確保pom.xml中有關於zuul和eureka client的依賴,由於zuul自己也將做爲一個微服務註冊到服務註冊中心。

<!--ZUUL 依賴-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--eureka client依賴-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

**第三步:**修改啓動類,將啓動類定義爲spring cloud的啓動類,同時開啓zuul代理支持,即在啓動類中使用兩個註解:@SpringCloudApplication@EnableZuulProxy

**第四步:**修改配置文件application.yml,定義端口、應用名稱、eureka server註冊中心地址,內容以下:

spring:
  application:
    name: api-gateway
server:
  port: 10000
eureka:
  client:
    serviceUrl:
      defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/

到這裏,一個基於Spring Cloud Zuul服務網關就已經構建完畢。啓動該應用,一個默認的服務網關就構建完畢了。因爲Spring Cloud Zuul在整合了Eureka以後,具有默認的服務路由功能,即:當咱們這裏構建的api-gateway應用啓動並註冊到eureka以後,服務網關會發現上面咱們啓動的兩個服務user-provideruser-consumer,這時候Zuul就會建立兩個路由規則。每一個路由規則都包含兩部分,一部分是外部請求的匹配規則,另外一部分是路由的服務ID。針對當前示例的狀況,Zuul會建立下面的兩個路由規則:

  • 轉發到user-provider服務的請求規則爲:/user-provider/**
  • 轉發到user-consumer服務的請求規則爲:/user-consumer/**

最後,咱們能夠經過訪問10000端口的服務網關來驗證上述路由的正確性:

經過上面的構建內容,咱們已經爲全部內部服務提供了一個統一的對外入口,同時對於服務的路由都是自動建立了,減小了傳統方式大量的運維配置工做。

zuul配置

過濾器

經過前面的學習,咱們已經可以實現請求的路由功能,因此咱們的微服務應用提供的接口就能夠經過統一的API網關入口被客戶端訪問到了。可是,每一個客戶端用戶請求微服務應用提供的接口時,它們的訪問權限每每都須要有必定的限制,系統並不會將全部的微服務接口都對它們開放。然而,目前的服務路由並無限制權限這樣的功能,全部請求都會被毫無保留地轉發到具體的應用並返回結果,爲了實現對客戶端請求的安全校驗和權限控制,最簡單和粗暴的方法就是爲每一個微服務應用都實現一套用於校驗簽名和鑑別權限的過濾器或攔截器。不過,這樣的作法並不可取,它會增長往後的系統維護難度,由於同一個系統中的各類校驗邏輯不少狀況下都是大體相同或相似的,這樣的實現方式會使得類似的校驗邏輯代碼被分散到了各個微服務中去,冗餘代碼的出現是咱們不但願看到的。因此,比較好的作法是將這些校驗邏輯剝離出去,構建出一個獨立的鑑權服務。

在完成了剝離以後,有很多開發者會直接在微服務應用中經過調用鑑權服務來實現校驗,可是這樣的作法僅僅只是解決了鑑權邏輯的分離,並無在本質上將這部分不屬於業餘的邏輯拆分出原有的微服務應用,冗餘的攔截器或過濾器依然會存在。

對於這樣的問題,更好的作法是經過前置的網關服務來完成這些非業務性質的校驗。因爲網關服務的加入,外部客戶端訪問咱們的系統已經有了統一入口,既然這些校驗與具體業務無關,那何不在請求到達的時候就完成校驗和過濾,而不是轉發後再過濾而致使更長的請求延遲。同時,經過在網關中完成校驗和過濾,微服務應用端就能夠去除各類複雜的過濾器和攔截器了,這使得微服務應用的接口開發和測試複雜度也獲得了相應的下降。

爲了在API網關中實現對客戶端請求的校驗,咱們將須要使用到Spring Cloud Zuul的另一個核心功能:過濾器

Zuul容許開發者在API網關上經過定義過濾器來實現對請求的攔截與過濾,實現的方法很是簡單,咱們只須要繼承ZuulFilter抽象類並實現它定義的四個抽象函數就能夠完成對請求的攔截和過濾了。

過濾器的實現

好比下面的代碼,咱們定義了一個簡單的Zuul過濾器,它實現了在請求被路由以前檢查HttpServletRequest的請求頭中是否有accessToken參數,如有就進行路由,若沒有就拒絕訪問,返回401 Unauthorized錯誤。

@Component
public class AccessFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "pre";
    }
    @Override
    public int filterOrder() {
        return 0;
    }
    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        String token = request.getHeader("accessToken");
        if(StringUtils.isBlank(token)){
            //令zuul過濾該請求,不對其進行路由
            ctx.setSendZuulResponse(false);
            //設置了其返回的錯誤碼
            ctx.setResponseStatusCode(HttpServletResponse.SC_UNAUTHORIZED);
        }

        return null;
    }
}

在上面實現的過濾器代碼中,咱們經過繼承ZuulFilter抽象類並重寫了下面的四個方法來實現自定義的過濾器。這四個方法分別定義了:

  • filterType:過濾器的類型,它決定過濾器在請求的哪一個生命週期中執行。該函數須要返回一個字符串來表明過濾器的類型,而這個類型就是在HTTP請求過程當中定義的各個階段。在Zuul中默認定義了四種不一樣生命週期的過濾器類型,具體以下:
    • pre:能夠在請求被路由以前調用。
    • routing:在路由請求時候被調用。
    • post:在routing和error過濾器以後被調用。
    • error:處理請求時發生錯誤時被調用。
  • filterOrder:過濾器的執行順序。當請求在一個階段中存在多個過濾器時,須要根據該方法返回的值來依次執行。經過int值來定義過濾器的執行順序,數值越小優先級越高。
  • shouldFilter:判斷該過濾器是否須要被執行。這裏咱們直接返回了true,所以該過濾器對全部請求都會生效。實際運用中咱們能夠利用該函數來指定過濾器的有效範圍。
  • run:過濾器的具體邏輯。這裏咱們經過ctx.setSendZuulResponse(false)令zuul過濾該請求,不對其進行路由,而後經過ctx.setResponseStatusCode(401)設置了其返回的錯誤碼,固然咱們也能夠進一步優化咱們的返回,好比,經過ctx.setResponseBody(body)對返回body內容進行編輯等。

在實現了自定義過濾器以後,它並不會直接生效,咱們還須要爲其建立具體的Bean才能啓動該過濾器,此處直接在類上增長了註解@Component

在對api-gateway服務完成了上面的改造以後,從新啓動它,併發起下面的請求,對上面定義的過濾器作一個驗證:

利用postMan構建一個url爲http://localhost:10000/user-consumer/call的get請求,不帶請求頭accessToken時的結果以下圖,可見返回了401錯誤。 image_1c98dndneb72it37j3dapikp13.png-27kB

接下來請求頭中帶上accessToken的結果以下圖,可見正確的路由到了user-consume的/call接口,並返回告終果。

image_1c98dq4u9vkd3061sb4mljsu11g.png-31.4kB

到這裏,對於Spring Cloud Zuul過濾器的基本功能就以介紹完畢。能夠根據本身的須要在服務網關上定義一些與業務無關的通用邏輯實現對請求的過濾和攔截,好比:簽名校驗、權限校驗、請求限流等功能。


消息總線-spring cloud bus

在微服務架構的系統中, 咱們一般會使用輕量級的消息代理來構建一個共用的消息主題讓系統中全部微服務實例都鏈接上來, 因爲該主題中產生的消息會被全部實例監聽和消費, 因此咱們稱它爲消息總線。在總線上的各個實例均可以方便地廣播一些須要讓其餘鏈接在該主題上的實例都知道的消息, 例如配置信息的變動或者其餘一些管理操做等。

因爲消息總線在微服務架構系統中被普遍使用, 因此它同配置中心同樣, 幾乎是微服務架構中的必備組件。Spring Cloud 做爲微服務架構綜合性的解決方案,對此天然也有本身的實現, 這就是本章咱們將要具體介紹的Spring Cloud Bus。經過使用Spring Cloud Bus,能夠很是容易地搭建起消息總線,同時實現了一些消息總線中的經常使用功能,好比,配合Spring Cloud Config 實現微服務應用配置信息的動態更新等。

在本章中, 咱們將從消息代理的基礎開始, 由淺入深地介紹如何使用Spring Cloud Bus構建微服務架構中的消息總線。

消息代理

消息代理(Message Broker) 是一種消息驗證、傳輸、路由的架構模式。它在應用程序之間起到通訊調度並最小化應用之間的依賴的做用, 使得應用程序能夠高效地解耦通訊過程。消息代理是一箇中間件產品, 它的核心是一個消息的路由程序, 用來實現接收和分發消息,並根據設定好的消息處理流來轉發給正確的應用。它包括獨立的通訊和消息傳遞協議, 可以實現組織內部和組織間的網絡通訊。設計代理的目的就是爲了可以從應用程序中傳入消息, 並執行一些特別的操做,下面這些是在企業應用中, 咱們常常須要使用消息代理的場景:

  • 將消息路由到一個或多個目的地。
  • 消息轉化爲其餘的表現方式。
  • 執行消息的彙集、消息的分解, 並將結果發送到它們的目的地, 而後從新組合響應 返回給消息用戶。
  • 調用Web服務來檢索數據。
  • 響應事件或錯誤。
  • 使用發佈-訂閱模式來提供內容或基於主題的消息路由。

目前已經有很是多的開源產品能夠供你們使用, 好比:

  • ActiveMQ
  • Kafka
  • RabbitMQ
  • RocketMQ
  • ...

當前版本的Spring Cloud Bus僅支待兩款中間件產品: RabbitMQ和Kafka。在下面的章節中, 咱們將介紹如何使用RabbitMQ與Spring Cloud Bus配合實現消息總線。

RabbitMQ實現消息總線

RabbitMQ是實現了高級消息隊列協議CAMQP)的開源消息代理軟件, 也稱爲面向消息的中間件。RabbitMQ服務器是用高性能、可伸縮而聞名的Erlang語言編寫而成的, 其 集羣和故障轉移是構建在開放電信平臺框架上的。

AMQP是Advanced Message Queuing Protocol的簡稱,它是一個面向消息中間件的開放式標準應用層協議。它定義瞭如下這些特性:

  • 消息方向
  • 消息隊列
  • 消息路由(包括點到點和發佈-訂閱模式)
  • 可靠性
  • 安全性

AMQP要求消息的提供者和客戶端接收者的行爲要實現對不一樣供應商能夠用相同的方式(好比SMTP、HTTP、FTP等)進行互相操做。在以往的中間件標準中, 主要仍是創建在API級別, 好比JMS, 集中於經過不一樣的中間件實現來創建標準化的程序間的互操做性, 而不是在多箇中間件產品間實現互操做性。

AMQP與JMS不一樣,JMS定義了一個API和一組消息收發必須實現的行爲,而AMQP是一個線路級協議。線路級協議描述的是經過網絡發送的數據傳輸格式。所以,任何符合該數據格式的消息發送和接收工具都能互相兼容和進行操做,這樣就能輕易實現跨技木平臺的架構方案。

RabbitMQ以AMQP協議實現, 因此它能夠支持多種操做系統、多種編程語言, 幾乎能夠覆蓋全部主流的企業級技術平臺。在微服務架構消息中間件的選型中, 它是一個很是適合且優秀的選擇。 所以, 在SpringCloudB us中包含了對Rabbit的自動化默認配置, 在下面的章節中, 咱們將先從RabbitMQ的基礎安裝和使用開始, 按部就班地學習如何與SprinCg loudB us進行整合實現消息總線。

RabbitMQ基本概念

在開始具體實踐以前, 咱們先介紹一些關於RabbitMQ的基本概念,.

  • Broker: 能夠理解爲消息隊列服務器的實體, 它是一箇中間件應用, 負責接收消息生產者的消息, 而後將消息發送至消息接收者或者其餘的Broker
  • Exchange: 消息交換機, 是消息第一個到達的地方, 消息經過它指定的路由規則,分發到不一樣的消息隊列中去。
  • Queue: 消息隊列, 消息經過發送和路由以後最終到達的地方, 到達Queue的消息即進入邏輯上等待消費的狀態。每一個消息都會被髮送到一個或多個隊列。
  • Binding: 綁定, 它的做用就是把ExchangeQueue按照路由規則綁定起來, 也就是ExchangeQueue之間的虛擬鏈接。
  • Routing Key: 路由關鍵字,Exchange根據這個關鍵字進行消息投遞。
  • Virtual host: 虛擬主機, 它是對Broker的虛擬劃分, 將消費者、生產者和它們依賴的AMQP相關結構進行隔離,通常都是爲了安全考虛。好比,咱們能夠在一個Broker中設置多個虛擬主機, 對不一樣用戶進行權限的分離。
  • Connection: 鏈接, 表明生產者、消費者、Broker之間進行通訊的物理網絡。
  • Channel: 消息通道,用千鏈接生產者和消費者的邏輯結構。在客戶端的每一個鏈接裏,可創建多個Channel, 每一個Channel表明一個會話任務, 經過Channel能夠隔離同一鏈接中的不一樣交互內容。
  • Producer: 消息生產者, 製造消息併發送消息的程序。
  • Consumer: 消息消費者, 接收消息並處理消息的程序。

消息投遞到隊列的整個過程大體以下:

  1. 客戶端鏈接到消息隊列服務器, 打開一個Channel
  2. 客戶端聲明一個Exchange, 並設置相關屬性。
  3. 客戶端聲明一個Queue, 並設置相關屬性。
  4. 客戶端使用Routing Key, 在ExchangeQueue之間創建好綁定關係。
  5. 客戶端投遞消息到Exchange
  6. Exchange接收到消息後,根據消息的Key和已經設置的Binding,進行消息路由,將消息投遞到一個或多個Queue裏。

Exchange也有幾種類型。

  1. Direct交換機:徹底根據Key進行投遞。好比,綁定時設置了Routing Key爲abc,那麼客戶端提交的消息,只有設置了Key爲abc 的纔會被投遞到隊列。
  2. Topic交換機:對Key進行模式匹配後進行投遞,能夠使用符號#匹配一個或多個詞,符號*匹配正好一個詞。好比,abc.#匹配abc.def.ghi,abc.*只匹配abc.def 。
  3. Fanout交換機:不須要任何Key,它採起廣播的模式,一個消息進來時,投遞到與該交換機綁定的全部隊列。

RabbitMQ支持消息的待久化,也就是將數據寫在磁盤上。爲了數據安全考慮,大多數狀況下都會選擇持久化。消息隊列持久化包括3個部分:

  1. Exchange 持久化,在聲明時指定durable => 1
  2. Queue 待久化,在聲明時指定durable => 1
  3. 消息持久化,在投遞時指定delivery_mode => 2 (1是非持久化)。

若是Exchange和Queue都是持久化的,那麼它們之間的Binding也是持久化的。若是Exchange和Queue二者之間有一個是待久化的,一個是非持久化的,就不容許創建綁定。

windows下安裝RabbitMQ

因爲Rabbit MQ 是創建在強大的Erlang OTP平臺上,所以咱們須要先安裝Erlang,而後在安裝RabbitMQ.

  1. 安裝Erlang, 經過官方下載頁面http://www.erlang.org/downloads獲取exe安裝包, 直接打開並完成安裝。
  2. 安裝RabbitMQ,經過官方下載頁面https://www.rabbitmq.com/download.html獲取exe安裝包。
  3. 下載完成後, 直接運行安裝程序。
  4. RabbitMQServer安裝完成以後,會自動註冊爲服務, 並以默認配置進行啓動。 image_1c9o4lufh15pr166k1j5i47lvsn9.png-8.4kB

在Windows的安裝過程當中, 有時候會碰到服務啓動失敗的狀況, 一般都是因爲windows用戶名爲中文, 致使默認的db和log目錄訪問出現問題。要解決該問題, 須要先卸載RabbitMQ Server, 而後設置環境變量RABBITMQ BASE 爲一個不含中文的路徑, 好比E:\server\rabbitmq。最後, 從新安裝RabbitMQ便可。

Rabbit管理

咱們能夠直接經過訪問配置文件進行管理, 也能夠經過訪問Web進行管理。下面將介紹如何經過Web進行管理。 在命令行執行rabbitmq-plugins enable rabbitmq management 命令, 開啓Web管理插件, 這樣就能夠經過瀏覽器來進行管理了。

打開瀏覽器並訪問http://localhost:15672/, 並使用默認用戶guest登陸,密碼也爲guest。能夠看到以下圖所示的管理頁面:

image_1c9o4u3ek5md7o21p911i92fgj16.png-121.4kB

從圖中咱們能夠看到以前提到的一些基本概念, 好比Connections、Channels、Exchanges、Queues 等。能夠點開各項看看都有些什麼內容, 熟悉一下RabbitMQ Server 的服務端。

  • 單擊Admin 選項卡, 以下圖所示, 能夠嘗試建立一個名爲dengcl的用戶。

image_1c9o51uff1npp1sae11qrjbt17nj1j.png-95.2kB

其中, Tags 標籤是RabbitMQ 中的角色分類, 共有下面幾種。

  • none: 不能訪問management plugin。
  • management: 用戶能夠經過AMQP 作的任何事外加以下內容。
    • 列出本身能夠經過AMQP 登入的virtual hosts。
    • 查看本身的virtual hosts 中的queues、exchanges 和bindings。
    • 查看和關閉本身的channels 和connections。
    • 查看有關本身的virtual hosts 的「 全局」 統計信息, 包含其餘用戶在這些virtual hosts 中的活動。
  • policymaker: management 能夠作的任何事外加以下內容。
    • 查看、建立和刪除本身的virtual hosts 所屬的policies 和parameters。
  • monitoring: management 能夠作的任何事外加以下內容。
    • 列出全部virtual hosts, 包括它們不能登陸的virtual hosts。
    • 查看其餘用戶的connections 和channels。
    • 查看節點級別的數據, 如clustering 和memory 的使用狀況。
    • 查看真正的關於全部virtual hosts的全局的統計信息。
  • administrator: policymaker和monitoring能夠作的任何事外加以下內容。
    • 建立和刪除virtual hosts。
    • 查看、建立和刪除users。
    • 查看、建立和刪除permissions。
    • 關閉其餘用戶的connections。

快速入門

接下來,咱們經過在Spring Boot應用中整合RabbitMQ, 實現一個簡單的發送、接收消息的例子來對RabbitMQ有一個直觀的感覺和理解。

在SpringBoot中整合RabbitMQ是一件很是容易的事,由於以前咱們已經介紹過Starter POMs, 其中的AMQP模塊就能夠很好地支持RabbitMQ, 下面咱們就來詳細說說整合過程。

建立生產者

  • 新建一個SpringBoot工程, 命名爲rabbitmq-sender
  • pom.xml中引入以下依賴內容, 其中spring-boot-starter-amqp用於支持RabbitMQ。
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • application.properties中配置關於RabbitMQ的鏈接和用戶信息,這裏使用以前安裝時建立的dengcl
#服務名稱
spring.application.name=rabbitmq-sender

###############################
#### RabbitMQ服務器相關配置#####
###############################

#rabbitmq server地址,默認localhost
spring.rabbitmq.host=localhost
#rabbitmq server端口,默認5672
spring.rabbitmq.port=5672
#rabbitmq server用戶名,默認guest
spring.rabbitmq.username=dengcl
#rabbitmq server密碼,默認guest
spring.rabbitmq.password=123456
  • 建立消息生產者Sender。經過注入AmqpTemplate接口的實例來實現消息的發送AmqpTemplate 接口定義了一套針對AMQP 協議的基礎操做。在Spring Boot中會根據配置來注入其具體實現。在該生產者中,咱們會產生一個字符串, 併發送到名爲hello的隊列中。
@Slf4j  ///
@Component
public class Sender {

    //隊列名稱
    public static final String QUEUE_NAME="hello";

    @Autowired
    private AmqpTemplate amqpTemplate;

    public void sendMsg(String msg){
        log.info("準備發送消息到RabbitMQ Server.消息是{}",msg);
        amqpTemplate.convertAndSend(QUEUE_NAME,msg);
    }

}
  • 建立單元測試類, 用來調用消息生產。
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqSenderApplicationTests {

	public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	@Autowired
	private Sender sender;

	@Test
	public void testSend(){
		sender.sendMsg("我發送了一個消息,如今的時間是:"+sdf.format(new Date()));
	}
}

建立消費者

  • 新建一個SpringBoot工程, 命名爲rabbitmq-receiver
  • pom.xml中引入以下依賴內容, 其中spring-boot-starter-amqp用於支持RabbitMQ。
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • application.properties中配置關於RabbitMQ的鏈接和用戶信息,這裏使用以前安裝時建立的dengcl
#服務名稱
spring.application.name=rabbitmq-receiver

###############################
#### RabbitMQ服務器相關配置#####
###############################

#rabbitmq server地址,默認localhost
spring.rabbitmq.host=localhost
#rabbitmq server端口,默認5672
spring.rabbitmq.port=5672
#rabbitmq server用戶名,默認guest
spring.rabbitmq.username=dengcl
#rabbitmq server密碼,默認guest
spring.rabbitmq.password=123456

以上步驟和建立消息發佈者一致。

  • 建立消息消費者Receiver。經過@RabbitListener 註解定義該類對hello隊列的監聽, 並用@Rabb江Handler 註解來指定對消息的處理方法。因此,該消費者實現了對hello隊列的消費, 消費操做爲輸出消息的字符串內容。
@Slf4j
@Component
@RabbitListener(queues = "hello")
public class Receiver {

    @RabbitHandler
    public void process(String msg){
        log.info("接收到RabbitMQ中的queue爲hello的消息,消息的內容是:{}",msg);
    }
}

測試

  1. 經過啓動類運行rabbitmq-receiver;從控制檯中,咱們可看到以下內容,程序建立了一個訪問127.0.0.1:5672中dengcl的鏈接。

    2018-03-29 14:40:42.225  INFO 11444 --- [cTaskExecutor-1] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#13d73fa:0/SimpleConnection@6ddac9a1 [delegate=amqp://dengcl@127.0.0.1:5672/, localPort= 61925]

    同時,咱們經過RabbitMQ 的控制面板,能夠看到Connections 和Channels中包含當前鏈接的條目。 image_1c9o859501ka718pukhn12kb182o9.png-40.7kB image_1c9o85rfeec617d3192bhcn1942m.png-43.5kB

  2. 運行rabbitmq-sender的測試類。咱們能夠在控制檯中看到下面的輸出內容, 消息被髮送到了RabbitMQ Server 的hello 隊列中。

    2018-03-29 14:41:02.266  INFO 16316 --- [           main] com.dengcl.rabbitmqsender.mq.Sender      : 準備發送消息到RabbitMQ Server.消息是我發送了一個消息,如今的時間是:2018-03-29 14:41:02
  3. 切換到rabbitmq-receiver應用主類的控制檯,咱們能夠看到相似以下的輸出,消費者對hello 隊列的監聽程序執行了, 並輸出了接收到的消息信息。

    2018-03-29 14:41:02.424  INFO 11444 --- [cTaskExecutor-1] com.dengcl.rabbitmqreceiver.mq.Receiver  : 接收到RabbitMQ中的queue爲hello的消息,消息的內容是:我發送了一個消息,如今的時間是:2018-03-29 14:41:02

經過上面的示例,咱們在Spring Boot 應用中引入spring-boot-starter-amqp模塊, 進行簡單配置就完成了對RabbitMQ 的消息生產和消費的開發內容。然而在實際應用中, 還有不少內容沒有演示, 好比以前提到的一些概念: 交換機、路由關鍵字、綁定、虛擬主機等, 這裏不作更多的講解, 你們能夠自行查閱RabbitMQ 的官方教程, 其中有更全面的講解。 咱們須要重點理解的是, 在整個生產消費過程當中, 生產和消費是一個異步操做,這也是在分佈式系統中要使用消息代理的重要緣由,以此咱們能夠使用通訊來解耦業務邏輯。在這個例子中, 能夠進一步作一些測試, 好比,不運行消費者,先運行生產者, 此時能夠看到在RabbitMQServer管理頁面的Queues選項卡下多了一些待處理的消息, 這時咱們再啓動消費者, 它就會處理這些消息, 因此經過生產消費模式的異步操做, 系統間調用就沒有同步調用須要那麼高的實時性要求, 同時也更容易控制處理的吞吐量以保證系統的正常運行等。

整合spring cloud bus

在上一節中, 咱們已經介紹了關於消息代理、AMQP以及RabbitMQ的基礎知識和使用方法,而且在spring boot中應用。在下面的內容中, 咱們開始具體介紹SpringCloud Bus的配置。

服務發現中心

直接使用前面章節的eureka-server做爲服務發現中心。並啓動服務發現中心。

消息生產者

  1. 建立消息生產者微服務項目microservice-msg-sender,並添加相關依賴。依賴信息以下:

    <!--eureka 客戶端依賴-->
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    <!--spring bus rabbitmq依賴-->
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
    <!--爲了使用@Slf4j註解-->
    <dependency>
    	<groupId>org.projectlombok</groupId>
    	<artifactId>lombok</artifactId>
    </dependency>
    
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-test</artifactId>
    	<scope>test</scope>
    </dependency>
  2. 主類添加@EnableDiscoveryClient註解

  3. 配置application.properties,固然你也能夠用application.yml,內容以下

    #應用端口號
    server.port=3000
    #服務名稱
    spring.application.name=microservice-msg-sender
    #註冊到服務註冊中心
    eureka.client.service-url.defaultZone=http://localhost:1000/eureka/
    
    ###############################
    #### RabbitMQ服務器相關配置#####
    ###############################
    
    #rabbitmq server地址,默認localhost
    spring.rabbitmq.host=localhost
    #rabbitmq server端口,默認5672
    spring.rabbitmq.port=5672
    #rabbitmq server用戶名,默認guest
    spring.rabbitmq.username=dengcl
    #rabbitmq server密碼,默認guest
    spring.rabbitmq.password=123456
    
    ###綁定spring cloud bus的rabbitmq消息通道的exchange名稱
    spring.cloud.stream.bindings.rabbitmq_channel_output.destination=exchangeName
  4. 建立spring cloud bus的通道配置接口RabbitSendChannel,內容以下:

    public interface RabbitSendChannel {
    
        String rabbitmqChannelName = "rabbitmq_channel_output";
    
        @Output(rabbitmqChannelName)
        MessageChannel output();
    }

    特別說明: 接口中的rabbitmqChannelName的值和application.properties中定義的spring.cloud.stream.bindings.rabbitmq_channel_output.destination=exchangeNamerabbitmq_channel_output內容必須一致。

  5. 建立消息生產業務類RabbitMQSendService並經過@EnableBinding註解綁定通道接口。

    @Slf4j
    @EnableBinding(RabbitSendChannel.class)
    public class RabbitMQSendService {
    
        @Autowired
        private RabbitSendChannel rabbitSendChannel;
        //發送消息的業務
        public boolean sendMsg(String msg){
            log.info("準備發送消息到rabbitmq server,消息內容是:{}",msg);
            return rabbitSendChannel.output().send(MessageBuilder.withPayload(msg).build());
        }
    
    }
  6. 建立一個定時任務類SendMsgTask,定時發送消息,固然在實際應用中具體發送消息的事件須要根據業務來定義。

    @Slf4j
    @Component
    public class SendMsgTask {
        private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @Autowired
        private RabbitMQSendService sendService;
    
        //每隔5秒發送一條消息
        @Scheduled(fixedDelay = 5000L)
        public void process(){
            String msg = String.format("我是消息的生產者,我在[%s]生成了一條信息。",sdf.format(new Date()));
            log.info("SEND :{}",msg);
            sendService.sendMsg(msg);
        }
    
    }

    注意 應用主類中要增長註解@EnableScheduling來開啓定時任務支持。

  7. 啓動應用主類,能夠在控制檯上看到如下信息:

    2018-03-29 15:41:38.585  INFO 16868 --- [ask-scheduler-4] c.d.m.mq.RabbitMQSendService             : 準備發送消息到rabbitmq server,消息內容是:我是消息的生產者,我在[2018-03-29 15:41:38]生成了一條信息。
    2018-03-29 15:41:43.587  INFO 16868 --- [ask-scheduler-4] c.d.m.task.SendMsgTask                   : SEND :我是消息的生產者,我在[2018-03-29 15:41:43]生成了一條信息。
    2018-03-29 15:41:43.587  INFO 16868 --- [ask-scheduler-4] c.d.m.mq.RabbitMQSendService             : 準備發送消息到rabbitmq server,消息內容是:我是消息的生產者,我在[2018-03-29 15:41:43]生成了一條信息。

此時在rabbitmq的控制面板能夠看到:

![image_1c9ocl9odqu42qtrcs1h17lqn4j.png-119.5kB][31]

消息消費者

  1. 建立消息消費者微服務項目microservice-msg-receiver,並添加相關依賴。依賴信息以下:

    <!--eureka 客戶端依賴-->
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    <!--spring bus rabbitmq依賴-->
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
    <!--爲了使用@Slf4j註解-->
    <dependency>
    	<groupId>org.projectlombok</groupId>
    	<artifactId>lombok</artifactId>
    </dependency>
    
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-test</artifactId>
    	<scope>test</scope>
    </dependency>
  2. 主類添加@EnableDiscoveryClient註解

  3. 配置application.properties,固然你也能夠用application.yml,內容以下

    server.port=4000
    #服務名稱
    spring.application.name=microservice-msg-reveciver
    #註冊到服務註冊中心
    eureka.client.service-url.defaultZone=http://localhost:1000/eureka/
    
    ###############################
    #### RabbitMQ服務器相關配置#####
    ###############################
    
    #rabbitmq server地址,默認localhost
    spring.rabbitmq.host=localhost
    #rabbitmq server端口,默認5672
    spring.rabbitmq.port=5672
    #rabbitmq server用戶名,默認guest
    spring.rabbitmq.username=dengcl
    #rabbitmq server密碼,默認guest
    spring.rabbitmq.password=123456
    
    ###綁定spring cloud bus的rabbitmq消息通道的exchange名稱
    spring.cloud.stream.bindings.rabbitmq_channel_input.destination=exchangeName
  4. 建立spring cloud bus的通道配置接口RabbitReceiverChannel,內容以下:

    public interface RabbitMQReciveChannel {
    
        String rabbitmqChannelName = "rabbitmq_channel_input";
    
        @Input(rabbitmqChannelName)
        SubscribableChannel input();
    }

    特別說明:

    1. 接口中的rabbitmqChannelName的值和application.properties中定義的spring.cloud.stream.bindings.rabbitmq_channel_input.destination=exchangeNamerabbitmq_channel_input內容必須一致。
    2. 與消息生產者的rabbitmqChannelName的值不能同樣。
  5. 建立消息生產業務類RabbitMQSendService並經過@EnableBinding註解綁定通道接口。而且提供一個方法,經過@StreamListener註解監聽指定通道的新的消息。

    @Slf4j
    @EnableBinding(RabbitMQReciveChannel.class)
    public class RabbitMQReciveService {
    
        @Autowired
        private RabbitMQReciveChannel reciveChannel;
    
        @StreamListener(RabbitMQReciveChannel.rabbitmqChannelName)
        public void reciveMsg(Message<String> msg){
            log.info("RECEIVER:{}",msg.getPayload());
        }
    
    }

    測試結果

    1. 啓動服務註冊中心
    2. 啓動消息消費者
    3. 啓動消息生產者
    4. 查看結果。

其餘說明

在這個示例中,僅僅是展現了spring cloud bus如何整合rabbitmq,更多關於rabbitmq的使用請參考rabbitmq的官方網站。

分佈式配置中心

Spring Cloud Config是Spring Cloud團隊建立的一個全新項目,用來爲分佈式系統中的基礎設施和微服務應用提供集中化的外部配置支持,它分爲服務端與客戶端兩個部分。其中服務端也稱爲分佈式配置中心,它是一個獨立的微服務應用,用來鏈接配置倉庫併爲客戶端提供獲取配置信息、加密/解密信息等訪問接口;而客戶端則是微服務架構中的各個微服務應用或基礎設施,它們經過指定的配置中心來管理應用資源與業務相關的配置內容,並在啓動的時候從配置中心獲取和加載配置信息。Spring Cloud Config實現了對服務端和客戶端中環境變量和屬性配置的抽象映射,因此它除了適用於Spring構建的應用程序以外,也能夠在任何其餘語言運行的應用程序中使用。因爲Spring Cloud Config實現的配置中心默認採用Git來存儲配置信息,因此使用Spring Cloud Config構建的配置服務器,自然就支持對微服務應用配置信息的版本管理,而且能夠經過Git客戶端工具來方便的管理和訪問配置內容。固然它也提供了對其餘存儲方式的支持,好比:SVN倉庫、本地化文件系統。

快速入門

在本文中,咱們將學習如何構建一個基於Git存儲的分佈式配置中心,並對客戶端進行改造,並讓其可以從配置中心獲取配置信息並綁定到代碼中的整個過程

準備配置倉庫

準備一個git倉庫,能夠在碼雲或Github上建立均可以。倉庫示例:https://gitee.com/dengcl/spring-cloud-config-server/

構建配置中心

經過Spring Cloud Config 構建一個分佈式配置中心很是簡單, 只須要如下三步:

  • 建立一個基礎的Spring Boot 工程, 命名爲config-server, 並在pom.xml 中引

入下面的依賴:

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-config-server</artifactId>
</dependency>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

<repositories>
	<repository>
		<id>spring-milestones</id>
		<name>Spring Milestones</name>
		<url>https://repo.spring.io/milestone</url>
		<snapshots>
			<enabled>false</enabled>
		</snapshots>
	</repository>
</repositories>
  • 建立Spring Boot 的程序主類, 並添加@EnableConfigServer 註解, 開啓SpringCloud Config 的服務端功能。
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {

   public static void main(String[] args) {
      SpringApplication.run(ConfigServerApplication.class, args);
   }
}
  • 在`application.properties中添加配置服務的基本信息以及Git 倉庫的相關信息, 以下所示:

    spring.cloud.config.server.git.uri=https://gitee.com/dengcl/spring-cloud-config-server
    spring.cloud.config.server.git.username=dengcl
    spring.cloud.config.server.git.password=dcl745120
    
    spring.application.name=config-server
    
    server.port=7000

其中Git 的配置信息分別表示以下內容。

  • spring.cloud.config.server.git.uri: 配置Git 倉庫位置。
  • spring.cloud.config.server.git.searchPaths: 配置倉庫路徑下的相對搜索位置, 能夠配置多個。
  • spring.cloud.config.server.git.username: 訪問Git 倉庫的用戶名。
  • spring.cloud.config.server.git.password: 訪問Git 倉庫的用戶密碼。

到這裏, 使用一個經過Spring Cloud Config實現, 並使用Git 管理配置內容的分佈式配置中心就完成了。咱們能夠將該應用先啓動起來, 確保沒有錯誤產生, 而後進入下面的學習內容。

配置規則詳解

爲了驗證上面完成的分佈式配置中心config-server, 根據Git 配置信息中指定的倉庫位置, 在https://gitee.com/dengcl/spring-cloud-config-server下建立了/config_repo目錄做爲配置倉庫, 並根據不一樣環境新建下面4個配置文件:

  • config-client.properties

  • config-client-dev.properties

  • config-client-test.properties

  • config-client-prod.properties

在這4個配置文件中均設置了一個username屬性, 併爲每一個配置文件分別設置了不一樣的值, 以下所示:

  • username=dengcl
  • username=dengcl_dev
  • username=dengcl_test
  • username=dengcl_prod

爲了測試版本控制,在該Git倉庫的master 分支中,咱們爲username屬性加入1.0 的後綴, 同時建立一個config-label-test 分支, 並將各配置文件中的值用2.0 做爲後綴。 完成了這些準備工做以後, 咱們就能夠經過瀏覽器、POSTMAN或CURL等工具直接來訪問咱們的配置內容了。訪問配置信息的URL與配置文件的映射關係以下所示:

  • /{application}/{profile}[/{label}]
  • /{application}-{profile}.yml
  • /{label}/{application}-{profile}.yml
  • /{application}-{profile}.properties
  • /{label}/{application}-{profile}.properties

上面的url會映射{application}-{profile}.properties對應的配置文件,其中{label}對應Git上不一樣的分支,默認爲master。咱們能夠嘗試構造不一樣的url來訪問不一樣的配置內容,好比,要訪問master分支,config-client應用的dev環境,就能夠訪問這個url:http://localhost:7000/config-client/dev/master,並得到以下返回:

{
    "name": "config-client",
    "profiles": [
        "dev"
    ],
    "label": "master",
    "version": "804509ea362b52de23ca06f627087301002aa6d0",
    "state": null,
    "propertySources": [
        {
            "name": "https://gitee.com/dengcl/spring-cloud-config-server/config_repo/config-client-dev.properties",
            "source": {
                "username": "dengcl_dev_1.0"
            }
        },
        {
            "name": "https://gitee.com/dengcl/spring-cloud-config-server/config_repo/config-client.properties",
            "source": {
                "username": "dengcl_1.0"
            }
        }
    ]
}

咱們能夠看到該Json中返回了應用名:config-client,環境名:dev,分支名:master,以及default環境和dev環境的配置內容。

同時, 咱們能夠看到config-server 的控制檯中還輸出了下面的內容,配置服務器在從Git 中獲取配置信息後, 會存儲一份在config-server 的文件系統中, 實質上config-server 是經過git clone 命令將配置內容複製了一份在本地存儲, 而後讀取這些內容並返回給微服務應用進行加載。

2018-03-30 11:05:00.663  INFO 4884 --- [nio-7000-exec-5] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7e88bbb: startup date [Fri Mar 30 11:05:00 CST 2018]; root of context hierarchy
2018-03-30 11:05:00.677  INFO 4884 --- [nio-7000-exec-5] o.s.c.c.s.e.NativeEnvironmentRepository  : Adding property source: file:/C:/Users/dengcl/AppData/Local/Temp/config-repo-8571982357257432495/config_repo/config-client-dev.properties
2018-03-30 11:05:00.677  INFO 4884 --- [nio-7000-exec-5] o.s.c.c.s.e.NativeEnvironmentRepository  : Adding property source: file:/C:/Users/dengcl/AppData/Local/Temp/config-repo-8571982357257432495/config_repo/config-client.properties
2018-03-30 11:05:00.677  INFO 4884 --- [nio-7000-exec-5] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7e88bbb: startup date [Fri Mar 30 11:05:00 CST 2018]; root of context hierarchy

config-server經過Git 在本地倉庫暫存,能夠有效防止當Git 倉庫出現故障而引發沒法加載配置信息的狀況。咱們能夠經過斷開網絡, 再次發起http://localhost:7000/config-client/test/master請求,能夠看到, config-server 提示沒法從遠程獲取該分支內容的報錯信息, 可是它依然會爲該請求返回配置內容, 這些內容源於以前訪問時存於config-server 本地文件系統中的配置內容。

構建客戶端

在完成了上述驗證以後,肯定配置服務中心已經正常運做,下面咱們嘗試如何在微服務應用中獲取上述的配置信息。

  • 建立一個Spring Boot應用,命名爲config-client,並在pom.xml中引入下述依賴:
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
  • 建立bootstrap.yml配置,來指定獲取配置文件的config-server-git位置,例如:
spring:
  application:
    name: config-client
  cloud:
    config:
      uri: http://localhost:7000/
      profile: default
      label: master

server:
  port: 8000

上述配置參數與Git中存儲的配置文件中各個部分的對應關係以下:

  • spring.application.name:對應配置文件規則中的{application}部分
  • spring.cloud.config.profile:對應配置文件規則中的{profile}部分
  • spring.cloud.config.label:對應配置文件規則中的{label}部分
  • spring.cloud.config.uri:配置中心config-server的地址

這裏須要格外注意:上面這些屬性必須配置在bootstrap.yml中,固然也能夠是bootstrap.properties,這樣config-server中的配置信息才能被正確加載。

在完成了上面的代碼編寫以後,將config-serverconfig-client都啓動起來,而後訪問http://localhost:2001/info ,咱們能夠看到該端點將會返回從git倉庫中獲取的配置信息:

  • 建立測試用例
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConfigClientApplicationTests {
   @Value("${username}")
   private String username;

   @Test
   public void test(){
      System.err.println(username);
   }
}
  • 運行測試用例,能夠看到正確的讀取到配置中心的用戶信息

配置中心的高可用

傳統做法

一般在生產環境,Config Server與服務註冊中心同樣,咱們也須要將其擴展爲高可用的集羣。在以前實現的config-server基礎上來實現高可用很是簡單,不須要咱們爲這些服務端作任何額外的配置,只須要遵照一個配置規則:將全部的Config Server都指向同一個Git倉庫,這樣全部的配置內容就經過統一的共享文件系統來維護,而客戶端在指定Config Server位置時,只要配置Config Server外的均衡負載便可,就像以下圖所示的結構:

mark

註冊爲服務

雖然經過服務端負載均衡已經可以實現,可是做爲架構內的配置管理,自己其實也是能夠看做架構中的一個微服務。因此,另一種方式更爲簡單的方法就是把config-server也註冊爲服務,這樣全部客戶端就能以服務的方式進行訪問。經過這種方法,只須要啓動多個指向同一Git倉庫位置的config-server就能實現高可用了。

首先啓動eureka實現的服務註冊中心應用server-center

接下來實現配置管理服務端和客戶端,配置過程很是簡單,具體以下:

config-server配置
  • pom.xmldependencies節點中引入以下依賴,相比以前的config-server就加入了spring-cloud-starter-netflix-eureka-client,用來註冊服務。
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • application.properties中配置參數eureka.client.serviceUrl.defaultZone以指定服務註冊中心的位置,詳細內容以下:
# 配置服務註冊中心
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
  • 在應用主類中,新增@EnableEurekaClient註解,用來將config-server註冊到上面配置的服務註冊中心上去。
@EnableEurekaClient
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {

   public static void main(String[] args) {
      SpringApplication.run(ConfigServerApplication.class, args);
   }
}
  • 啓動該應用,並訪問http://localhost:1111/,能夠在Eureka Server的信息面板中看到config-server已經被註冊了。
config-client配置
  • config-server同樣,在pom.xmldependencies節點中新增spring-cloud-starter-netflix-eureka-client依賴,用來註冊服務:
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • bootstrap.properties中,按以下配置:
spring.application.name=config-client
server.port=8000

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.serviceId=config-server
spring.cloud.config.profile=dev

其中,經過eureka.client.serviceUrl.defaultZone參數指定服務註冊中心,用於服務的註冊與發現,再將spring.cloud.config.discovery.enabled參數設置爲true,開啓經過服務來訪問Config Server的功能,最後利用spring.cloud.config.discovery.serviceId參數來指定Config Server註冊的服務名。這裏的spring.application.namespring.cloud.config.profile如以前經過URI的方式訪問時候同樣,用來定位Git中的資源。

  • 在應用主類中,增長@EnableEurekaClient註解,用來發現config-server服務,利用其來加載應用配置
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientApplication {

   public static void main(String[] args) {
      SpringApplication.run(ConfigClientApplication.class, args);
   }
}
  • 建立一個TestController,其代碼以下:
@RestController
public class TestController {


    @Value("${username}")
    private String userName;

    @GetMapping("/show")
    public String show(){
        return this.userName;
    }
}
  • 完成了上述配置以後,咱們啓動該客戶端應用。若啓動成功,訪問http://localhost:1111/,能夠在Eureka Server的信息面板中看到該應用已經被註冊成功了。

  • 訪問客戶端應用提供的服務:http://localhost:8000/show,此時,咱們會返回在Git倉庫中config-client.properties文件配置的username屬性內容dengcl_1.0

配置刷新

有時候,咱們須要對配置內容作一些實時更新的場景,那麼Spring Cloud Config是否能夠實現呢?答案顯然是能夠的。下面,咱們看看如何進行改造來實現配置內容的實時更新。

在改造程序以前,咱們先將config-serverconfig-client都啓動起來,並訪問客戶端提供的REST APIhttp://localhost:8000/show來獲取配置信息,能夠得到返回內容爲:dengcl_dev_1.0。接着,咱們能夠嘗試使用Git工具修改當前配置的內容,好比,將config-repo/config-client-dev.properties中的username的值從username=dengcl_dev_1.0修改成username=dengcl_dev_1.0,再訪問http://localhost:8000/show,能夠看到其返回內容仍是username=dengcl_dev_1.0

下面,咱們將在config-client端增長一些內容和操做以實現配置的刷新:

  • 在config-client的pom.xml中新增spring-boot-starter-actuator監控模塊,其中包含了/actuator/refresh刷新API。
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 在config-client的bootstrap.properties中開啓/actuator/refresh端點。
management.endpoints.web.exposure.include=refresh
  • 爲須要在/actuator/refresh時須要刷新的bean添加註解@RefreshScope,在本例中須要被刷新的是TestController,由於咱們須要在這裏面注入配置中心的屬性。
@RestController
@RefreshScope
public class TestController {


    @Value("${username}")
    private String userName;

    @GetMapping("/show")
    public String show(){
        return this.userName;
    }
}
  • 從新啓動config-client,訪問一次http://localhost:8000/show,能夠看到當前的配置值
  • 修改Git倉庫``config-repo/config-client-dev.properties文件中username`的值
  • 再次訪問一次http://localhost:8000/show,能夠看到配置值沒有改變
  • 經過POST請求發送到http://localhost:8000/actuator/refresh,咱們能夠看到返回內容以下,表明username參數的配置內容被更新了
[
    "config.client.version",
    "username"
]

除此以外,咱們還能夠經過git倉庫的web hook來功能進行關聯,當有Git提交變化時,就給對應的配置主機發送/actuator/refresh請求來實現配置信息的實時更新。可是,這種方式不只對網絡拓撲有需求,同時當咱們的系統發展壯大以後,維護這樣的刷新清單也將成爲一個很是大的負擔,並且很容易犯錯,那麼有什麼辦法能夠解決這個複雜度呢?其實能夠經過Spring Cloud Bus來實現以消息總線的方式進行通知配置信息的變化,完成集羣上的自動化更新。

基於消息中間件實現配置參數自動刷新

在上一節的示例中,雖然咱們已經可以經過/actuator/refresh接口和Git倉庫的Web Hook來實現Git倉庫中的內容修改觸發應用程序的屬性更新。可是, 若全部觸發操做均須要咱們手工去維護Web Hook中的應用配置的話, 隨着系統的不斷擴展, 會變得愈來愈難以維護, 而消息代理中間件是解決該問題最爲合適的方案。是否還記得咱們在介紹消息代理中的特色時提到過這樣一個功能: 消息代理中間件能夠將消息路由到一個或多個目的地。利用這個功能, 咱們就能完美地解決該問題, 下面來講說SpringCloud Bus中的具體實現方案。

下面咱們來具體動手嘗試整個配置過程。

  • 準備工做: 這裏咱們不建立新的應用, 但須要用到前面已經實現的關於SpringCloudConfig的幾個工程。

    • config-repo: 定義在Git倉庫中的一個目錄,其中存儲了應用名爲中config-client的多環境配置文件, 配置文件中有一個username參數。
    • eureka-server: 基於eureka實現的服務註冊中心。
    • config-server: 配置中心服務器,配置了Git倉庫, 並註冊到了Eureka的服務端。
    • config-client: 經過Eureka發現ConfigServer的客戶端, 應用名爲config-client, 用來訪問配置服務器以獲取配置信息。該應用中提供了一個/show接口, 它會獲取config-repo/config-client-dev.properties中的username屬性並返回。
  • 擴展config-client應用

    • 修改pom.xml,添加spring-cloud-starter-bus-amqp模塊依賴,注意spring-boot-starter-actuator也是必須的,用來添加提供刷新節點。
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> ```

    • 在配置文件中增長關千RabbitMQ的鏈接和用戶信息,同時打開bus-refresh端點。

      #配置rabbitmq鏈接信息
      spring.rabbitmq.host=localhost
      spring.rabbitmq.port=5672
      spring.rabbitmq.username=dengcl
      spring.rabbitmq.password=123456
      #打開/bus-refresh節點
      management.endpoints.web.exposure.include=bus-refresh
    * 啓動`config-server`, 再啓動兩個`config-client`分別在不一樣的端口上, 好比8000、8001)。咱們能夠在`config-client`中的控制檯中看到以下內容, 在啓動時, 客戶端程序多了一個`/actuator/bus-refresh`請求。

    2018-03-30 16:41:16.895 INFO 15376 --- [ main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/bus-refresh],methods=[POST]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web .servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)

    * 先訪問兩個`config-client` 的`/show`請求, 會返回當前`config-repo/config-client-dev.properties`中的`username`屬性。
    
    * 接着, 修改`config-repo/config-client-dev.properties`中的`username`屬性值,
    併發送`POST`請求到`config-client`中的一個`/actuator/bus-refresh`。
    
    * 最後, 再分別訪問啓動的兩個`config-client`的`/show`請求, 此時這兩個請求都會返回最新的`username`屬性。
    
    到這裏, 咱們已經可以經過SpringCloud Bus來實時更新總線上的屬性配置了。而且能夠有效的結合GIT倉庫的web hook實現配置信息的全自動更新。
    
    ​

分佈式服務跟蹤.

經過以前學習,實際上咱們已經可以經過使用它們搭建起一個基礎的微服務架構系統來實現咱們的業務需求了。可是,隨着業務的發展,咱們的系統規模也會變得愈來愈大,各微服務間的調用關係也變得愈來愈錯綜複雜。一般一個由客戶端發起的請求在後端系統中會通過多個不一樣的微服務調用來協同產生最後的請求結果,在複雜的微服務架構系統中,幾乎每個前端請求都會造成一條複雜的分佈式服務調用鏈路,在每條鏈路中任何一個依賴服務出現延遲太高或錯誤的時候都有可能引發請求最後的失敗。這時候對於每一個請求全鏈路調用的跟蹤就變得愈來愈重要,經過實現對請求調用的跟蹤能夠幫助咱們快速的發現錯誤根源以及監控分析每條請求鏈路上的性能瓶頸等好處。

針對上面所述的分佈式服務跟蹤問題,Spring Cloud Sleuth提供了一套完整的解決方案。在本章中,咱們將詳細介紹如何使用Spring Cloud Sleuth來爲咱們的微服務架構增長分佈式服務跟蹤的能力。

[]: http://static.zybuluo.com/dengcl/9y5h5ldcgga4ovtpf59fib9p/image_1c9ocl9odqu42qtrcs1h17lqn4j.png

相關文章
相關標籤/搜索