一 微服務架構概述
1. 單體應用架構存在的問題
結合:https://www.cnblogs.com/jialanshun/p/10637454.html一起看,在該篇博客中搜索“單塊架構的優缺點”
(1)復雜性高
以筆者經手的一個百萬行級別的單體應用為例,整個項目包含的模塊非常多、模塊的邊界模糊、依賴關系不
清晰、代碼質量參差不齊、混亂地堆砌在一起整個項目非常復雜。每次修改代碼都心驚膽戰,甚至添加一個簡單
的功能,或者修改一個Bug都會帶來隱含的缺陷。
(2)技術債務
隨着時間推移、需求變更和人員更迭,會逐漸形成應用程序的技術債務,並且越積越多。“不壞不修
(Notbroken,don'tfix)'',這在軟件開發中非常常見,在單體應用中這種思想更甚。已使用的系統設計或代碼難以
被修改,因為應用程序中的其他模塊可能會以意料之外的方式使用它。
(3)部署頻率低
隨着代碼的增多,構建和部署的時間也會增加。而在單體應用中,每次功能的變更或缺陷的修復都會導致需
要重新部署整個應用。全量部署的方式耗時長、影響范圍大、風險高,這使得單體應用項目上線部署的頻率較低。
而部署頻率低又導致兩次發布之間會有大量的功能變更和缺陷修復,出錯概率比較高。
(3)可靠性差
某個應用Bug,例如死循環、OOM等,可能會導致整個應用的崩潰。
(4)新人培養周期長
2. 什么是微服務架構
什么是微服務的架構和微服務架構的一些思想去博客https://www.cnblogs.com/jialanshun/p/10637454.html中看
二 Spring Cloud概述
1. Spring Cloud的版本號
如下圖所示,Spring Cloud是以英文單詞加SRX(X代表數字)來命名版本號的,其中英文單詞 Brixton、Angel、
Camden表示的是倫敦地鐵站的名稱,按照字母順序發行。SR表示“Service Release”,一般表示BUG修復,在SR版
本發行前,會先發行一個Release版本,例如“Camden Release”。
這樣定義版本的原因是因為SpringCloud是一個綜合項目,它包含很多的子項目。由於子項目也維護着自己的版
本號,springcloud采用了這種版本命名方式,從而避免與子項目的版本混淆。
3. Spring Cloud的前世今生
https://my.oschina.net/polly/blog/1790057
2. Spring Cloud和Spring Boot的兼容性
Spring Cloud |
Spring Boot |
Finchley |
兼容Spring Boot 2.0.x,不兼容Spring Boot 1.5.x |
Dalston和Edgware |
兼容Spring Boot 1.5.x,不兼容Spring Boot 2.0.x |
Camden |
兼容Spring Boot 1.4.x,也兼容Spring Boot 1.5.x |
Brixton |
兼容Spring Boot 1.3.x,也兼容Spring Boot 1.4.x |
Angel |
兼容Spring Boot 1.2.x |
可以去https://spring.io/projects/spring-cloud查看版本兼容性,這本書比較老,上面表格可能不是最新的
三 Eureka
SpringCloud支持多種注冊中心,如Eureka、Consul、Zookeeper,但是本文只講Eureka
1. Eureka簡介
(1)Eureka的基本原理
關於Eureka的原理參看:
https://blog.csdn.net/forezp/article/details/73017664
由圖可知,Eureka包含兩個組件,Eureka Server和Eureka Client
Eureka Server:提供服務注冊服務,各個節點啟動后會在Eureka Server進行注冊。Eureka Server之間通過復
制的方式完成數據的同步。在應用啟動后,將會向Eureka Server發送心跳,默認周期為30秒,
如果Eureka Server在多個心跳周期內沒有接收到某個節點的心跳,將會重服務器注冊表中把
這個服務節點移除(開啟自我保護除外)。
Eureka Client:是一個Java客戶端,用於簡化與Eureka Server的交互,該客戶端具備一個內置的使用輪詢負
載均算法的負載均衡器。Eureka提供客戶端緩存機制,即使所有的Eureka Server都掛掉,客
戶端依然可以利用緩存中的信息消費其它服務中的Api。
客戶端緩存的實現原理:
Eureka Client緩存機制很簡單,設置了一個每30秒執行一次的定時任務,定時去服務端獲
取注冊信息。獲取之后,存入本地內存。
綜上,Eureka通過心跳檢測、健康檢查、客戶端緩存等機制確保了系統的高可用性、靈活性和可伸縮性。
(2)Eureka包含的核心功能
(a)Register:服務注冊
當Eureka客戶端向Eureka Server注冊時,它提供自身的元數據,比如IP地址、端口,運行狀況指示符
URL,主頁等。
(b)Renew:服務續約
Eureka客戶會每隔30秒發送一次心跳來續約。 通過續約來告知Eureka Server該Eureka客戶仍然存在,沒
有出現問題。 正常情況下,如果Eureka Server在90秒沒有收到Eureka客戶的續約,它會將實例從其注冊
表中刪除。 建議不要更改續約間隔。
(c)Fetch Registries:獲取注冊列表信息
Eureka客戶端從服務器獲取注冊表信息,並將其緩存在本地。客戶端會使用該信息查找其他服務,從而進
行遠程調用。該注冊列表信息定期(每30秒鍾)更新一次。每次返回注冊列表信息可能與Eureka客戶端的
緩存信息不同, Eureka客戶端自動處理。如果由於某種原因導致注冊列表信息不能及時匹配,Eureka客戶
端則會重新獲取整個注冊表信息。 Eureka服務器緩存注冊列表信息,整個注冊表以及每個應用程序的信息
進行了壓縮,壓縮內容和沒有壓縮的內容完全相同。Eureka客戶端和Eureka 服務器可以使用JSON / XML
格式進行通訊。在默認的情況下Eureka客戶端使用壓縮JSON格式來獲取注冊列表的信息。
(d)Cancel:服務下線
Eureka客戶端在程序關閉時向Eureka服務器發送取消請求。 發送請求后,該客戶端實例信息將從服務器的
實例注冊表中刪除。該下線請求不會自動完成,它需要調用以下內容:
DiscoveryManager.getInstance().shutdownComponent();
2. 學習Eureka需要掌握的知識點
(1)Eureka集群配置
Eureka服務器端application.yml配置(以兩台為例):
(2)健康檢查:
如果Eureka Server在一定時間內(默認90秒)沒有接收到某個微服務實例的心跳,Eureka Server將會移除該
實例。
健康檢查配置(下面代碼是一個eureka客戶端而不是eureka server):
spring:
application:
name: consume-demo-hertbeat
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
#eureka客戶端發送給eureka服務器心跳的頻率
lease-renewal-interval-in-seconds: 5
#表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超時時間,在這個時間內若沒收到下一次心跳,則將移除該instance。
lease-expiration-duration-in-seconds: 10
同時應該在eureka server中關閉自我保護,示例代碼如下(下例是一個eureka server而不是eureka client):
spring:
application:
name: eurker-servier-heartbeat
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
server:
#自我保護配置(true 開啟,false 關閉)
enable-self-preservation: false
#(清理服務列表的時間間隔)
上面代碼中為了測試方便還增加了清理服務列表的時間,即使滿足“10秒沒收到請求但不滿足到了清理服務列
表的時間,一樣不會剔除該服務”。
(3)自我保護:
首先闡明存在的一個問題:
默認情況下,如果Eureka Server在一定時間內(默認90秒)沒有接收到某個微服務實例的心跳,
Eureka Server將會移除該實例。但是當網絡分區故障發生時,微服務與Eureka Server之間無法正常通
信,而微服務本身是正常運行的,此時不應該移除這個微服務,Eureka通過自我保護機制來解決該問題
的。
自我保護機制
當Eureka Server節點在短時間內丟失過多客戶端時(15分鍾內超過85%的客戶端節點都沒有正常的心
跳),那么這個節點就會進入自我保護模式。一旦進入該模式,Eureka Server就會保護服務注冊表中的
信息,不再刪除服務注冊表中的數據(也就是不會注銷任何微服務)。當網絡故障恢復后,該
Eureka Server節點會自動退出自我保護模式。
自我保護配置:
spring:
application:
name: eurker-servier-heartbeat
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
server:
#自我保護配置(true 開啟,false 關閉)
enable-self-preservation: false
#(清理服務列表的時間間隔)
eviction-interval-timer-in-ms: 10000
(4)健康監控
默認情況下注冊到eureka server的服務是通過心跳來告知自己是UP還是DOWN,並不是通過
spring-boot-actuator模塊的/health端點來實現的,這樣其實不是很合理。因為默認的心跳實現方式可以有效
的檢查eureka客戶端進程是否正常運作,但是無法保證客戶端應用能夠正常提供服務(大多數微服務應用都
會有一些其他的外部資源依賴,比如數據庫,REDIS緩存等,如果我們的應用與這些外部資源無法連通的時
候,實際上已經不能提供正常的對外服務了,但因為客戶端心跳依然在運行,所以它還是會被服務消費者調
用)。
健康狀態有UP和DOWN兩種,如果Eureka中的是UP,則該服務可以正常調用;如果Eureka中的健康狀
態是DOWN則該服務不可調用。
問題場景:如果一個服務並沒有死掉,但是其本身是有問題的,例如訪問數據庫的服務無法連接
到數據庫,這個時候需要使用健康監控。
注意:健康監控監控的是客戶端,所以健康指示器和健康處理器的代碼只能寫在需要健康的客戶端。
(a)引入jar包
package app; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.Status; import org.springframework.stereotype.Component; @Component public class MyHealthIndicator implements HealthIndicator { public Health health() { if(ProviderMonitorController.canVisitDB) { return new Health.Builder(Status.UP).build(); }else { return new Health.Builder(Status.DOWN).build(); } } }
(c)模擬數據庫無法訪問
package app; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController public class ProviderMonitorController { public static boolean canVisitDB=true; @RequestMapping(value = "/setDB/{can}",method = RequestMethod.GET) public void setDB(@PathVariable boolean can) { canVisitDB=can; } }
(d)測試
(i)查看健康狀態
在瀏覽器里輸入http://localhost:8080/health,返回狀態為UP
(ii)執行模擬數據庫無法訪問並查看健康狀態
瀏覽器中輸入:http://localhost:8080/setDB/false 設置數據庫無法訪問
瀏覽器中輸入:http://localhost:8080/health 查看健康狀態為 DOWN
查看Eureka中的健康狀態仍然為UP,這時需要使用健康檢查處理器來改變Eureka中的狀態(只有改
變了Eureka中的狀態,那么有問題的服務才不能被其它被訪問)。
(e)健康處理器代碼(默認情況下這個處理器30秒執行一次):
package app; import com.netflix.appinfo.HealthCheckHandler; import com.netflix.appinfo.InstanceInfo; import jdk.net.SocketFlow; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.boot.actuate.health.Status; @Component public class MyHealthCheckHandle implements HealthCheckHandler { @Autowired private MyHealthIndicator myHealthIndicator; public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus instanceStatus) { Status status= myHealthIndicator.health().getStatus(); if(status.equals(Status.UP)) { return InstanceInfo.InstanceStatus.UP; }else { return InstanceInfo.InstanceStatus.DOWN; } } }
application.yml(主要關注健康處理器的執行頻率)
spring:
application:
name: my-health-provider
endpoints:
sensitive: false
eureka:
client:
#健康處理器執行頻率。默認30秒執行一次,這里改成10秒執行一次
instanceInfoReplicationIntervalSeconds: 10
serviceUrl:
defaultZone: http://localhost:8761/eureka/
(f)測試健康處理器
瀏覽器中執行:http://localhost:8080/setDB/false,可以看到health端口和Eureka 中的狀態全部變成
DOWN
(5)Eureka元數據
(6)REST端點
(7)多網卡環境下的IP選擇
上面代碼中為了測試方便還增加了清理服務列表的時間,即使滿足“10秒沒收到請求但不滿足到了清理服務列
表的時間,一樣不會剔除該服務”。
二 Ribbon
1.客戶端負載均衡框架,支持可插拔式的負載均衡規則
2.支持多中協議,如HTTP、UDP
3.Ribbon支持的負載均衡Rule
(1)RoundRobinRule:輪詢
(2)AvailabilityFilteringRule:會過濾掉兩類服務器。(1)短路狀態(連續3次超時)(2)並發數過高的服務
器。
(3)WeightedResponseTimeRule:為每個服務器設置一個權重值,服務器的響應時間越長權重值越小。這里
是根據權重值隨機選擇服務器。
(4)ZoneAvoidanceRule:復合判斷server所在區域的性能和server的可用性選擇server,使用區域對服務器進
行選擇。
(5)BestAvailableRule:忽略短路的服務器並選擇並發數最小的一個服務器
(6)RandomRule:隨機數
(7)RetryRule:含有重試機制的選擇邏輯。
4. Ribbon可配置的類
NFLoadBalancerPingClassName:用於配置查看服務器是否存活。配置IPing的實現類。
NFLoadBalancerRuleClassName:指定負載均衡器的實現類。當然,可以設置自己實現的負載均衡器。配置
IRule的實現類。IRule代表的是負載均衡算法。
NFLoadBalancerClassName:配置ILoadBalancer的實現類
NIWSServerListClassName:是服務器列表的處理類,用來維護服務器列表的。Ribbon已經實現了動態服務器
列表。配置ServerList的實現類。
NIWSServerListFilterClassName:是服務器的攔截類。配置ServerListFilter的實現類。
三 feign
Feign是Netflix開發的聲明式、模板化的HTTP客戶端,Feign可幫助我們更加便捷、優雅地調用HTTPAPI。
在springCloud中,使用Feign非常簡單一創建一個接口,並在接口上添加一些注解,代碼就完成了。Feign支持多
種注解,例如Feign自帶的注解或者JAX-RS注解等。
SpringCloud對Feign進行了增強,使Feign支持了SpringMVC注解,並整合了Ribbon和Eureka,從而讓
Feign的使用更加方便。
feign是可以配置的,默認配置類是FeignClientsConfiguration,該類定義了feign的編碼器、解碼器、使用契約
(SpringCloud的默認契約使用的是SpringMVCContract,因此它可以使用SpringMVC的注解)等。SpringCloud允
許通過注解@FeignCIient的configuration屬性自定義Feign的配置,自定義配置的優先級要高於
FeignClientsConfiguration。
feign中已經使用了Ribbon的Api,默認負載均衡配置是輪詢,可以通過設置Ribbon的負載均衡策略來定義
Feign客戶端的負載均衡策略
1. SpriingCloud整合feign
(1)添加feign的依賴
(2)創建一個Feign接口,並添加@FeignClient注解
@FeignC1ient注解中的microservice-provider-user是一個任意的客戶端名稱,用於創建Ribbon負載均衡
器。在本例中,由於使用了Eureka,所以Ribbon會把microservice-provider-user解析成EurekaServer服
務注冊表中的服務。當然,如果不想使用Eureka,可使用service.ribbon.listofservers屬性配置服務器列
表(詳見5·5節)。
還可使用url屬性指定請求的URL(URL可以是完整的URL或者主機名),例如:
(4)調用feign接口的Controller代碼如下:
(5)啟動類添加@EnableFeignClients使feign生效
這樣就可以通過feign去調用微服務的microservice-provider-user了
2. 自定義feign的配置
SpringCloud允許通過注解@FeignCIient的configuration屬性自定義Feign的配置,自定義配置的優先級比
FeignClientsConfiguration要高。
(1)自定義契約
SpringCloud中,默認使用的契約是SpriingMVCContract,因此,我們在使用feign時,使用的是
SprinigMVC注解。
本例示范自定義使用feign的自己的注解。
(a)創建feign的配置類
擴展,為了看懂該例子,了解下@Bean
代碼:
@Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); } }
等同於:
<beans> <bean id="transferService" class="com.acme.TransferServiceImpl"/> </beans>
(b)Feign接口修改為如下,使用@FeignC1ient的configuration屬性指定配置類,同時,將findById
上的SpringMVC注解修改為Feign自帶的注解。
(2)為接口添加HTTP Baseic
配置類寫法:
其它略。
(3)類似地,還可自定義Feign的編碼器、解碼器、日志打印,甚至為Feign添加攔截器。具體demo網上找,
書上沒寫。
Feign的常用配置有:
解碼器(Decoder):bean名稱為feignDecoder,ResponseEntityDecoder類。
編碼器(Encoder):bean名稱為feignEecoder,SpringEncoder類。
日志(Logger): bean名稱為feignLogger,Slf4jLogger類。
注解翻譯器(Contract): bean名稱為feignContract,SpringMvcContract類。
Feign實例的創建者(Feign.Builder):bean名稱為feignBuilder,HystrixFeign.Builder類。Hystrix框架
將在后面章節中講述。
Feign客戶端(Client):bean名稱為feignClient,LoadBalancerFeignClient類。
Logger.Level:接口日志的記錄級別,相當於調用了Fiegn.Builder的logLevel方法,請見5.2.9章節(視頻)。
Retryer:重試處理器,相當於調用了Fiegn.Builder的retryer方法。
ErrorDecoder:異常解碼器,相當於調用了Fiegn.Builder的errorDecoder方法。
Request.Options:設置請求的配置項,相當於調用了Fiegn.Builder的options方法。
Collection<RequestInterceptor>:設置請求攔截器,相當於調用了Fiegn.Builder的requestInterceptors
方法。下面是配置多個攔截器的樣例,並不全,只是為了便於理解,
只是補充需要百度。
2. feign對壓縮的支持
壓縮配置:
feign.compression.request.enabled:
設置為true時,表示開啟“請求”壓縮。
feign.compression.response.enabled:
設置為true時,表示開啟響應壓縮。
feign.compression.request.mime-types:
數據類型列表,默認值為text/xml,application/xml,application/json。
feign.compression.request.min-request-size:
設置請求內容的最小閥值,默認值為2048。若大於2048則開啟壓縮。
3. feign構造多參數請求
看書,挺重要但是不帶要摘錄了
四 Hystrix
1.雪崩效應
服務雪崩效應是一種因,服務提供者的不可用導致服務調用者的不可用,並將不可用逐漸放大的過程。
要想防止雪崩效應,必須有一個強大的容錯機制。該容錯機制需實現以下兩點:
(1)為網絡設置超時時間
必須為網絡請求設置超時。正常情況下,一個遠程調用一般在幾十毫秒內就能得到響應了。如果
依賴的服務不可用或者網絡有問題,那么響應時間就會變得很長(幾十秒)通常情況下,一次遠程調
用對應着一個線程/進程。如果響應太慢,這個線程/進程就得不到釋放。而線程/進程又對應着系統資
源,如果得不到釋放的線程/進程越積越多,資源就會逐漸被耗盡,最終導致服務的不可用。因此必須
為每個網絡請求設置超時,讓資源盡快釋放。
(2)使用斷路器
如果對某個微服務的請求有大量超時(常常說明該微服務不可用),再去讓新的請求訪問該服務
已經沒有任何意義,只會無謂消耗資源。例如,設置了超時時間為1秒,如果短時間內有大量的請求無
法在1秒內得到響應,就沒有必要再去請求依賴的服務了。
斷路器可以實現快速失敗,如果它在一段時間內檢測到許多類似的錯誤(例如超時),就會在之
后的一段時間內,強迫對該服務的調用快速失敗,即不再請求所依賴的服務。這樣,應用程序就無須
再浪費CPU時間去等待長時間的超時。
斷路器也可自動診斷依賴的服務是否已經恢復正常。如果發現依賴的服務已經恢復正常,那么就
會恢復請求該服務。使用這種方式,就可以實現微服務的“自我修復"一當依賴的服務不正常時打開斷路
器時快速失敗,從而防止雪崩效應;當發現依賴的服務恢復正常時,又會恢復請求。
斷路器的執行邏輯:
-- 正常情況下,斷路器關閉,可正常請求依賴的服務。
-- 當一段時間內,請求失敗率達到一定閾值(例如錯誤率達到50%,或100次/分鍾等),斷路器
就會打開。此時,不會再去請求依賴的服務。
-- 斷路器打開一段時間后,會自動進入“半開"狀態。此時,斷路器可允許一個請求訪問依賴的服務。
如果該請求能夠調用成功,則關閉斷路器;否則繼續保持打開狀態。
2. 使用Hystrix實現容錯
翻以前博客和看書
3. Hystrix的監控
(1)使用/hystrix.stream端點進行監控(有用)
看書
(2)使用Hystrix Dashboard可視化監控(有用)
看書,書上的例子並沒有把Hystrix Dashboard注冊到Eureka,生產環境可以把Hystrix Dashboard注冊
到Eureka上。
(3)使用Turbine進行監控(重點)
由於/hystrix.stream只能看單個服務,如需要查看其它服務就需要在Hystrix Dashboard上切換要
監控的地址,這樣做很不方便,於是我們使用Turbine,它可將所有./hystrix.stream聚合到一個組合的
./turbine.stream中,讓集群監控更加方便。
具體使用示例看書,太繁瑣,不好摘錄