javascript
【檀越剑指大厂—SpringCloudNetflix】SpringCloudNetflix高阶篇
一.基礎(chǔ)概念
1.架構(gòu)演進
在系統(tǒng)架構(gòu)與設(shè)計的實踐中,從宏觀上可以總結(jié)為三個階段;
單體架構(gòu) :就是把所有的功能、模塊都集中到一個項目中,部署在一臺服務(wù)器上,從而對外提供服務(wù)(單體架構(gòu)、單體服務(wù)、單體應(yīng)用)。直白一點:就是只有一個項目,只有一個 war;
分布式架構(gòu) :就是把所有的功能、模塊拆分成不同的子項目,部署在多臺不同的服務(wù)器上,這些子項目相互協(xié)作共同對外提供服務(wù)。直白一點:就是有很多項目,有很多 wr 包,這些項目相互協(xié)作完成需要的功能,不是一個 war 能完成的,一個 war 包完成不了;
微服務(wù)架構(gòu) :分布式強調(diào)系統(tǒng)的拆分,微服務(wù)也是強調(diào)系統(tǒng)的拆分,微服務(wù)架構(gòu)屬于分布式架構(gòu)的范疇;
并且到目前為止,微服務(wù)并沒有一個統(tǒng)一的標準的定義
2.分布式和微服務(wù)區(qū)別?
分布式,就是將巨大的一個系統(tǒng)劃分為多個模塊,這一點和微服務(wù)是一樣的,都是要把系統(tǒng)進行拆分,部署到不同機器上,因為一臺機器可能承受不了這么大的訪問壓力,或者說要支撐這么大的訪問壓力需要采購一臺性能超級好的服務(wù)器其財務(wù)成本非常高,有這些預(yù)算完全可以采購很多臺普通的服務(wù)器了,分布式系統(tǒng)各個模塊通過接口進行數(shù)據(jù)交互,其實分布式也是一種微服務(wù),因為都是把模塊拆分變?yōu)楠毩⒌膯卧?#xff0c;提供接口來調(diào)用
它們的本質(zhì)的區(qū)別體現(xiàn)在“目標”上,何為目標,就是你采用分布式架構(gòu)或者采用微服務(wù)架構(gòu),你最終是為了什么,要達到什么目的?分布式架構(gòu)的目標是什么?就是訪問量很大一臺機器承受不了,或者是成本問題,不得不使用多臺機器來完成服務(wù)的部署;而微服務(wù)的目標是什么?只是讓各個模塊拆分開來,不會被互相影響,比如模塊的升級或者出現(xiàn) BUG 或者是重構(gòu)等等都不要影響到其他模塊,微服務(wù)它是可以在一臺機器上部署;
3.什么是微服務(wù)?
簡單的說,微服務(wù)架構(gòu)就是將一個完整的應(yīng)用從數(shù)據(jù)存儲開始垂直拆分成多個不同的服務(wù),每個服務(wù)都能獨立部署、獨立維護、獨立擴展,服務(wù)與服務(wù)間通過諸如 RESTful API 的方式互相調(diào)用。
4.服務(wù)治理
服務(wù)治理可以說是微服務(wù)架構(gòu)中最為核心和基礎(chǔ)的模塊, 它主要用來實現(xiàn)各個微服務(wù)實例的自動化注冊與發(fā)現(xiàn)。 為什么我們在微服務(wù)架構(gòu)中那么需要服務(wù)治理模塊呢?微服務(wù)系統(tǒng)沒有它會有什么不好的地方嗎?
在最初開始構(gòu)建微服務(wù)系統(tǒng)的時候可能服務(wù)并不多, 我們可以通過做一些靜態(tài)配置來 完成服務(wù)的調(diào)用。 比如,有兩個服務(wù) A 和 B, 其中服務(wù) A 需要調(diào)用服務(wù) B 來完成一個業(yè)務(wù) 操作時,為了實現(xiàn)服務(wù) B 的高可用, 不論采用服務(wù)端負載均衡還是客戶端負載均衡, 都需 要手工維護服務(wù) B 的具體實例清單。 但是隨著業(yè)務(wù)的發(fā)展, 系統(tǒng)功能越來越復(fù)雜, 相應(yīng)的 微服務(wù)應(yīng)用也不斷增加, 我們的靜態(tài)配置就會變得越來越難以維護。 并且面對不斷發(fā)展的業(yè)務(wù), 我們的集群規(guī)模、 服務(wù)的位置 、 服務(wù)的命名等都有可能發(fā)生變化, 如果還是通過手 工維護的方式, 那么極易發(fā)生錯誤或是命名沖突等問題。 同時, 對于這類靜態(tài)內(nèi)容的維護 也必將消耗大量的人力。
為了解決微服務(wù)架構(gòu)中的服務(wù)實例維護問題, 產(chǎn)生了大量的服務(wù)治理框架和產(chǎn)品。 這 些框架和產(chǎn)品的實現(xiàn)都圍繞著服務(wù)注冊與服務(wù)發(fā)現(xiàn)機制來完成對微服務(wù)應(yīng)用實例的自動化 管理。
5.服務(wù)注冊
在服務(wù)治理框架中, 通常都會構(gòu)建一個注冊中心, 每個服務(wù)單元向注冊 中心登記自己提供的服務(wù), 將主機與端口號、 版本號、 通信協(xié)議等一些附加信息告 知注冊中心, 注冊中心按服務(wù)名分類組織服務(wù)清單。 比如, 我們有兩個提供服務(wù) A 的進程分別運行于 192.168.0.100:8000 和 192.168.0.101:8000 位置上, 另外還有三個提供服務(wù) B 的進程分別運行千 192.168.0.100:9000、 192.168.0.101:9000、 192.168.0.102:9000 位置上。 當(dāng)這些進程均啟動, 并向注冊中心注冊自己的服務(wù)之后, 注冊中心就會維護類似下面的一個服務(wù)清單。 另外, 服務(wù)注冊中心還需要以心跳的方式去監(jiān)測清單中的服務(wù)是否可用, 若不可用 需要從服務(wù)清單中剔除, 達到排除故障服務(wù)的效果。
Spring Cloud 支持得最好的是 Nacos,其次是 Eureka,其次是 Consul,再次是 Zookeeper。
6.服務(wù)發(fā)現(xiàn)
服務(wù)消費者向注冊中心請求已經(jīng)登記的服務(wù)列表,然后得到某個服務(wù)
由于在服務(wù)治理框架下運作, 服務(wù)間的調(diào)用不再通過指定具體的實例地 址來實現(xiàn), 而是通過向服務(wù)名發(fā)起請求調(diào)用實現(xiàn)。 所以, 服務(wù)調(diào)用方在調(diào)用服務(wù)提 供方接口的時候, 并不知道具體的服務(wù)實例位置。 因此, 調(diào)用方需要向服務(wù)注冊中 心咨詢服務(wù), 并獲取所有服務(wù)的實例清單, 以實現(xiàn)對具體服務(wù)實例的訪問。 比如, 現(xiàn)有服務(wù) C 希望調(diào)用服務(wù) A, 服務(wù) C 就需要向注冊中心發(fā)起咨詢服務(wù)請求,服務(wù)注 冊中心就會將服務(wù) A 的位置清單返回給服務(wù) C, 如按上例服務(wù) A 的情況,C 便獲得 了服務(wù) A 的兩個可用位置 192.168.0.100:8000 和 192.168.0.101:8000。 當(dāng)服務(wù) C 要發(fā)起調(diào)用的時候,便從該清單中以某種輪詢策略取出一個位置來進行服 務(wù)調(diào)用,這就是后續(xù)我們將會介紹的客戶端負載均衡。 這里我們只是列舉了一種簡 單的服務(wù)治理邏輯, 以方便理解服務(wù)治理框架的基本運行思路。 實際的框架為了性 能等因素, 不會采用每次都向服務(wù)注冊中心獲取服務(wù)的方式, 并且不同的應(yīng)用場景 在緩存和服務(wù)剔除等機制上也會有一些不同的實現(xiàn)策略。
7.什么是 Spring Cloud?
Spring Cloud 是一個基于 Spring Boot 實現(xiàn)的云應(yīng)用開發(fā)工具,它為基于 JVM 的云應(yīng)用開發(fā)中的配置管理、服務(wù)發(fā)現(xiàn)、斷路器、智能路由、微代理、控制總線、全局鎖、決策競選、分布式會話和集群狀態(tài)管理等操作提供了一種簡單的開發(fā)方式。
Spring Cloud 包含了多個子項目(針對分布式系統(tǒng)中涉及的多個不同開源產(chǎn)品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI 等項目。
Spring Cloud 為開發(fā)人員提供了一些工具用來快速構(gòu)建分布式系統(tǒng)中的一些常見模式和解決一些常見問題(例如配置管理、服務(wù)發(fā)現(xiàn)、斷路器智能路由、微代理、控制總線、一次性令牌、全局鎖、領(lǐng)導(dǎo)選舉、分布式會話、群集狀態(tài))。
8.微服務(wù)與 Spring-Cloud?
微服務(wù)只是一種項目的架構(gòu)方式、架構(gòu)理念,或者說是一種概念,就如同我們的 MVC 架構(gòu)一樣
Spring Cloud 便是對這種架構(gòu)方式的技術(shù)落地實現(xiàn);
9.Spring Cloud 版本
為了避免大家對版本號的誤解,避免與子項目版本號混淆,所以 Spring Cloud 發(fā)布的版本是一個按照字母順序的倫敦地鐵站的名字(“天使”是第一個版本,"布里克斯頓”是第二個),字母順序是從 A-Z,目前最新穩(wěn)定版本 Greenwich
SR3,當(dāng) Spring Cloud 里面的某些子項目出現(xiàn)關(guān)鍵性 bug 或重大更新,則發(fā)布序列將推出名稱以“.SRX”結(jié)尾的版本,其中“X”是一個數(shù)字,比如:Greenwich SRl、Greenwich SR2、Greenwich SR3;
Spring Cloud 是微服務(wù)開發(fā)的一整套解決方案,采用 Spring Cloud 開發(fā),每個項目依然是使用 Spring Boot
版本
- Hoxton
- Greenwich
- Finchley
- Edgware
- Dalston
- Camden
- Brixton
- Angel
10.Spring Cloud 整體架構(gòu)?
11.Spring Cloud 組件
它主要提供的模塊包括:
- 服務(wù)發(fā)現(xiàn)(Eureka)
- 斷路器(Hystrix)
- 智能路有(Zuul)
- 客戶端負載均衡(Ribbon)
12.Spring Cloud注解
- @EnableEurekaServer: 用在springboot啟動類上,表示這是一個eureka服務(wù)注冊中心;
- @EnableDiscoveryClient:用在springboot啟動類上,表示這是一個服務(wù),可以被注冊中心找到;
- @LoadBalanced:開啟負載均衡能力;
- @EnableCircuitBreaker:用在啟動類上,開啟斷路器功能;
- @HystrixCommand(fallbackMethod=”backMethod”):用在方法上,fallbackMethod指定斷路回調(diào)方法;
- @EnableConfigServer:用在啟動類上,表示這是一個配置中心,開啟Config Server;
- @EnableZuulProxy:開啟zuul路由,用在啟動類上;
- @SpringCloudApplication:
@SpringBootApplication
@EnableDiscovertyClient
@EnableCircuitBreaker
分別是SpringBoot注解、注冊服務(wù)中心Eureka注解、斷路器注解。對于SpringCloud來說,這是每一微服務(wù)必須應(yīng)有的三個注解,所以才推出了@SpringCloudApplication這一注解集合。 - @ConfigurationProperties:Spring源碼中大量使用了ConfigurationProperties注解,比如server.port就是由該注解獲取到的,通過與其他注解配合使用,能夠?qū)崿F(xiàn)Bean的按需配置。
該注解有一個prefix屬性,通過指定的前綴,綁定配置文件中的配置,該注解可以放在類上,也可以放在方法上。
二.eureka
1.什么是 eureka?
Eureka 服務(wù)治理體系中的三個核心角色: 服務(wù)注冊中心、 服務(wù)提供者以及服務(wù)消費者。
Spring Cloud Eureka, 使用 Netflix Eureka 來實現(xiàn)服務(wù)注冊與發(fā)現(xiàn), 它既包含了服務(wù)端組件,也包含了客戶端組件,并且服務(wù)端與客戶端均采用 Java 編寫,所以 Eureka 主要適用 于通過 Java 實現(xiàn)的分布式系統(tǒng),或是與 NM 兼容語言構(gòu)建的系統(tǒng)。但是,由于 Eureka 服 務(wù)端的服務(wù)治理機制提供了完備的 RESTfulAPL 所以它也支持將非 Java 語言構(gòu)建的微服 務(wù)應(yīng)用納入 Eureka 的服務(wù)治理體系中來。只是在使用其他語言平臺的時候,需要自己來實 現(xiàn) Euerka 的客戶端程序。不過慶幸的是,在目前幾個較為流行的開發(fā)平臺上,都已經(jīng)有了 一些針對 Eureka 注冊中心的客戶端實現(xiàn)框架,比如.NET 平臺的 Steeltoe、 Node.js 的 euerkaj-sc-lient 等。
Eureka 服務(wù)端,我們也稱為服務(wù)注冊中心。它同其他服務(wù)注冊中心一樣,支持高可用 配置。它依托于強一致性提供良好的服務(wù)實例可用性,可以應(yīng)對多種不同的故障場景。如 果 Eureka 以集群模式部署,當(dāng)集群中有分片出現(xiàn)故障時,那么 Eureka 就轉(zhuǎn)入自我保護模 式。它允許在分片故障期間繼續(xù)提供服務(wù)的發(fā)現(xiàn)和注冊,當(dāng)故障分片恢復(fù)運行時,集群中 的其他分片會把它們的狀態(tài)再次同步回來。以在 AWS 上的實踐為例,Netflix 推薦每個可 用的區(qū)域運行一個 Eureka 服務(wù)端,通過它來形成集群。不同可用區(qū)域的服務(wù)注冊中心通過 異步模式互相復(fù)制各自的狀態(tài),這意味著在任意給定的時間點每個實例關(guān)于所有服務(wù)的狀 態(tài)是有細微差別的。
Eureka 客戶端,主要處理服務(wù)的注冊與發(fā)現(xiàn)。客戶端服務(wù)通過注解和參數(shù)配置的方式, 嵌入在客戶端應(yīng)用程序的代碼中,在應(yīng)用程序運行時,Euerka 客戶端向注冊中心注冊自身 提供的服務(wù)并周期性地發(fā)送心跳來更新它的服務(wù)租約。同時,它也能從服務(wù)端查詢當(dāng)前注 冊的服務(wù)信息并把它們緩存到本地并周期性地刷新服務(wù)狀態(tài)。
2.eureka 架構(gòu)
- Service Provider:暴露服務(wù)的服務(wù)提供方。
- Service Consumer:調(diào)用遠程服務(wù)的服務(wù)消費方。
- EureKa Server:服務(wù)注冊中心和服務(wù)發(fā)現(xiàn)中心。
3.eureka 保護機制
在沒有 Eureka 自我保護的情況下,如果 Eureka Server 在一定時間內(nèi)沒有接收到某個微服務(wù)實例的心跳 I Eureka Server 將會注銷該實例,但是當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)故障時,那么微服務(wù)與 Eureka Server 之間將無法正常通信,以上行為可能變得非常危險了因為微服務(wù)本身其實是正常的此時不應(yīng)該注銷這個微服務(wù),如果沒有自我保護機制,那么 Eureka Server 就會將此服務(wù)注銷掉。
當(dāng) Eureka Server 節(jié)點在短時間內(nèi)丟失過多客戶端時(可能發(fā)生了網(wǎng)絡(luò)分區(qū)故障),那么就會把這個微服務(wù)節(jié)點進行保護。一旦進入自我保護模式,Eureka Server 就會保護服務(wù)注冊表中的信息,不刪除服務(wù)注冊表中的數(shù)據(jù)(也就是不會注銷任何微服務(wù))。當(dāng)網(wǎng)絡(luò)故障恢復(fù)后,該 Eureka Server 節(jié)點會再自動退出自我保護模式。
所以,自我保護模式是一種應(yīng)對網(wǎng)絡(luò)異常的安全保護措施,它的架構(gòu)哲學(xué)是寧可同時保留所有微服務(wù)(健康的微服務(wù)和不健康的微服務(wù)都會保留),也不盲目注銷任何健康的微服務(wù),使用自我保護模式,可以讓 Eureka 集群更加的健壯.穩(wěn)定
#禁用eurekd目我保護 eureka.server.enable-self-preservation=false4.Eureka 與 Zookeeper?
著名的 CAP 理論指出,一個分布式系統(tǒng)不可能同時滿足 C(一致性街)、A(可用性)和 P(分區(qū)容錯性)。
由于分區(qū)容錯性在是分布式系統(tǒng)中必須要保證的,因此我們只能在 A 和 C 之間進行權(quán)衡,在此 Zookeeper 保證的是 CP,而 Eureka 則是 AP。
Eureka保證AP
Eureka 優(yōu)先保證可用性,Eureka 各個節(jié)點是平等的,某節(jié)點異常,剩余的節(jié)點依然可以提供注冊和服務(wù)查詢功能.
在向某個 Eureka 注冊時,如果發(fā)現(xiàn)連接失敗,則只要有一臺 Eureka 還在,就能保證注冊服務(wù)可用,但是數(shù)據(jù)可能不是最新的(不保證強一致性)。
Zookeeper保證CP
在 ZooKeeper 中,當(dāng) master 節(jié)點因為網(wǎng)絡(luò)故障與其他節(jié)點失去聯(lián)系時,剩余節(jié)點會重新進行 leader 選舉,但是問題在于,選舉 leader 需要一定時間,且選舉期間整個 ZooKeeper 集群都是不可用的,這就導(dǎo)致在選舉期間注冊服務(wù)癱瘓。在云部署的環(huán)境下,因網(wǎng)絡(luò)問題使得 ZooKeeper 集群失去 master 節(jié)點是大概率事件,雖然服務(wù)最終能夠恢復(fù),但是在選舉時間內(nèi)導(dǎo)致服務(wù)注冊長期不可用是難以容忍的。
5.Eureka 高可用集群?
相互注冊,去中心化
#端口號 server:port: 8769spring:application:name: eurka-servereureka:instance:hostname: eureka8769client:register-with-eureka: false #由于該應(yīng)用為注冊中心,所以設(shè)置 為 false, 代表不向注冊中心注冊自己。fetch-registry: false #由于注冊中心的職責(zé)就是維護服務(wù)實例, 它并不需要去檢索服務(wù), 所以也設(shè)置為 false。serviceUrl:defaultZone: http://eureka8767:8767/eureka/,http://eureka8768:8768/eureka/6.eureka 三要素?
在 “服務(wù)治理”示例中, 我們的示例雖然簡單, 但是麻雀雖小、 五臟俱全。 它已經(jīng)包含了整個 Eureka 服務(wù)治理基礎(chǔ)架構(gòu)的三個核心要素。
- 服務(wù)注冊中心: Eureka 提供的服務(wù)端, 提供服務(wù)注冊與發(fā)現(xiàn)的功能, 也就是我們實現(xiàn)的 eureka-server。
- 服務(wù)提供者:提供服務(wù)的應(yīng)用, 可以是 SpringBoot 應(yīng)用, 也可以是其他技術(shù)平臺且遵循 Eureka 通信機制的應(yīng)用。它將自己提供的服務(wù)注冊到 Eureka, 以供其他應(yīng)用發(fā)現(xiàn), 也就是我們實現(xiàn)的 spring-boot-service 應(yīng)用。
- 服務(wù)消費者:消費者應(yīng)用從服務(wù)注冊中心獲取服務(wù)列表, 從而使消費者可以知道去何處調(diào)用其所需要的服務(wù),在面我們使用了 Ribbon 來實現(xiàn)服務(wù)消費,另外后續(xù)還會介紹使用 Feign 的消費方式。 很多時候, 客戶端既是服務(wù)提供者也是服務(wù)消費者。
7.Eureka 本地緩存
8.服務(wù)提供者
1.服務(wù)注冊
將服務(wù)所在主機、端口、版本號、通信協(xié)議等信息登記到注冊中心上;
“服務(wù)提供者” 在啟動的時候會通過發(fā)送 REST 請求的方式將自己注冊到 EurekaServer 上, 同時帶上了自身服務(wù)的一些元數(shù)據(jù)信息。Eureka Server 接收到這個 REST 請求之后, 將元數(shù)據(jù)信息存儲在一個雙層結(jié)構(gòu) Map 中, 其中第一層的 key 是服務(wù)名, 第二層的 key 是 具體服務(wù)的實例名。(我們可以回想一下之前在實現(xiàn) Ribbon 負載均衡的例子中, Eureka 信 息面板中一個服務(wù)有多個實例的清況, 這些內(nèi)容就是以這樣的雙層 Map 形式存儲的)
在服務(wù)注冊時, 需要確認一下 eureka.client.register-with-eureka=true 參數(shù)是否正確, 該值默認為 true。若設(shè)置為 false 將不會啟動注冊操作。
boolean register() throws Throwable {logger.info("DiscoveryClient_{}: registering service...", this.appPathIdentifier);EurekaHttpResponse httpResponse;try {httpResponse = this.eurekaTransport.registrationClient.register(this.instanceInfo);} catch (Exception var3) {logger.warn("DiscoveryClient_{} - registration failed {}", new Object[]{this.appPathIdentifier, var3.getMessage(), var3});throw var3;}if (logger.isInfoEnabled()) {logger.info("DiscoveryClient_{} - registration status: {}", this.appPathIdentifier, httpResponse.getStatusCode());}return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode(); }2.服務(wù)同步
如架構(gòu)圖中所示, 這里的兩個服務(wù)提供者分別注冊到了兩個不同的服務(wù)注冊中心上, 也就是說, 它們的信息分別被兩個服務(wù)注冊中心所維護。 此時, 由于服務(wù)注冊中心之間因互相注冊為服務(wù), 當(dāng)服務(wù)提供者發(fā)送注冊請求到一個服務(wù)注冊中心時, 它會將該請求轉(zhuǎn)發(fā)給集群中相連的其他注冊中心,從而實現(xiàn)注冊中心之間的服務(wù)同步。通過服務(wù)同步,兩個服務(wù)提供者的服務(wù)信息就可以通過這兩臺服務(wù)注冊中心中的任意一臺獲取到。
3.服務(wù)續(xù)約
在注冊完服務(wù)之后,服務(wù)提供者會維護一個心跳用來持續(xù)告訴 Eureka Server: "我還活著”,以防止 Eureka Server 的 “剔除任務(wù) ” 將該服務(wù)實例從服務(wù)列表中排除出去,我們稱該操作為服務(wù)續(xù)約(Renew)。
關(guān)千服務(wù)續(xù)約有兩個重要屬性,我們可以關(guān)注并根據(jù)需要來進行調(diào)整:
而 “服務(wù)獲取” 的邏輯在獨立的一個 W 判斷中, 其判斷依據(jù)就是我們之前所提到的 eureka.clien.fetch-registry=true 參數(shù), 它默認為 true, 大部分情況下我們 不需要關(guān)心。 為了定期更新客戶端的服務(wù)清單, 以保證客戶端能夠訪問確實健康的服務(wù)實 例, “服務(wù)獲取” 的請求不會只限于服務(wù)啟動, 而是一個定時執(zhí)行的任務(wù), 從源碼中我們可 以看到任務(wù)運行中的 registryFetchintervalSeconds 參數(shù)對應(yīng)的就是之前所提到的 eureka.client.registry-fetch-interval-seconds=30 配置參數(shù), 它默認為 30 秒。
eureka.instance.lease-renewal-interval-in-seconds=30 eureka.instance.lease-expiration-duration-in-seconds=90 #參數(shù)用于定義服務(wù)續(xù)約任務(wù)的調(diào)用間隔時間,默認為30秒。 eureka.instance.lease-renewal-interval-in-seconds #參數(shù)用于定義服務(wù)失效的時間,默認為90秒。 eureka.instance.lease-expiration-duration-in-seconds private void initScheduledTasks() {int renewalIntervalInSecs;int expBackOffBound;if (this.clientConfig.shouldFetchRegistry()) {renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds();expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);}if (this.clientConfig.shouldRegisterWithEureka()) {renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound();logger.info("Starting heartbeat executor: renew interval is: {}", renewalIntervalInSecs);this.scheduler.schedule(new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);this.instanceInfoReplicator = new InstanceInfoReplicator(this, this.instanceInfo, this.clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2);this.statusChangeListener = new StatusChangeListener() {public String getId() {return "statusChangeListener";}public void notify(StatusChangeEvent statusChangeEvent) {if (InstanceStatus.DOWN != statusChangeEvent.getStatus() && InstanceStatus.DOWN != statusChangeEvent.getPreviousStatus()) {DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent);} else {DiscoveryClient.logger.warn("Saw local status change event {}", statusChangeEvent);}DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();}};if (this.clientConfig.shouldOnDemandUpdateStatusChange()) {this.applicationInfoManager.registerStatusChangeListener(this.statusChangeListener);}this.instanceInfoReplicator.start(this.clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());} else {logger.info("Not registering with Eureka server per configuration");}}9.服務(wù)消費者
1.獲取服務(wù)
到這里,在服務(wù)注冊中心已經(jīng)注冊了一個服務(wù),并且該服務(wù)有兩個實例。 當(dāng)我們啟動 服務(wù)消費者的時候,它會發(fā)送一個 REST 請求給服務(wù)注冊中心,來獲取上面注冊的服務(wù)清 單。為了性能考慮,EurekaServer 會維護一份只讀的服務(wù)清單來返回給客戶端,同時該緩 存清單會每隔 30 秒更新一次。
獲取服務(wù)是服務(wù)消費者的基礎(chǔ),所以必須確保 eureka.client.fetch-registry= true 參數(shù)沒有被修改成 false, 該值默認為 true。若希望修改緩存清單的更新時間,可 以通過 eureka.client.registry-fetch-interval-seconds=30 參數(shù)進行修改, 該參數(shù)默認值為 30, 單位為秒。
2.服務(wù)調(diào)用
服務(wù)消費者在獲取服務(wù)清單后,通過服務(wù)名可以獲得具體提供服務(wù)的實例名和該實例的元數(shù)據(jù)信息。 因為有這些服務(wù)實例的詳細信息, 所以客戶端可以根據(jù)自己的需要決定具 體調(diào)用哪個實例,在 ribbon 中會默認采用輪詢的方式進行調(diào)用,從而實現(xiàn)客戶端的負載均衡。
對于訪問實例的選擇,Eureka 中有 Region 和 Zone 的概念, 一個 Region 中可以包含多個 Zone, 每個服務(wù)客戶端需要被注冊到一個 Zone 中, 所以每個客戶端對應(yīng)一個 Region 和一個 Zone。 在進行服務(wù)調(diào)用的時候,優(yōu)先訪問同處一個 Zone 中的服務(wù)提供方, 若訪問不到,就 訪問其他的 Zone, 更多關(guān)于 Region 和 Zone 的知識,我們會在后續(xù)的源碼解讀中介紹。
3.服務(wù)下線
在系統(tǒng)運行過程中必然會面臨關(guān)閉或重啟服務(wù)的某個實例的情況, 在服務(wù)關(guān)閉期間, 我們自然不希望客戶端會繼續(xù)調(diào)用關(guān)閉了的實例。 所以在客戶端程序中, 當(dāng)服務(wù)實例進行正常的關(guān)閉操作時, 它會觸發(fā)一個服務(wù)下線的 REST 請求給 Eureka Server, 告訴服務(wù)注冊中心:“我要下線了”。服務(wù)端在接收到請求之后, 將該服務(wù)狀態(tài)置為下線(DOWN), 并把該下線事件傳播出去。
10.服務(wù)注冊中心
1.失效剔除
有些時候, 我們的服務(wù)實例并不一定會正常下線, 可能由于內(nèi)存溢出、 網(wǎng)絡(luò)故障等原因使得服務(wù)不能正常工作, 而服務(wù)注冊中心并未收到 “服務(wù)下線” 的請求。 為了從服務(wù)列表中將這些無法提供服務(wù)的實例剔除,EurekaSrevre 在啟動的時候會創(chuàng)建一個定時任務(wù), 默認每隔一段時間(默認為 60 秒) 將當(dāng)前清單中超時(默認為 90 秒)沒有續(xù)約的服務(wù)剔除出去。
2.自我保護
當(dāng)我們在本地調(diào)試基于 Eurkea 的程序時, 基本上都會碰到這樣一個問題, 在服務(wù)注冊中心的信息面板中出現(xiàn)類似下面的紅色警告信息:
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.實際上, 該警告就是觸發(fā)了 EurekaServer 的自我保護機制。 之前我們介紹過, 服務(wù)注 冊到 EurekaSrevre 之后,會維護一個心跳連接,告訴 EurekaServer 自己還活著。EurkeaServer 在運行期間,會統(tǒng)計心跳失敗的比例在 15 分鐘之內(nèi)是否低于 85%,如果出現(xiàn)低于的情況(在 單機調(diào)試的時候很容易滿足, 實際在生產(chǎn)環(huán)境上通常是由于網(wǎng)絡(luò)不穩(wěn)定導(dǎo)致), Eureka Server 會將當(dāng)前的實例注冊信息保護起來, 讓這些實例不會過期, 盡可能保護這些注冊信 息。 但是, 在這段保護期間內(nèi)實例若出現(xiàn)問題, 那么客戶端很容易拿到實際已經(jīng)不存在的 服務(wù)實例, 會出現(xiàn)調(diào)用失敗的清況, 所以客戶端必須要有容錯機制, 比如可以使用請求重 試、 斷路器等機制。
由于本地調(diào)試很容易觸發(fā)注冊中心的保護機制, 這會使得注冊中心維護的服務(wù)實例不那么準確。 所以, 我們在本地進行開發(fā)的時候, 可以使用參數(shù)來關(guān)閉保護機制, 以確保注冊中心可以將不可用的實例正確剔除。
eureka.server.enableself-preservation =false11.eureka 源碼
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({EnableDiscoveryClientImportSelector.class}) public @interface EnableDiscoveryClient {boolean autoRegister() default true; } //服務(wù)發(fā)現(xiàn)的核心類 package com.netflix.discovery; package org.springframework.cloud.netflix.eureka;org.springframework.cloud.client.discovery.DiscoveryClient 是 SpringCloud 的接口, 它定義了用來發(fā)現(xiàn)服務(wù)的常用抽象方法, 通過該接口可以有效地 屏蔽服務(wù)治理的實現(xiàn)細節(jié), 所以使用 SpringCloud 構(gòu)建的微服務(wù)應(yīng)用可以方便地切換不同服務(wù)治理框架, 而不改動程序代碼, 只需要另外添加一些針對服務(wù)治理框架的配置即可。 org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient 是對該接口的實現(xiàn), 從命名來判斷, 它實現(xiàn)的是對 Eureka 發(fā)現(xiàn)服務(wù)的封裝。 所以 EurekaDiscoveryClient 依賴了 Netflix Eureka 的 com.netflix.discovery. EurekaClient 接口, EurekaClient 繼承了 LookupService 接口, 它們都是 Netflix 開源包中的內(nèi)容, 主要定義了針對 Eureka 的發(fā)現(xiàn)服務(wù)的抽象方法, 而真正實現(xiàn)發(fā)現(xiàn)服務(wù)的 則是 Netflix 包中的 com.netftx.discovery.DiscoveryClient 類。
12.DiscoveryClient
DiscoveryClient 這個類用于幫助與 Eureka Server 互相協(xié)作。
EurekaClient 負責(zé)下面的任務(wù):
-
向 Eureka Server 注冊服務(wù)實例
-
向 Eureka Server 服務(wù)租約
-
當(dāng)服務(wù)關(guān)閉期間, 向 Eureka Server 取消租約
-
查詢 Eureka Server 中的服務(wù)實例列表
13.什么是 Region?
客戶端依次加載了兩個內(nèi)容, 第一個是 Region, 第二個 是 Zone, 從其加載邏輯上我們可以判斷它們之間的關(guān)系:
通過 getRegion 函數(shù), 我們可以看到它從配置中讀取了一個 Region 返回, 所以 一 個微服務(wù)應(yīng)用只可以屬于一個 Region, 如果不特別配置, 默認為 defaul。若我們要 自己設(shè)置, 可以通過 eureka.client.region 屬性來定義。
public static String getRegion(EurekaClientConfig clientConfig) {String region = clientConfig.getRegion();if (region == null) {region = "default";}region = region.trim().toLowerCase();return region; }14.什么是 Zone?
通過 getAvailabilityZones 函數(shù), 可以知道當(dāng)我們沒有特別為 Region 配置 Zone 的時候, 將默認采用 defaultZone, 這也是我們之前配置參數(shù) eureka.client.serviceUrl.defaultZone 的由來。 若要為應(yīng)用指定 Zone, 可以通過 eureka.client.availability-zones 屬性來進行設(shè)置。從該函數(shù)的 return 內(nèi)容, 我們可以知道 Zone 能夠設(shè)置多個, 并且通過逗號分隔來配置。 由此, 我們可以判斷 Region 與 Zone 是一對多的關(guān)系
public String[] getAvailabilityZones(String region) {String value = (String)this.availabilityZones.get(region);if (value == null) {value = "defaultZone";}return value.split(",");}當(dāng)我們在微服務(wù)應(yīng)用中使用 Ribbon 來實現(xiàn)服務(wù)調(diào)用時,對于 Zone 的設(shè)置可以在負載 均衡時實現(xiàn)區(qū)域親和特性: Ribbon 的默認策略會優(yōu)先訪問同客戶端處于一個 Zone 中的服 務(wù)端實例,只有當(dāng)同一個 Zone 中沒有可用服務(wù)端實例的時候才會訪問其他 Zone 中的實例。 所以通過 Zone 屬性的定義,配合實際部署的物理結(jié)構(gòu),我們就可以有效地設(shè)計出對區(qū)域性 故障的容錯集群。
15.服務(wù)消費基礎(chǔ)
服務(wù)消費基礎(chǔ)實現(xiàn)方式,不是傳統(tǒng)實現(xiàn)方式
@RestController public class DcController {@AutowiredLoadBalancerClient loadBalancerClient;@AutowiredRestTemplate restTemplate;@GetMapping("/consumer")public String dc() {ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-server");String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/dc";System.out.println(url);return restTemplate.getForObject(url, String.class);} }可以看到這里,我們注入了LoadBalancerClient和RestTemplate,并在/consumer接口的實現(xiàn)中,先通過loadBalancerClient的choose函數(shù)來負載均衡的選出一個eureka-client的服務(wù)實例,這個服務(wù)實例的基本信息存儲在ServiceInstance中,然后通過這些對象中的信息拼接出訪問/dc接口的詳細地址,最后再利用RestTemplate對象實現(xiàn)對服務(wù)提供者接口的調(diào)用。
16.什么是 consul?
consul 特性:
- 服務(wù)發(fā)現(xiàn)
- 健康檢查
- Key/Value 存儲
- 多數(shù)據(jù)中心
服務(wù)發(fā)現(xiàn)的接口DiscoveryClient是 Spring Cloud 對服務(wù)治理做的一層抽象,所以可以屏蔽 Eureka 和 Consul 服務(wù)治理的實現(xiàn)細節(jié),我們的程序不需要做任何改變,只需要引入不同的服務(wù)治理依賴,并配置相關(guān)的配置屬性就能輕松的將微服務(wù)納入 Spring Cloud 的各個服務(wù)治理框架中。
由于 Consul 自身提供了服務(wù)端,所以我們不需要像之前實現(xiàn) Eureka 的時候創(chuàng)建服務(wù)注冊中心,直接通過下載 consul 的服務(wù)端程序就可以使用。
三.Ribbon 和 feign
1.什么是負載均衡?
載均和分為硬件負載均衡和軟件負載均衡:
- 硬件負載均衡:比如 F5、深信服、Array 等;
- 軟件負載均衡:比如 Nginx、LVS、HAProxy 等;
2.服務(wù)端負載均衡
3.什么是 Ribbon?
Ribbon 是 Netfliⅸ 公司發(fā)布的開源項目(組件、框架、jar 包),主要功能是提供客戶端的軟件負載均衡算法,它會從 eureka 中獲取一個可用的服務(wù)端清單通過心跳檢測來易剔除故的服務(wù)端節(jié)點以保證清單中都是可以正常訪問的服務(wù)端節(jié)點。
當(dāng)客戶端發(fā)送請求,則 ribbon 負載均衡器按某種算法(比如輪詢、權(quán)重、最小連接數(shù)等從維護的可用服務(wù)端清單中取出一臺服務(wù)端的地址然后進行請求
Ribbon 是一個基于 HTTP 和 TCP 客戶端的負載均衡器。Ribbon 非常簡單,可以說就是一個 jar 包,這個 jar 包實現(xiàn)了負載均衡算法,Spring Cloud 對 Ribbon 做了二次封裝,可以讓我們使用 RestTemplate 的服務(wù)請求,自動轉(zhuǎn)換成客戶端負載均衡的服務(wù)調(diào)用。
Ribbon 可以在通過客戶端中配置的 ribbonServerList 服務(wù)端列表去輪詢訪問以達到均衡負載的作用。當(dāng) Ribbon 與 Eureka 聯(lián)合使用時,ribbonServerList 會被 DiscoveryEnabledNIWSServerList 重寫,擴展成從 Eureka 注冊中心中獲取服務(wù)端列表。同時它也會用 NIWSDiscoveryPing 來取代 IPing,它將職責(zé)委托給 Eureka 來確定服務(wù)端是否已經(jīng)啟動。
Ribbon 支持多種負載均衡算法,還支持自定義的負載均衡算法。
使用 Ribbon 實現(xiàn)負載均衡的調(diào)用(spring cloud -> 封裝 ribbon + eureka + restTemplate)
Ribbon 默認是采用懶加載,即第一次訪問時才會去創(chuàng)建 LoadBalanceClient,請求時間會很長。
而饑餓加載則會在項目啟動時創(chuàng)建,降低第一次訪問的耗時,通過下面配置開啟饑餓加載:
ribbon:eager-load:enabled: trueclients: user4.IRule
public interface IRule {Server choose(Object var1);void setLoadBalancer(ILoadBalancer var1);ILoadBalancer getLoadBalancer(); }5.ILoadBalancer
一個服務(wù)對應(yīng)一個 LoadBalancer,一個 LoadBalancer 只有一個 Rule,LoadBalancer 記錄服務(wù)的注冊地址,Rule 提供從服務(wù)的注冊地址中找出一個地址的規(guī)則。
通過 LoadBalancerIntercepor 攔截器,可以攔截請求 url 和服務(wù) id,然后結(jié)合 IRule 進行路由處理
public interface ILoadBalancer {void addServers(List<Server> var1);Server chooseServer(Object var1);void markServerDown(Server var1);/** @deprecated */@DeprecatedList<Server> getServerList(boolean var1);List<Server> getReachableServers();List<Server> getAllServers(); }6.負載均衡策略
Ribbon 提供七種負載均衡策略,默認的負載均衡策略是輪訓(xùn)策略。RoundRobinRule
public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnectionListener, IClientConfigAware {private static final IRule DEFAULT_RULE = new RoundRobinRule(); }通過 choose 方法選擇指定的算法。完整的算法包含如下:
| RandomRule | 隨機算法實現(xiàn) |
| RoundRobinRule | 輪詢負載均衡策略,依次輪詢所有可用服務(wù)器列表 |
| RetryRule | 先按照 RoundRobinRule 策略獲取服務(wù),如果獲取服務(wù)失敗會在指定時間內(nèi)重試 |
| AvaliabilityFilteringRule | 過濾掉那些因為一直連接失敗的被標記為 circuit tripped 的后端 server,并過濾掉那些高并發(fā)的的后端 server(active connections 超過配置的閾值) |
| BestAvailableRule | 會先過濾掉由于多次訪問故障二處于斷路器跳閘狀態(tài)的服務(wù),然后選擇一個并發(fā)量最小的服務(wù) |
| WeightedResponseTimeRule | 根據(jù)響應(yīng)時間分配一個 weight,響應(yīng)時間越長,weight 越小,被選中的可能性越低 |
| ZoneAvoidanceRule | 復(fù)合判斷 server 所在區(qū)域的性能和 server 的可用性選擇 server,新版本默認的負載均衡策略 |
-
RoundRobinRule: 輪詢策略。Ribbon 默認采用的策略。若經(jīng)過一輪輪詢沒有找到可用的 provider,其最多輪詢 10 輪(代碼中寫死的,不能修改)。若還未找到,則返回 null。
-
RandomRule: 隨機策略,從所有可用的 provider 中隨機選擇一個。。
-
RetryRule: 重試策略。先按照 RoundRobinRule 策略獲取 server,若獲取失敗,則在指定的時限內(nèi)重試。默認的時限為 500 毫秒。
-
BestAvailableRule: 最可用策略。選擇并發(fā)量最小的 provider,即連接的消費者數(shù)量最少的 provider。其會遍歷服務(wù)列表中的每一個 server,選擇當(dāng)前連接數(shù)量 minimalConcurrentConnections 最小的 server。
-
AvailabilityFilteringRule: 可用過濾算法。該算法規(guī)則是:過濾掉處于熔斷狀態(tài)的 server 與已經(jīng)超過連接極限的 server,對剩余 server 采用輪詢策略。
7.負載均衡原理
Ribbon 負載均衡基本原理:
1、Ribbon 會攔截 Eureka Client 客戶端發(fā)出的 http 請求,獲得服務(wù)名(userservice)
2、從 Eureka 中拉取動態(tài)服務(wù)列表(8081、8082。。。。。)
3、從里面選一個服務(wù)(如 8081)出來(輪詢、隨機等算法)
4、去調(diào)用這個服務(wù)
8.ribbon組件
主要有以下組件:
- IRule,定義如何選擇規(guī)則和策略
- IPing,接口定義了我們?nèi)绾巍皃ing”服務(wù)器檢查是否活著
- ServerList,定義了獲取服務(wù)器的列表接口,存儲服務(wù)列表
- ServerListFilter,接口允許過濾配置或動態(tài)獲得的候選列表服務(wù)器
- ServerListUpdater,接口使用不同的方法來做動態(tài)更新服務(wù)器列表
- IClientConfig,定義了各種api所使用的客戶端配置,用來初始化ribbon客戶端和負載均衡器,默認實現(xiàn)是DefaultClientConfigImpl ILoadBalancer, 接口定義了各種軟負載,動態(tài)更新一組服務(wù)列表及根據(jù)指定算法從現(xiàn)有服務(wù)器列表中選擇一個服務(wù)
@LoadBalanced注解,開啟客戶端的負載均衡:
- RestTemplate
- LoadBalancerAutoConfiguration
- @ConditionalOnClass(RestTemplate.class)
- @ConditionalOnBean(LoadBalancerClient.class)
- SmartInitializingSingleton 加載@LoadBalanced注解的RestTemplate到RestTemplateCustomizer
- LoadBalancerInterceptor攔截器攔截RestTemplate請求
9.遠程調(diào)用有哪些?
- Httpclient(apache)
- Httpurlconnection (jdk)
- restTemplate (spring)
- OkHttp(android)
- feign(Spring cloud)
10.什么是 Feign?
Feign 是一個聲明式的 Web Service 客戶端,它使得編寫 Web Serivce 客戶端變得更加簡單。我們只需要使用 Feign 來創(chuàng)建一個接口并用注解來配置它既可完成。它具備可插拔的注解支持,包括 Feign 注解和 JAX-RS 注解。Feign 也支持可插拔的編碼器和解碼器。Spring Cloud 為 Feign 增加了對 Spring MVC 注解的支持,還整合了 Ribbon 和 Eureka 來提供均衡負載的 HTTP 客戶端實現(xiàn)。
Feign 在Ribbon+RestTemplate 的基礎(chǔ)上做了進一步封裝,在Feign 封裝之后,我們只需創(chuàng)建一個接口并使用注解的方式來配置,即可完成對服務(wù)提供方的接口綁定簡化了使用 Ribbon + RestTemplate 的調(diào)用,自動封裝服務(wù)調(diào)用客戶端.
Feign 中也使用 Ribbon,自動集成了 ribbon
11.feignclient 冪等性
Retryer是重試器,其實現(xiàn)方法有兩種,第一種是系統(tǒng)默認實現(xiàn)方式,第二種是可以自定義重試器,一般少用,通過默認實現(xiàn)重試類Default可以看到其構(gòu)造函數(shù)中的重試次數(shù)為5。
public Default() {this(100, SECONDS.toMillis(1), 5); }public Default(long period, long maxPeriod, int maxAttempts) {this.period = period;this.maxPeriod = maxPeriod;this.maxAttempts = maxAttempts;this.attempt = 1; }因此解決Feign調(diào)用的冪等性問題最簡單也就最常用的就是讓Feign不重試。
12.ip 不能訪問
使用了@LoadBalanced 注解后不能再使用 192.168.28.1(ip)調(diào)用,必須要使用注冊中心中注冊的服務(wù)名稱
所以在使用 RestTemplate 的時候,有些場景下我們只是單純的用 ip 進行調(diào)用(如調(diào)用第三方接口服務(wù)),此時的 RestTemplate 就不能夠加@LoadBanlanced 注解。還有一種辦法是直接在需要使用 RestTemplate 的地方,直接 new 一個新的 RestTemplate 對象。如下
public void test(){RestTemplate restTemplate = new RestTemplate();//這里的接口地址就可以直接放協(xié)議+ip+端口,原理與上面一樣restTemplate.exchange("接口地址",xxx,xxx,xxx); }13.feign 超時
Spring Cloud 中 Feign 客戶端是默認開啟支持 Ribbon 的,最重要的兩個超時就是連接超時 ConnectTimeout 和讀超時 ReadTimeout,在默認情況下,也就是沒有任何配置下,Feign 的超時時間會被 Ribbon 覆蓋,兩個超時時間都是 1 秒。
#ribbon配置 ribbon:#建立連接超時時間ConnectTimeout: 5000#建立連接之后,讀取響應(yīng)資源超時時間ReadTimeout: 5000#Feign配置 feign:client:config:#這里填具體的服務(wù)名稱(也可以填default,表示對所有服務(wù)生效)app-order:#connectTimeout和readTimeout這兩個得一起配置才會生效connectTimeout: 5000readTimeout: 5000四.hystrix
1.什么是 Hystrix?
在 Spring Cloud 中使用了 Hystrix 來實現(xiàn)斷路器的功能。Hystrix 是 Netflix 開源的微服務(wù)框架套件之一,該框架目標在于通過控制那些訪問遠程系統(tǒng)、服務(wù)和第三方庫的節(jié)點,從而對延遲和故障提供更強大的容錯能力。Hystrix 具備擁有回退機制和斷路器功能的線程和信號隔離,請求緩存和請求打包,以及監(jiān)控和配置等功能。
Hystrix 原理是命令模式,在遇到不同的情況下,請求 fallback,降級處理
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @EnableCircuitBreaker public @interface EnableHystrix { }2.Hystrix 有哪些功能?
核心功能:
- 超時
- 異常
- 限流
斷路器的作用:
一個簡單的場景就是家里的保險絲,當(dāng)電流過大時保險絲熔斷,使得家里的其他電器不至于造成大規(guī)模的損壞:
在微服務(wù)中,當(dāng)大量請求到達產(chǎn)品微服務(wù),這會導(dǎo)致其服務(wù)響應(yīng)變得緩慢,或者因為內(nèi)存不足等原因使得服務(wù)器出現(xiàn)宕機或者數(shù)據(jù)庫出現(xiàn)問題;
另一個原因,當(dāng)一個微服務(wù)中有很多請求時,會導(dǎo)致他調(diào)用其他微服務(wù)的時候(正常的服務(wù)線程)造成積壓,產(chǎn)生雪崩;
3.Hystrix 超時配置
#開啟feign開始hystrix的支持 feign:hystrix:enabled: truehystrix:command:default:execution:isolation:thread:timeoutInMilliseconds: 5000 #hystrix超時時間timeout:enabled: true #開啟hystrix超時管理 ribbon:ReadTimeout: 2000 #請求超時時間http:client:enabled: true #開啟ribbon超時管理ConnectTimeout: 2000 #連接超時時間- Dalston 版本之后默認關(guān)閉,注意開啟 feign 的 hystrix 的功能
- ribbon 和 hystrix 都要設(shè)置,誰小以誰為準
4.熔斷降級
- 可以監(jiān)聽你的請求有沒有超時;(默認是 1 秒,時間可以改)
- 異常或報錯了可以快速讓請求返回,不會一直等待;(避免線程累積)
- 當(dāng)?shù)南到y(tǒng)馬上迎來大量的并發(fā)(雙十一秒殺這種或者促銷活動)
此時如果系統(tǒng)承載不了這么大的并發(fā)時,可以考慮先關(guān)閉一些不重要的微服務(wù)(在降級方法中返回一個比較友好的信息),把資源讓給核心微服務(wù),待高峰流量過去,再開啟回來。
5.服務(wù)限流
通過 2 種方式進行限流
- 線程數(shù)限制
- 信號量限制
限流配置,通過限制線程數(shù)進行限流,限流就是限制你某個微服務(wù)的使用量(可用線程),
hystriⅸ 通過線程池的方式來管理微服務(wù)的調(diào)用,它默認是一個線程池(大小 10 個)管理你的所有微服務(wù),你可以給某個微服務(wù)開辟新的線程池.
@HystrixCommand(fallbackMethod = "addServiceFallback",commandProperties = {@HystrixProperty(name = "execution.timeout.enabled", value = "true"),@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")},threadPoolProperties = {@HystrixProperty(name = "coreSize", value = "2"),@HystrixProperty(name = "maxQueueSize", value = "1"),@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),@HystrixProperty(name = "queueSizeRejectionThreshold", value = "100")} )- commandProperties 可以配置 hystrix 的各種配置
- hreadPoolProperties 屬性用于控制線程池的行為,要求為我們建立一個大小為 coreSize 的,且在之前創(chuàng)建一個隊列的線程池,隊列大小為 maxQueueSize 對應(yīng)的 value 的值
- 這個隊列的作用是:控制在線程池中線程繁忙時允許堵塞的請求數(shù)(即 value 值),一旦請求數(shù)超過了隊列大小,對線程池的任何請求都將失敗,直到隊列中有空間為止。此外,對于 maxQueueSize 的值也有說明,當(dāng) value=-1 時,則將使用 Java SynchronousQueue 來保存所有傳入的請求,同步隊列本質(zhì)上會強制要求正在處理中的請求數(shù)量永遠不要超過線程池中的可用數(shù)量;將 maxQueueSize 設(shè)置為大于 1 的值將導(dǎo)致 Hystrix 使用 Java LinkedBlockingQueue。這使得即使所有的線程都忙于處理請求,也能對請求進行排隊。
6.commandProperties
Command 屬性主要用來控制 HystrixCommand 命令的行為,它主要分下面的類別:
- Execution:用來控制 HystrixCommand.run()的執(zhí)行
- execution.isolation.strategy:該屬性用來設(shè)置 HystrixCommand.run()執(zhí)行的隔離策略。默認為 THREAD
- execution.isolation.thread.timeoutInMilliseconds:該屬性用來配置 HystrixCommand 執(zhí)行的超時時間,單位為毫秒。
- execution.timeout.enabled:該屬性用來配置 HystrixCommand.run()的執(zhí)行是否啟用超時時間。默認為 true。
- execution.isolation.thread.interruptOnTimeout:該屬性用來配置當(dāng) HystrixCommand.run()執(zhí)行超時的時候是否要它中斷。
- execution.isolation.thread.interruptOnCancel:該屬性用來配置當(dāng) HystrixCommand.run()執(zhí)行取消時是否要它中斷。
- execution.isolation.semaphore.maxConcurrentRequests:當(dāng) HystrixCommand 命令的隔離策略使用信號量時,該屬性用來配置信號量的大小。當(dāng)最大并發(fā)請求達到該設(shè)置值時,后續(xù)的請求將被拒絕。
- Fallback:用來控制 HystrixCommand.getFallback()的執(zhí)行
- fallback.isolation.semaphore.maxConcurrentRequests:該屬性用來設(shè)置從調(diào)用線程中允許 HystrixCommand.getFallback()方法執(zhí)行的最大并發(fā)請求數(shù)。當(dāng)達到最大并發(fā)請求時,后續(xù)的請求將會被拒絕并拋出異常。
- fallback.enabled:該屬性用來設(shè)置服務(wù)降級策略是否啟用,默認是 true。如果設(shè)置為 false,當(dāng)請求失敗或者拒絕發(fā)生時,將不會調(diào)用 HystrixCommand.getFallback()來執(zhí)行服務(wù)降級邏輯。
- Circuit Breaker:用來控制 HystrixCircuitBreaker 的行為。
- circuitBreaker.enabled:確定當(dāng)服務(wù)請求命令失敗時,是否使用斷路器來跟蹤其健康指標和熔斷請求。默認為 true。
- circuitBreaker.requestVolumeThreshold:用來設(shè)置在滾動時間窗中,斷路器熔斷的最小請求數(shù)。例如,默認該值為 20 的時候,如果滾動時間窗(默認 10 秒)內(nèi)僅收到 19 個請求,即使這 19 個請求都失敗了,斷路器也不會打開。
- circuitBreaker.sleepWindowInMilliseconds:用來設(shè)置當(dāng)斷路器打開之后的休眠時間窗。休眠時間窗結(jié)束之后,會將斷路器設(shè)置為“半開”狀態(tài),嘗試熔斷的請求命令,如果依然時候就將斷路器繼續(xù)設(shè)置為“打開”狀態(tài),如果成功,就設(shè)置為“關(guān)閉”狀態(tài)。
- circuitBreaker.errorThresholdPercentage:該屬性用來設(shè)置斷路器打開的錯誤百分比條件。默認值為 50,表示在滾動時間窗中,在請求值超過 requestVolumeThreshold 閾值的前提下,如果錯誤請求數(shù)百分比超過 50,就把斷路器設(shè)置為“打開”狀態(tài),否則就設(shè)置為“關(guān)閉”狀態(tài)。
- circuitBreaker.forceOpen:該屬性默認為 false。如果該屬性設(shè)置為 true,斷路器將強制進入“打開”狀態(tài),它會拒絕所有請求。該屬性優(yōu)于 forceClosed 屬性。
- circuitBreaker.forceClosed:該屬性默認為 false。如果該屬性設(shè)置為 true,斷路器強制進入“關(guān)閉”狀態(tài),它會接收所有請求。如果 forceOpen 屬性為 true,該屬性不生效。
- Metrics:該屬性與 HystrixCommand 和 HystrixObservableCommand 執(zhí)行中捕獲的指標相關(guān)。
- metrics.rollingStats.timeInMilliseconds:該屬性用來設(shè)置滾動時間窗的長度,單位為毫秒。該時間用于斷路器判斷健康度時需要收集信息的持續(xù)時間。斷路器在收集指標信息時會根據(jù)設(shè)置的時間窗長度拆分成多個桶來累計各度量值,每個桶記錄了一段時間的采集指標。例如,當(dāng)為默認值 10000 毫秒時,斷路器默認將其分成 10 個桶,每個桶記錄 1000 毫秒內(nèi)的指標信息。
- metrics.rollingStats.numBuckets:用來設(shè)置滾動時間窗統(tǒng)計指標信息時劃分“桶”的數(shù)量。默認值為 10。
- metrics.rollingPercentile.enabled:用來設(shè)置對命令執(zhí)行延遲是否使用百分位數(shù)來跟蹤和計算。默認為 true,如果設(shè)置為 false,那么所有的概要統(tǒng)計都將返回-1。
- metrics.rollingPercentile.timeInMilliseconds:用來設(shè)置百分位統(tǒng)計的滾動窗口的持續(xù)時間,單位為毫秒。
- metrics.rollingPercentile.numBuckets:用來設(shè)置百分位統(tǒng)計滾動窗口中使用桶的數(shù)量。
- metrics.rollingPercentile.bucketSize:用來設(shè)置每個“桶”中保留的最大執(zhí)行數(shù)。
- metrics.healthSnapshot.intervalInMilliseconds:用來設(shè)置采集影響斷路器狀態(tài)的健康快照的間隔等待時間。
- Request Context:涉及 HystrixCommand 使用 HystrixRequestContext 的設(shè)置。
- requestCache.enabled:用來配置是否開啟請求緩存。
- requestLog.enabled:用來設(shè)置 HystrixCommand 的執(zhí)行和事件是否打印到日志的 HystrixRequestLog 中。
7.threadPoolProperties
threadPoolProperties = {@HystrixProperty(name = "coreSize",value = "2"),@HystrixProperty(name = "allowMaximumSizeToDivergeFromCoreSize",value="true"),@HystrixProperty(name = "maximumSize",value="2"),@HystrixProperty(name = "maxQueueSize",value="2")},-
如果請求量少,達不到 coreSize,通常會使用核心線程來執(zhí)行任務(wù)。
-
如果設(shè)置了 maxQueueSize,當(dāng)請求數(shù)超過了 coreSize, 通常會把請求放到 queue 里,待核心線程有空閑時消費。
-
如果 queue 長度無法滿足存儲請求,則會創(chuàng)建新線程執(zhí)行直到達到 maximumSize 最大線程數(shù),多出核心線程數(shù)的線程會在空閑時回收。
8.常用限流方式?
- Hystriⅸ 限流
- Nginx
- Redis+Lua
- Sentinel
- 基于限流算法自己實現(xiàn)(令牌桶、漏桶慎法)
9.熔斷器什么場景下會失效
10.什么是 Dashboard?
Hystrix Dashboard 是一個通過收集 actuator 端點提供的 Hystrix 流數(shù)據(jù),因此在這些被斷路器保護的應(yīng)用中需要開啟 Hystrix 流的 Actuator 端點,并將其圖表化的客戶端。如果需要通過圖表化的界面查看被斷路器保護的方法相關(guān)調(diào)用信息、或者實時監(jiān)控這些被斷路器保護的應(yīng)用的健康情況,就可以使用 Hystrix Dashboard。
11.Dashboard 使用
#監(jiān)控鏈接 http://localhost:8080/hystrix.stream http://localhost:8080/actuator/hystrix.stream#請求下hystrix的接口 http://localhost:8080/portal/hystrix/1 #訪問鏈接 http://localhost:9909/hystrixportal中需要加入
@Bean public ServletRegistrationBean getServlet() {HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);registrationBean.setLoadOnStartup(1);registrationBean.addUrlMappings("/hystrix.stream");registrationBean.setName("HystrixMetricsStreamServlet");return registrationBean; }dashboard中需要加入
hystrix:dashboard:proxy-stream-allow-list: "localhost"12.儀表盤解讀
-
實心圓:共有兩種含義。它通過顏色的變化代表了實例的健康程度
-
該實心圓除了顏色的變化之外,它的大小也會根據(jù)實例的請求流量發(fā)生變化,流量越大該實心圓就越大。所以通過該實心圓的展示,就可以在大量的實例中快速的發(fā)現(xiàn)故障實例和高壓力實例。
-
曲線:用來記錄 2 分鐘內(nèi)流量的相對變化,可以通過它來觀察到流量的上升和下降趨勢。
在圖表中,左上角的圓圈代表了該方法的流量和狀態(tài):
- 圓圈越大代表方法流量越大
- 圓圈為綠色代表斷路器健康、黃色代表斷路器偶發(fā)故障、紅色代表斷路器故障
右上角的計數(shù)器(三列數(shù)字):
第一列從上到下
-
綠色代表當(dāng)前成功調(diào)用的數(shù)量
-
藍色代表短路請求的數(shù)量
-
藍綠色代表錯誤請求的數(shù)量
第二列從上到下
-
黃色代表超時請求的數(shù)量
-
紫色代表線程池拒絕的數(shù)量
-
紅色代表失敗請求的數(shù)量
第三列
- 過去 10s 的錯誤請求百分比
Thread Pools:
Hystrix 會針對一個受保護的類創(chuàng)建一個對應(yīng)的線程池,這樣做的目的是 Hystrix 的命令被調(diào)用的時候,不會受方法請求線程的影響(或者說 Hystrix 的工作線程和調(diào)用者線程相互之間不影響)
左下角從上至下:
-
Active 代表線程池中活躍線程的數(shù)量
-
Queued 代表排隊的線程數(shù)量,該功能默認禁止,因此默認情況下始終為 0
-
Pool Size 代表線程池中線程的數(shù)量(上面圖我搞錯了,困得死 MMP)
右下角從上至下:
-
Max Active 代表最大活躍線程,這里展示的數(shù)據(jù)是當(dāng)前采用周期中,活躍線程的最大值
-
Execcutions 代表線程池中線程被調(diào)用執(zhí)行 Hystrix 命令的次數(shù)
-
Queue Size 代表線程池隊列的大小,默認禁用,無意義
13.turbine 的作用
監(jiān)控數(shù)據(jù)聚合
Turbine 是聚合服務(wù)器發(fā)送事件流數(shù)據(jù)的一個工具,Hystrix 的監(jiān)控中,只能監(jiān)控單個節(jié)點,實際生產(chǎn)中都為集群,因此可以通過 Turbine 來監(jiān)控集群下 Hystrix 的 metrics 情況,同一個服務(wù)負載均衡情況下,可以監(jiān)控多個實例.hosts>1
14.turbine 使用
http://localhost:8081/portal/hystrix/1 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-turbine</artifactId> </dependency> @EnableTurbine @EnableEurekaClient @EnableHystrixDashboard @SpringBootApplication public class HystrixTurbineApplication {public static void main(String[] args) {SpringApplication.run(HystrixTurbineApplication.class, args);}} #端口號 server:port: 9999spring:application:name: hystrix-turbine-service #服務(wù)名稱#服務(wù)提供者 eureka:client:service-url:defaultZone: http://eureka8767:8767/eureka/,http://eureka8768:8768/eureka/,http://eureka8769:8769/eureka/instance:lease-renewal-interval-in-seconds: 2 #每間隔2s,向服務(wù)端發(fā)送一次心跳,證明自己依然"存活”lease-expiration-duration-in-seconds: 10 #告訴服務(wù)端,如果我10s之內(nèi)沒有給你發(fā)心跳,就代表我故障了,將我踢出掉prefer-ip-address: true #告訴服務(wù)端,服務(wù)實例以IP作為鏈接,而不是取機器名instance-id: springcloud-service-turbine-9999 #告訴服務(wù)端,服務(wù)實例的id,id要是唯一的# 開啟Feign對Hystrix的支持 feign:hystrix:enabled: trueclient:config:default:connectTimeout: 5000 # 指定Feign連接提供者的超時時限readTimeout: 5000 # 指定Feign從請求到獲取提供者響應(yīng)的超時時限# 開啟actuator的所有web終端 management:endpoints:web:exposure:include: "*"# 設(shè)置服務(wù)熔斷時限 hystrix:dashboard:proxy-stream-allow-list: "localhost"command:default:execution:isolation:thread:timeoutInMilliseconds: 3000turbine:app-config: portal-servicecluster-name-expression: new String("default")combine-host-port: trueinstanceUrlSuffix: /actuator/hystrix.stream #turbine默認監(jiān)控actuator/路徑下的端點,修改直接監(jiān)控hystrix.stream#cluster-name-expression: metadata['cluster']#aggregator:# cluster-config: ribbon#instanceUrlSuffix: /hystrix.stream #turbine默認監(jiān)控actuator/路徑下的端點,修改直接監(jiān)控hystrix.stream15.@SpringCloudApplication
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker public @interface SpringCloudApplication { }- @SpringBootApplication
- @EnableDiscoveryClient
- @EnableCircuitBreaker
組合注解,SpringBootApplication,注冊中心客戶端 EnableDiscoveryClient,支持 hystrix 的 EnableCircuitBreaker
16.超時時間
Ribbon 和 Hystrix和Feign 的超時時間配置的關(guān)系
- 在 Spring Cloud 中使用 Feign 進行微服務(wù)調(diào)用分為兩層:Hystrix 的調(diào)用和 Ribbon 的調(diào)用,Feign 自身的配置會被覆蓋。
- Hystrix 的超時時間和 ribbon 的超時時間存在一定的關(guān)系
- 說明下,如果不啟用 Hystrix,Feign 的超時時間則是 Ribbon 的超時時間,Feign 自身的配置也會被覆蓋
五.zuul
1.微服務(wù)網(wǎng)關(guān)?
服務(wù)網(wǎng)關(guān)是微服務(wù)架構(gòu)中一個不可或缺的部分。通過服務(wù)網(wǎng)關(guān)統(tǒng)一向外系統(tǒng)提供 REST API 的過程中,除了具備服務(wù)路由、均衡負載功能之外,它還具備了權(quán)限控制等功能。Spring Cloud Netflix 中的 Zuul 就擔(dān)任了這樣的一個角色,為微服務(wù)架構(gòu)提供了前門保護的作用,同時將權(quán)限控制這些較重的非業(yè)務(wù)邏輯內(nèi)容遷移到服務(wù)路由層面,使得服務(wù)集群主體能夠具備更高的可復(fù)用性和可測試性。
Zuu 包含了對請求的路由和過濾 兩個最主要的功能:
其中路由功能負責(zé)將外部請求轉(zhuǎn)發(fā)到具體的微服務(wù)實例上是實現(xiàn)外部訪問統(tǒng)一入口的基礎(chǔ),過濾功能則負責(zé)對請求的處理過程進行干預(yù),是實現(xiàn)請求校驗、服務(wù)聚合等功能的基礎(chǔ);
Zuul 和 Eureka 進行整合,將 Zuul 自身注冊為 Eureka 服務(wù)治理下的應(yīng)用,同時從 Eureka 中獲得其他微服務(wù)的信息,也即以后的訪問微服務(wù)都是通過 Zuul.
2.Zuul 網(wǎng)關(guān)的作用
網(wǎng)關(guān)有以下幾個作用:
- 統(tǒng)一入口:未全部為服務(wù)提供一個唯一的入口,網(wǎng)關(guān)起到外部和內(nèi)部隔離的作用,保障了后臺服務(wù)的安全性。
- 鑒權(quán)校驗:識別每個請求的權(quán)限,拒絕不符合要求的請求。
- 動態(tài)路由:動態(tài)的將請求路由到不同的后端集群中。
- 減少客戶端與服務(wù)端的耦合:服務(wù)可以獨立發(fā)展,通過網(wǎng)關(guān)層來做映射。
3.zuul 的功能?
使用 Zuul 的過濾器, 可以對請求的一些列信息進行安全認證/權(quán)限認證/身份識別/限流/記錄日志等功能。只需要自定義 Filter 后繼承 ZuulFilter 類即可。重寫其 filterType、filterOrder、shouldFilter 以及 run 方法。 filterType 表示 filter 執(zhí)行的時機:
其 value 值含義如下所示:
- pre:在請求被路由之前調(diào)用 比如:登錄驗證
- routing: 在請求被路由之中調(diào)用
- post: 在請求被路由之后調(diào)用
- error: 處理請求發(fā)生錯誤時調(diào)用
- filterOrder 表示執(zhí)行的優(yōu)先級,值越小表示優(yōu)先級越高
- shouldFilter 則表示該 filter 是否需要執(zhí)行
4.路由配置
zuul:sensitiveHeaders: Cookie,Set-Cookie,Authorizationroutes:portal:path: /portal-service/** #訪問路徑:http:/localhost:8888/portal-service/portal/1service-id: portal-servicegoods:path: /goods-service/** #http:/localhost:8888/goods-service/kwanGoodsInfo/1service-id: goods-servicehost:connect-timeout-millis: 5000 #超時時間prefix: /api #訪問路徑:http:/localhost:8888/api/portal-service/portal/1 http:/localhost:8888/api/goods-service/kwanGoodsInfo/1retryable: trueignored-services: portal-service #感略某個服務(wù)名,禁止通過該服務(wù)名訪可# ignored-services: * #禁止通過所有的服務(wù)名訪間ignored-patterns: /**/feign/** #不給匹配此棋式的路徑進行路由·那么你到時候訪間不到LogFilter:route:disable: true #用LogFilter過濾器4.過濾器
filter 是 zuul 的核心組件,zuul 大部分功能都是通過過濾器來實現(xiàn)的。zuul 中定義了 4 種標準過濾器類型,這些過濾器類型對應(yīng)于清求的典型生命周期。
- PRE:這種過濾器在請求被路由之前調(diào)用。可利用這種過濾器實現(xiàn)身份驗證、在集群中選擇請求的微服務(wù)、記錄調(diào)試信息等。
- ROUTING:這種過濾器將請求路由到微服務(wù)。這種過濾器用于構(gòu)建發(fā)送給微服務(wù)的請求,并使用 Apache HttpClient:或 Netfilx Ribbon 請求微服務(wù)
- POST:這種過濾器在路由到微服務(wù)以后執(zhí)行。這種過濾器可用來為響應(yīng)添加標準的 HTTP Header 收集統(tǒng)計信息和指標、將響應(yīng)從微服務(wù)發(fā)送給客戶端等。
- ERROR:在其他階段發(fā)生錯誤時執(zhí)行該過濾器。
5.過濾器禁用
zuul 過濾器的禁用,Spring Cloud 默認為 Zuul 編寫并啟用了一些過濾器,例妝如 DebugFilter,
FormBodyWrapperFilter 等,這些過濾器都存放在 spring-cloud-netflix-zuul 這個 jr 包里,一些場景下,想要禁用掉部分過濾器,該怎么辦呢?只需在 application.properties 里設(shè)置 zuul…disable=true 例如,要禁用上面我們寫的過濾器,這樣配置就行了:
zuul.LogFilter.route.disable=true6.Zuul 的熔斷降級
zuul 是一個代理服務(wù),但如果被代理的服務(wù)突然斷了,這個時候 zuul 上面會有出錯信息,例如,停止了被調(diào)用的微服務(wù);一般服務(wù)方自己會進行服務(wù)的熔斷降級,但對于 zuul 本身,也應(yīng)該進行 zuul 的降級處理:
Zuul 的 fallback 容錯處理邏輯,只針對 timeout 異常處理,當(dāng)請求被 Zuul 路由后,只要服務(wù)有返回(包括異常),都不會觸發(fā)Zuul的fallback容錯邏輯。
因為對于 Zuul 網(wǎng)關(guān)來說,做請求路由分發(fā)的時候,結(jié)果由遠程服務(wù)運算的。那么遠程服務(wù)反饋了異常信息,Zuul 網(wǎng)關(guān)不會處理異常,因為無法確定這個錯誤是否是應(yīng)用真實想要反饋給客戶端的。
六.config
1.分布式配置中心
Spring Cloud Config 為服務(wù)端和客戶端提供了分布式系統(tǒng)的外部化配置支持。
配置服務(wù)器默認采用 git 來存儲配置信息,這樣就有助于對環(huán)境配置進行版本管理,并且可以通過 git 客戶端工具來方便的管理和訪問配置內(nèi)容。當(dāng)然他也提供本地化文件系統(tǒng)的存儲方式
配置中心用途 :
- 變量獲取
- 加解密
- 自動刷新
- 高可用
- 安全認證
2.為什么需要分布式配置中心
在分布式微服務(wù)體系中,服務(wù)的數(shù)量以及配置信息日益增多,比如各種服務(wù)器參數(shù)配置、各種數(shù)據(jù)庫訪問參數(shù)配置、各種環(huán)境下配置信息的不同、配置信息修改之后實時生效等等,傳統(tǒng)的配置文件方式或者將配置信息存放于數(shù)據(jù)庫中的方式已無法滿足開發(fā)人員對配置管理的要求,如:
- 安全性:配置跟隨源代碼保存在代碼庫中,容易造成配置泄漏
- 時效性:修改配置,需要重啟服務(wù)才能生效
- 局限性:無法支持動態(tài)調(diào)整:例如日志開關(guān)、功能開關(guān)
3.常用分布式配置中心框架
- Apollo(阿波羅):攜程框架部門研發(fā)的分布式配置中心,能夠集中化管理應(yīng)用不同環(huán)境、不同集群的配置,配置修改后能夠?qū)崟r推送到應(yīng)用端,并且具備規(guī)范的權(quán)限、流程治理等特性,適用于微服務(wù)配置管理場景
- diamond:淘寶開源的持久配置中心,支持各種持久信息(比如各種規(guī)則,數(shù)據(jù)庫配置等)的發(fā)布和訂閱
- XDiamond:全局配置中心,存儲應(yīng)用的配置項,解決配置混亂分散的問題,名字來源于淘寶的開源項目 diamond,前面加上一個字母 X 以示區(qū)別。
- Qconf:奇虎 360 內(nèi)部分布式配置管理工具,用來替代傳統(tǒng)的配置文件,使得配置信息和程序代碼分離,同時配置變化能夠?qū)崟r同步到客戶端,而且保證用戶高效讀取配置,這使的工程師從瑣碎的配置修改、代碼提交、配置上線流程中解放出來,極大地簡化了配置管理工作;
- Disconf:百度的分布式配置管理平臺,專注于各種分布式系統(tǒng)配置管理的通用組件和通用平臺,提供統(tǒng)一的配置管理服務(wù);
- Spring Cloud Config:Spring Cloud 微服務(wù)開發(fā)的配置中心,提供服務(wù)端和客戶端支持;
4.什么是 Spring Cloud Config?
Spring Cloud Config 是一個解決分布式系統(tǒng)的配置管理方案。它包含 Client 和 Server 兩個部分,Server 提供配置文件的存儲、以接口的形式將配置文件的內(nèi)容提供出去,Client 通過接口獲取數(shù)據(jù)、并依據(jù)此數(shù)據(jù)初始化自己的應(yīng)用。
Spring cloud 使用 git 或 svn、也可以是本地存放配置文件,默認情況下使用 git。
5.配置路由規(guī)則
/{application)-{profile}.properties
http://localhost:8888/application-dev.properties
/{label}/{application}-{profile}.properties
http://localhost:8888/master/application-dev.properties
/[application}-{profile}.yml
http://localhost:8888/master/application-dev.yml
/{label}/{application}-{profile}.yml
http://localhost:8888/master/application-dev.properties
其中:
{application}表示配置文件的名字,對應(yīng)的配置文件即 application,
{profile}表示環(huán)境,有dev、test、online 及默認,
{label}表示分支,默認我們放在 master 分支上,
通過瀏覽器上訪問http:/localhost:8888/application,/dev/master
6.加解密
加解密是借助 JCE 實現(xiàn)的,默認情況下我們的 JRE 中自帶了 JCE(Java Cryptography Extension),但是默認是一個有限長度的版本,我們這里需要一個不限長度的 JCE,JCE 是對稱加密,安全性相對非對稱加密低一點,但是使用更加方便一點
下載完成之后解壓,把得到到兩個 Jar 包復(fù)制到$JAVA_HOME\jre\lib\security 目錄下。
在我們的 configserver 項目中的 bootstrap.yml 配置文件中加入如下配置項:
encrypt:key: Thisismysecretkey訪問下面連接進行單個數(shù)據(jù)加密,這里是 post 請求,不是 get
#加密 http://localhost:7001/encrypt#解密 http:/192.168.6.817001/decrypt可能會報錯,需要添加賬號密碼
username='{cipher)516269d5f6af298bf843b31c847ad705d8305e784c394c7395a55a0742d8b69d'需要使用{cipher}的密文形式,單引號不要忘記了。然后 push 到遠程。
加解密在 config-server 服務(wù)中進行的,解密過程不需要在客戶端進行配置
7.加密過程安全認證
1.添加依賴
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId> </dependency>2.config 配置
在 config 的配置文件中配置
spring:security:user:password: user123 #配置登陸的密碼是user123name: user #配置登陸的賬戶是user或者
顯式指定賬號密碼,指定賬戶密碼的優(yōu)先級要高于 URI
spring:cloud:config:label: ${spring.profiles.active} #指定Git倉庫的分支,對應(yīng)config server 所獲取的配置文件的{label}discovery:enabled: true #表示使用服務(wù)發(fā)現(xiàn)組件中的configserver ,而不是自己指定config server的uri,默認為falseservice-id: microservice-config-server #服務(wù)發(fā)現(xiàn)中configserver的serverIdfail-fast: true #失敗快速響應(yīng)username: userpassword: user1233.客戶端配置
spring:cloud:config:uri: http://user:user123@localhost:7001/7.局部刷新
Spring Boot 的 actuator 提供了一個刷新端點/refresh,添加依賴
spring-boot-starter-actuator,可用于配置的刷新;
<!--springboot的一個監(jiān)控actuator--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId> </dependency>在 Controller 上添加注解@RefreshScope,添加這個注解的類會在配置更新時得到特殊的處理;
打開 web 訪問端點
management,endpoints.web.exposure.include=“*”訪問http:/localhost:8080/actuator/refresh 進行手動刷新配置;必須要 post 方式訪問這個接口
8.詳解@RefreshScope
8.全局刷新
全局刷新需要使用到消息總線
前面使用/actuator/refresh 端點手動刷新配置雖然可以實現(xiàn)刷新,但所有微服務(wù)節(jié)點的配置都需要手動去刷新,如果微服務(wù)非常多,其工作量非常龐大。因此實現(xiàn)配置的自動刷新是志在必行,Spring Cloud Bus 就可以用來實現(xiàn)配置的自動刷新;
Spring Cloud Bus 使用輕量級的消息代理(例如 RabbitMQ、Kafka 等)廣播傳播狀態(tài)的更改(例如配置的更新)或者其他的管理指令,可以將 Spring Clou Bus 想象成一個分布式的 Sprina Boot Actuator;
#形置rabbitmq spring.rabbitmq.host=192.168.10.128 spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password-guest #開啟spring cloud bus默認是開啟的,也可以省略速形置 spring.cloud.bus.enabled=true 打開所有的web訪問端點 management.endpoints.web.exposure.include=*各個微服務(wù)(客戶端),其他各個微服務(wù)用于接收消息,那么也需要有 spring cloud bus 的依賴和 RabbitMQ 的連接信息;I
然后 post 方式請求地址,如果返回成功,則 RabbitMQ 將收到消息,然后微服務(wù)會消費消息,config 的所有客戶端的微服務(wù)配置都會動態(tài)刷新;
http:/localhost::8888/actuator//bus-refresh2.什么是總線
在微服務(wù)架構(gòu)的系統(tǒng)中,通常會使用輕量級的消息代理來構(gòu)建一個共用的消息主題,并讓系統(tǒng)中所有微服務(wù)實例都連接上來。由于該主題中產(chǎn)生的消息會被所有實例監(jiān)聽和消費,所以稱它為消息總線。在總線上的各個實例,都可以方便地廣播一些需要讓其他連接在該主題上的實例都知道的消息。
3.消息總線原理
ConfigClient 實例都監(jiān)聽 MQ 中同一個 topic(默認是 springCloudBus)。當(dāng)一個服務(wù)刷新數(shù)據(jù)的時候,它會把這個信息放入到 Topic 中,這樣其它監(jiān)聽同一 Topic 的服務(wù)就能得到通知,然后去更新自身的配置。
通過使用 Spring Cloud Bus 與 Spring Cloud Config 的整合,并以 RabbitMQ 作為消息代理,實現(xiàn)了應(yīng)用配置的動態(tài)更新。
4.SpringCloudBus
Spring Cloud Bus 是用來將分布式系統(tǒng)的節(jié)點與輕量級消息系統(tǒng)鏈接起來的框架,它整合了 Java 的事件處理機制和消息中間件的功能。
Spring Clud Bus 目前支持 RabbitMQ 和 Kafka。
Spring Cloud Bus 能管理和傳播分布式系統(tǒng)間的消息,就像一個分布式執(zhí)行器,可用于廣播狀態(tài)更改、事件推送等,也可以當(dāng)作微服務(wù)間的通信通道。
利用消息總線觸發(fā)一個服務(wù)端 ConfigServer 的/bus/refresh 端點,而刷新所有客戶端的配置
當(dāng)我們將系統(tǒng)啟動起來之后,“Service A”的三個實例會請求 Config Server 以獲取配置信息,Config Server 根據(jù)應(yīng)用配置的規(guī)則從 Git 倉庫中獲取配置信息并返回。
此時,若我們需要修改“Service A”的屬性。首先,通過 Git 管理工具去倉庫中修改對應(yīng)的屬性值,但是這個修改并不會觸發(fā)“Service A”實例的屬性更新。我們向“Service A”的實例 3 發(fā)送 POST 請求,訪問/bus/refresh接口。此時,“Service A”的實例 3 就會將刷新請求發(fā)送到消息總線中,該消息事件會被“Service A”的實例 1 和實例 2 從總線中獲取到,并重新從 Config Server 中獲取他們的配置信息,從而實現(xiàn)配置信息的動態(tài)更新。
而從 Git 倉庫中配置的修改到發(fā)起/bus/refresh的 POST 請求這一步可以通過 Git 倉庫的 Web Hook 來自動觸發(fā)。由于所有連接到消息總線上的應(yīng)用都會接受到更新請求,所以在 Web Hook 中就不需要維護所有節(jié)點內(nèi)容來進行更新,從而解決了通過 Web Hook 來逐個進行刷新的問題。
通過destination參數(shù)來指定需要更新配置的服務(wù)或?qū)嵗?/p>
七.Sleuth
1.什么是 Sleuth?
Spring Cloud Sleuth 是 Spring Cloud 提供的分布式系統(tǒng)服務(wù)鏈追蹤組件,它大量借用了 Google 的 Dapper,Twitter 的 Zipkin。學(xué)習(xí) Spring Cloud Sleuth,最好先對 Zipkin 有一些了解,對 span、trace 這些概念有相應(yīng)的認識。
2.為什么需要 Sleuth?
鏈路追蹤:通過 Sleuth 可以很清楚的看出一個請求都經(jīng)過了那些服務(wù),可以很方便的理清服務(wù)間的調(diào)用關(guān)系等。
性能分析:通過 Sleuth 可以很方便的看出每個采樣請求的耗時,分析哪些服務(wù)調(diào)用耗時,當(dāng)服務(wù)調(diào)用的耗時隨著請求量的增大而增大時, 可以對服務(wù)的擴容提供一定的提醒。
數(shù)據(jù)分析,優(yōu)化鏈路:對于頻繁調(diào)用一個服務(wù),或并行調(diào)用等,可以針對業(yè)務(wù)做一些優(yōu)化措施。
可視化錯誤:對于程序未捕獲的異常,可以配合 Zipkin 查看。
- 快速發(fā)現(xiàn)問題
- 判斷故障影響范圍
- 梳理服務(wù)依賴以及依賴的合理性
- 分析鏈路性能問題以及實時容量規(guī)劃
單純的理解鏈路追蹤,就是將一次分布式請求還原成調(diào)用鏈路,進行日志記錄,性能監(jiān)控并將一次分布式請求的調(diào)用情況集中展示。比如各個服務(wù)節(jié)點上的耗時、請求具體到達哪臺機器上、每個服務(wù)節(jié)點的請求狀態(tài)等等。
3.鏈路追蹤相關(guān)產(chǎn)品
常見的鏈路追蹤技術(shù)有下面這些 :
cat:由大眾點評開源,基于 Java 開發(fā)的實時應(yīng)用監(jiān)控平臺,包括實時應(yīng)用監(jiān)控,業(yè)務(wù)監(jiān)控 。 集成方案是通過代碼埋點的方式來實現(xiàn)監(jiān)控,比如: 攔截器,過濾器等。 對代碼
的侵入性很大,集成成本較高。風(fēng)險較大。
zipkin:由 Twitter 公司開源,開放源代碼分布式的跟蹤系統(tǒng),用于收集服務(wù)的定時數(shù)據(jù),以解決微服務(wù)架構(gòu)中的延遲問題,包括:數(shù)據(jù)的收集、存儲、查找和展現(xiàn)。該產(chǎn)品結(jié)合
spring-cloud-sleuth 使用較為簡單, 集成很方便, 但是功能較簡單。
pinpoint:Pinpoint 是韓國人開源的基于字節(jié)碼注入的調(diào)用鏈分析,以及應(yīng)用監(jiān)控分析工具。特點是支持多種插件, UI 功能強大,接入端無代碼侵入。
skywalking:本土開源的基于字節(jié)碼注入的調(diào)用鏈分析,以及應(yīng)用監(jiān)控分析工具。特點是支持多種插件, UI 功能較強,接入端無代碼侵入。目前已加入 Apache 孵化器。
Sleuth:SpringCloud 提供的分布式系統(tǒng)中鏈路追蹤解決方案。
注意: SpringCloud alibaba 技術(shù)棧中并沒有提供自己的鏈路追蹤技術(shù)的,我們可以采用Sleuth +Zinkin來做鏈路追蹤解決方案
4.基本術(shù)語?
Sleuth 基本概念涉及到三個專業(yè)術(shù)語: span、Trace、Annotations。
span :基本工作單位,每次發(fā)送一個遠程調(diào)用服務(wù)就會產(chǎn)生一個 Span。Span 是一個 64 位的唯一 ID。通過計算 Span 的開始和結(jié)束時間,就可以統(tǒng)計每個服務(wù)調(diào)用所花費的時間。。
Trace :一系列 Span 組成的樹狀結(jié)構(gòu),一個 Trace 認為是一次完整的鏈路,內(nèi)部包含 n 多個 Span。Trace 和 Span 存在一對多的關(guān)系,Span 與 Span 之間存在父子關(guān)系。
Annotations :用來及時記錄一個事件的存在,一些核心 annotations 用來定義一個請求的開始和結(jié)束。
開始與結(jié)束:
- cs(Client Sent) :客戶端發(fā)起一個請求,這個 annotation 描述了這個 span 的開始;
- sr(Server Received) :服務(wù)端獲得請求并準備開始處理它,如果 sr 減去 cs 時間戳便可得到網(wǎng)絡(luò)延遲;
- ss(Server Sent) :請求處理完成(當(dāng)請求返回客戶端),如果 ss 減去 sr 時間戳便可得到服務(wù)端處理請求需要的時間;
- cr(Client Received) :表示 span 結(jié)束,客戶端成功接收到服務(wù)端的回復(fù),如果 cr 減去 cs 時間戳便可得到客戶端從服務(wù)端獲取回復(fù)的所有所需時間。
核心: 為什么能夠進行整條鏈路的追蹤? 其實就是一個 Trace ID 將 一連串的 Span 信息連起來了。根據(jù) Span 記錄的信息再進行整合就可以獲取整條鏈路的信息。
5.什么是 zipkin
Zipkin 是一種分布式跟蹤系統(tǒng)。它有助于收集解決微服務(wù)架構(gòu)中的延遲問題所需的時序數(shù)據(jù)。它管理這些數(shù)據(jù)的收集和查找。Zipkin 的設(shè)計基于 Google Dapper 論文。應(yīng)用程序用于向 Zipkin 報告時序數(shù)據(jù)。Zipkin UI 還提供了一個依賴關(guān)系圖,顯示了每個應(yīng)用程序通過的跟蹤請求數(shù)。如果要解決延遲問題或錯誤,可以根據(jù)應(yīng)用程序,跟蹤長度,注釋或時間戳對所有跟蹤進行篩選或排序。選擇跟蹤后,您可以看到每個跨度所需的總跟蹤時間百分比,從而可以識別有問題的應(yīng)用程序。
6.zipkin 的坑
SpringBoot2.2.x 以后的版本 集成 zipkin 的方式改變了,原來是通過@EnablezipkinServer注解,現(xiàn)在這個注解不起作用了。
服務(wù)端應(yīng)該通過下載 jar 包,然后 運行 jar 包來集成 。 可以選擇版本下載,要選擇后綴為 -exec.jar 的,之后切換到 jar 包在的路徑,然后用 java -jar 的方式啟動就可以了(默認端口號是 9411 ,所以如果提供了 zipkin client,需要把它的配置文件中的端口號改成 9411,對應(yīng)的配置應(yīng)該是 zipkin: base-url: http://localhost:9411)
spring:cloud:config:fail-fast: false #客戶端連接失敗時開啟重試,需要結(jié)合spring-retry、spring-aoplabel: main #獲取配置文件的分支名name: application-portal8081 #獲取的文件名profile: portal8081 #文件后綴uri: http://localhost:7001 #配置中心服務(wù)端地址zipkin:base-url: http://localhost:94117.zipkin 下載安裝
1.下載地址
https://repo1.maven.org/maven2/io/zipkin/zipkin-server/2.下載 jar 包
可以使用迅雷下載,或者科學(xué)上網(wǎng)
3.啟動服務(wù)端
啟動報錯,看看端口是否被占用
java -jar zipkin-server-2.23.9-exec.jar4.訪問控制臺
http://127.0.0.1:9411/zipkin8.客戶端配置
1.pom 依賴
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>2.配置文件
#端口號,指定端口,不然默認是8080,會出現(xiàn)沖突 server:port: 8081spring:cloud:config:fail-fast: false #客戶端連接失敗時開啟重試,需要結(jié)合spring-retry、spring-aoplabel: main #獲取配置文件的分支名name: application-portal8081 #獲取的文件名profile: portal8081 #文件后綴uri: http://localhost:7001 #配置中心服務(wù)端地址zipkin:base-url: http://localhost:9411 #zipkin地址 默認值就是0.1,代表收集10%的請求追蹤信息。discovery-client-enabled: falsesender:type: websleuth:sampler:percentage: 0.1 #收集百分比 eureka:client:service-url:defaultZone: http://eureka8767:8767/eureka/,http://eureka8768:8768/eureka/,http://eureka8769:8769/eureka/9.控制臺
需要調(diào)用一下服務(wù),才能在控制臺查詢到
- 可以看到請求過的鏈路信息
- 包含的微服務(wù)
- 耗時信息
- 還能通過篩選條件進行查詢
- 可以通過 trace id 進行查詢
詳細面板可以看到 :
- 具體的統(tǒng)計信息
- 統(tǒng)計耗時信息,開始時間以及結(jié)束時間,以及每一段的耗時信息
- 標簽信息,接口信息
- 請求方式,請求方法
- Span ID
- Trace ID
- 服務(wù)名
- 服務(wù)數(shù)
- 深度
- 跨度總數(shù)
10.依賴關(guān)系圖
11.采樣數(shù)據(jù)
zikpin 日志采樣率設(shè)置為 0.1,就是 10 次只能有一次被記錄下來。當(dāng)時看到這個問題很郁悶,那 9 次的請求咋辦,日志不完整怎么排查錯誤呢?
日志其實是完整的,日志可以用 logback 收集并保存成文件。所有的請求信息 都會被這個文件所記錄,只是 zikpin 側(cè)重于鏈路追蹤,并不是排查錯誤,更多的是我們知道服務(wù)直接調(diào)用耗時和服務(wù)直接依賴關(guān)系而已。所以不需要完整日志,只需要 0.1 比例就夠了
sleuth:sampler:probability: 0.1而這個采樣其實只是對 zipkin 有效,只是在 zipkin 界面顯示 0.1 的日志而已,并不是后臺日志也都是只收集 0.1。
而 logback 會把我們所有請求信息全部記錄下來,不會遺漏,這樣的話就可以順利排錯。
12.數(shù)據(jù)持久化
zipkin 中的數(shù)據(jù)默認是保存在內(nèi)存中的,重啟 sleuth 服務(wù),數(shù)據(jù)會被清空,這顯然是不合適的.
zipkin 可以集成 elasticsearch,或者 mysql 進行數(shù)據(jù)的持久化,這樣服務(wù)重啟,數(shù)據(jù)不丟失.
總結(jié)
以上是生活随笔為你收集整理的【檀越剑指大厂—SpringCloudNetflix】SpringCloudNetflix高阶篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 英特尔的低于服务器的作用是什么,什么是英
- 下一篇: java ajax清除缓存_JS 清除浏