一、什么是SpringCloud
1、官方定義
1)官方定義:springcloud為開發人員提供了在分布式系統中快速構建一些通用模式的工具(例如配置管理、服務發現、斷路器、智能路由、微代理、控制總線)。分布式系統的協調導致了鍋爐板模式,使用springcloud開發人員可以快速地建立實現這些模式的服務和應用程序。
2)springcloud是一個含概多個子項目的開發工具集,集合了眾多的開源框架,他利用了spring boot開發的便利性實現了很多功能,如服務注冊,服務注冊發現,負載均衡等,springcloud在整合過程中主要是針對Netflix開源組件的封裝,springcloud的出現真正的簡化了分布式架構的開發。netflix是美國的一個在線視頻網站,微服務業的翹楚,他是公認的大規模生產級微服務的傑出實踐者,netflix的開源組件已經在他大規模分布式微服務環境中經過多年的生產實戰驗證,因此springcloud中很多組件都是基於netflix組件的封裝。
2、核心架構及其組件
1)核心組件說明
eureka/consul/nacos(alibaba):服務注冊中心組件
rabbion 、 openfeign:服務負載均衡和服務調用組件
hystrix 、 hystrix dashboard:服務斷路器和服務監控組件
zuul/gateway:服務網關組件
config:統一配置中心組件
bus:消息總線組件 |
|
3、環境搭建
1)版本命名
springcloud是一個由眾多獨立子項目組成的大型綜合項目,原則每個子項目有不同的發布節奏,都維護自己發布版本號。為了更好的管理springcloud的版本,通過一個資源清單BOM(bill of materials),為了避免與子項目的發布好混淆,所以沒有采用版本號的方式,而是通過命名的方式。這些名字是按字母順序排列的。當單個項目的點發布累積到一個臨界量,或者其中一個項目中有一個關鍵缺陷需要每個人都可以使用時,發布序列將推出名稱以“.SRX”結尾的“服務發布”,其中“x”是一個數字。
2)版本選擇(springboot ,springcloud)
![]()
|
![]() |
3)環境搭建(所有springcloud項目都要准備以下內容)
(1)非自創父項目
新建一個空項目,然后按下述方式建模塊。(如下左圖結構)
- 說明
springboot 2.2.x.RELEASE、springcloud Hoxton SR6、java8+、maven3.3.6+、idea2020+
- 創建springboot模塊
指定版本為2.2.5版本
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
- 引入springcloud的版本管理
<properties> <!--springcloud具體版本號--> <spring-cloud.version>Hoxton.SR6</spring-cloud.version> </properties> <!--全局管理springboot版本,並不會引入具體依賴--> <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>
(2)自創父項目
將上述的依賴整合到一個父項目中,然后后續以此為基礎創建子模塊,並引入該父項目。(如下右圖結構)
- 使用方式
將父項目的(groupId、artifactId)放入子項目的<parent/>中,同時在父項目中添加<modules>
父:
<modules> <module> test3 </module> </modules>
子:
<parent> <groupId>com.icucoder</groupId> <artifactId>parent</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>
二、服務注冊中心
1、基本內容
1)概念
所謂的服務注冊中心就是在整個微服務架構中單獨提出一個服務,整個服務不完成系統的任何業務功能,僅僅用來對整個微服務系統的服務注冊和服務發現,以及對服務健康狀態的監控和管理功能。
2)功能
可以對所有的微服務的信息進行存儲,如微服務的名稱、ip和端口等
可以在進行服務調用時通過服務發現查詢可用的微服務列表及網絡地址進行服務調用
可以對所有的微服務進行心跳檢測,如發現某實例長時間無法訪問,就會從服務注冊表移除該實例。
3)常用的注冊中心
Eureka(Netflix)、Consul、Zookeeper、以及阿里巴巴的Nacos組件。這些注冊中心在本質上都是用來管理服務的注冊和發現以及服務狀態的檢查的。
2、使用方法
(1)簡介
Eureka是Netflix開發的服務發現框架,本身是一個基於REST的服務。springcloud將它集成在其子項目spring-cloud-netflix中,以實現springcloud的服務注冊和發現功能。
Eureka包含兩個組件:Eureka Server和Eureka Client。
(2)開發Eureka Server
- 創建項目並引入eureka server依賴
<!--一引入Eureka Server依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
- 編寫配置application.properties
#指定服務端口 server.port=9000 #指定服務名稱 唯一標識 spring.application.name=eurekaserver #指定服務注冊中心的地址 eureka.client.service-url.defaultZone=http://localhost:9000/eureka
- 開啟Eureka Server入口類加入注解
@SpringBootApplication @EnableEurekaServer public class Eurekaserver9000Application { public static void main(String[] args) { SpringApplication.run(Eurekaserver9000Application.class, args); } }
- 訪問: http://localhost:9000/
- 說明:
出現上述問題的原因:Eureka組件包含Eureka server和Eureka client。server是一個服務注冊中心,用來接收客戶端的注冊。client的特性會讓當前啟動的服務把自己作為Eureka的客戶端進行服務中心的注冊,當項目啟動時服務注冊中心還沒有創建好,所以找不到服務的客戶端組件就直接報錯了,當啟動成功服務注冊中心創建好了,日后client也能進行 注冊,就不會報錯了。
關閉自己注冊自己:
#不再將自己同時作為客戶端進行注冊(用在eureka server上) eureka.client.register-with-eureka=false #關閉作為客戶端時從Eureka server獲取服務信息 eureka.client.fetch-registry=false
(3)開發Eureka Client
- 創建項目並引入eureka client依賴
<!--一引入Eureka Client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
- 編寫配置application.properties
#指定服務端口 server.port=8000 #指定服務名稱 唯一標識 spring.application.name=eurekaclient #指定服務注冊中心的地址 eureka.client.service-url.defaultZone=http://localhost:9000/eureka
- 開啟Eureka client入口類加入注解
@SpringBootApplication @EnableEurekaClient public class Eurekaclient8000Application { public static void main(String[] args) { SpringApplication.run(Eurekaclient8000Application.class, args); } }
(4)eureka自我保護機制
- 默認情況下,如果eureka server在一定時間內(默認90s)沒有接收到某個微服務實例的心跳,eureka server將會移除該實例。自我保護機制是:eureka server在運行期間會去統計心跳失敗比例在15分鍾之內是否低於85%,如果低於85%,eureka sever會將這些實例保護起來,讓這些實例永不過期。
- 在eureka server端關閉自我保護機制
#關閉自我保護 eureka.server.enable-self-preservation=false #超時3s自動清除 eureka.server.eviction-interval-timer-in-ms=3000
- 微服務修改減短服務心跳時間
#用來修改eureka sever默認接受心跳的最大時間,默認是90s eureka.instance.lease-expiration-duration-in-seconds=10 #指定客戶端多久向eureka server發送一次心跳,默認是30s eureka.instance.lease-renewal-interval-in-seconds=5
2)consul
(1)簡介
consul是一個可以提供服務發現、健康檢查、多數據中心、key/value存儲等功能的分布式服務框架,用於實現分布式系統的服務發現與配置。與其他分布式服務注冊方案相比,使用起來也較為簡單。consul用golang實現,因此具有天然可移植性(支持linux、windows和mac os x)。安裝包僅包含一個可執行文件,方便部署。
下載consul,解壓后執行命令:
- linux:執行consul agent -dev -client 0.0.0.0 -ui 啟動consul服務器
- windows:執行consul.exe agent -dev 啟動consul服務器
然后訪問:http://localhost:8500
(3)開發Consul Client
- 創建項目並引入consul依賴
<!--引入consul依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
- 編寫application.properties配置
server.port=8100 spring.application.name=consulclient #注冊consul服務的主機 spring.cloud.consul.host=192.168.227.134 #注冊consul服務的端口號 spring.cloud.consul.port=8500 #關閉consul的服務健康檢查(不推薦)這里開啟 spring.cloud.consul.discovery.register-health-check=true #指定注冊的服務名稱 默認就是應用名 spring.cloud.consul.discovery.service-name=${spring.application.name}
- 啟動服務查看consul界面服務信息
(4)consul開啟健康監控檢查
默認情況下consul監控健康是開啟的,但是必須依賴健康監控依賴才能正確監控健康狀態,所以直接開啟會顯示錯誤,引入健康監控依賴之后服務正常。
<!--consul監控健康依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
3)zookeeper
todo
4)nacos
todo
3、不同注冊中心區別
1)CAP定理
CAP定理又稱為CAP原則,指的是在一個分布式系統中,一致性(Consistency)、可用性(Availability)、分區容錯性(Partition tolerance)。CAP原則指的是,這三個要素最多只能同時實現兩點,不可能三者兼顧。
(1)一致性:在分布式系統中的所有數據備份,在同一時刻是否同樣的值。(等同於所有節點訪問同一份最新的數據副本)
(2)可用性:在集群中一部分接地那故障后,集群整體是否還能響應客戶端的讀寫要求。(對數據更新具備高可用性)
(3)分區容錯性:就是高可用性,一個節點崩了,並不影響其他的節點(100個節點,掛了幾個,不影響服務,越多機器越好)
2)Eureka特點
Eureka中沒有使用任何的數據一致性算法保障不同集群間的server的數據一致,僅通過數據拷貝的方式爭取注冊中心數據的最終一致性,雖然放棄數據一致性但是換來了server的可用性,降低了注冊的代價,提高了集群運行的健壯性。
3)Consul特點
基於Raft算法,Consul提供強一致性的注冊中心服務,但是由於Leader節點承擔了所有的處理工作,勢必加大了注冊和發現的代價,降低了服務的可用性,通過Gossip協議,Consul可以很好地監控Consul集群的運行,同時可以方便通知各類事件,如Leader選擇發生、server地址變更等。
4)zookeeper特點
基於Zab協議,Zookeeper可以用於構建具備數據強一致性的服務注冊於發現中心,而與此相對地犧牲了服務的可用性和提高了注冊需要的時間。
組件名 | 語言 | CAP | 一致性算法 | 服務健康檢查 | 對外暴露接口 | Spring Cloud集成 |
Eureka | java | ap | 無 | 可配支持 | HTTP | 已集成 |
Consul | go | cp | Raft | 支持 | HTTP/DNS | 已集成 |
Zookeeper | java | cp | Paxos | 支持 | 客戶端 | 已集成 |
三、服務間通信方式
- 一個服務開啟多個端口:
![]() |
|
在springcloud中服務間調用主要是使用http restful方式進行服務調用。
1、基於RestTemplate的服務調用
1)說明
spring框架提供的RestTemplate類可用於在應用中調用rest服務,它簡化了與http服務的通信方式,統一了RESTful的標准,封裝了http鏈接,我們只需要傳入url及返回值類型即可。相較於之前常用的HttpClient,RestTemplate是一種更優雅的調用RESTful服務的方式。
2)示例
- 商品服務提供一個接口:http://localhost:8200/product/showMsg
@RestController public class ProductController { @Value("${server.port}") private int port; @GetMapping("/product/showMsg") public String ShowMsg() { return "進入商品服務,展示商品~~~,當前服務的端口:" + port; } }
- 用戶服務調用這個接口:http://localhost:8200/user/showProductMsg1
@RestController public class UserController { @GetMapping("/user/showProductMsg1") public String showProductMsg1() { RestTemplate restTemplate = new RestTemplate(); String msg = restTemplate.getForObject("http://localhost:8200/product/showMsg", String.class); return msg; } }
3)分析
rest template是直接基於服務地址調用沒有在服務注冊中心獲取服務,也沒有辦法完成服務的負載均衡,如果需要實現服務的負載均衡需要自己書寫服務負載均衡策略。
2、基於Ribbon的服務調用
1)說明
Spring Cloud Ribbon是一個基於HTTP和TCP的客戶端負載均衡工具,它基於Netflix Ribbon實現。通過Spring Cloud的封裝,可以讓我們輕松地面向服務的REST模板請求自動轉換成客戶端負載均衡的服務調用。
2)示例
(1)項目中引入依賴
如果使用的是eureka client和consul client,無須引入依賴,因為在eureka和consul中默認集成了ribbon組件。如果使用的client沒有ribbon依賴,則需要引入下述依賴:
<!--引入ribbon依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
(2)使用restTemplate+ribbon進行服務調用有三種方式(DiscoveryClient、LoadBalancerClient、@LoadBalanced):
- 使用DiscoveryClient進行客戶端調用
@RestController public class UserController { @Autowired private DiscoveryClient discoveryClient; /*** * ribbon:DiscoveryClient 沒有提供負載均衡 * @return */ @GetMapping("/user/findProductAll") public List<ServiceInstance> findProductAll() { List<ServiceInstance> products = discoveryClient.getInstances("products"); //自己寫負載均衡,通過獲取的ip:port,然后使用RestTemplate return products; } }
- 使用LoadBalancerClient進行客戶端調用
@RestController public class UserController { @Autowired private LoadBalancerClient loadBalancerClient; /*** * ribbon:LoadBalancerClient 負載均衡:默認輪詢 * @return */ @GetMapping("/user/findProductAll") public ServiceInstance findProductAll() { ServiceInstance products = loadBalancerClient.choose("products"); //通過獲取的ip:port,使用RestTemplate return products; } }
- 使用@LoadBalanced進行客戶端調用
新建一個配置類GetRestTemplateConfig ,並使用@LoadBalanced注解:
@Configuration public class GetRestTemplateConfig { //在工廠中創建一個RestTemplate對象 @Bean @LoadBalanced//代表具有ribbon負載均衡的RestTemplate對象 public RestTemplate getRestTemplate() { return new RestTemplate(); } }
在controller中使用:
@RestController public class UserController { @Autowired private RestTemplate restTemplate; /*** * ribbon:@LoadBalanced 直接使用服務名:products * @return */ @GetMapping("/user/findProductAll") public String findProductAll() { String forObject = restTemplate.getForObject("http://products/product/findAll", String.class); return forObject; } }
3)ribbon的負載均衡策略說明
(1)ribbon負載均衡算法
- RoundRobinRule:輪詢測試,按順序循環選擇
- RandomRule:隨機策略,隨機選擇
- AvailabilityFilteringRule:可用過濾策略,會先過濾由於多次訪問故障而處於斷路跳閘狀態的服務,還有並發的連接數量超過閾值的服務,然后對剩余的服務列表按照輪詢策略進行訪問。
- WeightedResponseTimeRule:響應時間加權策略,根據平均響應的時間計算所有服務的權重,響應時間越快服務權重越大,被選中的概率越高,剛啟動時如果統計信息不足,則使用RoundRobinRule策略,等統計信息足夠會切換到
- RetryRule:重試策略,先按照RoundRobinRule的策略獲取服務,如果獲取失敗則在指定時間內重試,獲取可用的服務。
- BestAviableRule:最低並發策略,會先過濾由於多次訪問故障而處於斷路跳閘狀態的服務,然后選擇一個並發量最小的服務。
(2)修改默認的負載均衡策略
3、OpenFeign組件的使用
1)使用RestTemplate+ribbon已經可以完成服務間的調用,為什么還要使用feign?
問題:
每次調用服務的代碼存在大量的代碼冗余;服務地址如果修改,維護成本增高;使用時不靈活。
2)OpenFeign組件
Feign是一個聲明式偽http客戶端,它使得寫http客戶端變的更簡單。使用feign,只需要創建一個接口並注解。它具有可拔插的注解特性(可以使用springmvc的注解),可使用feign注解和jax-rs注解。feign支持可拔插的編碼器和解碼器。feign默認集成了ribbon,默認實現了負載均衡的效果並且springcloud未feign添加了springmvc注解的支持。
(1)示例
- 服務調用方法 引入OpenFeign依賴
<!--引入openfeign依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
- 入口類加入注解開啟OpenFeign支持
@SpringBootApplication @EnableFeignClients//開啟支持openfeign組件方式調用 public class UsersApplication { public static void main(String[] args) { SpringApplication.run(UsersApplication.class, args); } }
- 創建一個接口
//調用商品服務的openfeign組件 @FeignClient(value = "products")//標識當前接口是一個feign組件 value=調用服務的id public interface ProductsClient { @GetMapping("/product/showMsg") String ShowMsg(); @GetMapping("/product/findAll") Map<String, Object> findAll(); }
- controller中使用
@RestController public class UserController { @Autowired private ProductsClient productsClient; /*** * openfein: * @return */ @GetMapping("/user/findProductAll") public Map<String, Object> findProductAll() { return productsClient.findAll(); } }
訪問:http://127.0.0.1:8100/user/findProductAll
(2)參數傳遞(服務和服務之間通信,不僅僅是調用,往往在調用過程中還伴隨着參數傳遞。)
- 單個變量:使用openfeign的get、post方式傳遞參數,接口類中的參數變量必須通過@RequestParam注解進行修飾;
//1、ProductController @RestController public class ProductController { @GetMapping("/product/findOne/{productId}")//必須加@PathVariable("productId") public Map<String, Object> findOne(@PathVariable("productId") String productId) { Map<String, Object> map = new HashMap<>(); map.put("status", true); map.put("msg", "根據商品id查詢商品成功"); map.put("productId", productId); return map; } @PostMapping("/product/save")//可以加@RequestParam也可以不加 public Map<String, Object> save(@RequestParam("productName") String productName) { Map<String, Object> map = new HashMap<>(); map.put("status", true); map.put("msg", "保存商品成功"); map.put("productName", productName); return map; } } //2、ProductsClient //調用商品服務的openfeign組件 @FeignClient(value = "products")//標識當前接口是一個feign組件 value=調用服務的id public interface ProductsClient { @GetMapping("/product/findOne/{productId}") Map<String, Object> findOne(@PathVariable("productId") String productId); @PostMapping("/product/save") Map<String, Object> save(@RequestParam("productName") String productName); } //3、UserController @RestController public class UserController { @Autowired private ProductsClient productsClient; @GetMapping("/user/findProductOne") public Map<String, Object> findProductOne(String productId) { return productsClient.findOne(productId); } @GetMapping("/user/saveProduct") public Map<String, Object> saveProduct(String productName) { return productsClient.save(productName); } }
訪問:http://127.0.0.1:8100/user/findProductOne?productId=1001或http://127.0.0.1:8100/user/saveProduct?productName=orange
- 對象:使用openfeign方式傳遞對象參數,接口類中的參數變量和被調用的controller中的參數變量必須通過@RequestBody注解進行修飾;
//1、實體類 @Data public class Product { private String id; private String name; private Date upate; } //2、ProductController @RestController public class ProductController { @PostMapping("/product/update")//必須加@RequestBody,作用:將json格式字符串轉為對應對象信息 public Map<String, Object> update(@RequestBody Product product) { Map<String, Object> map = new HashMap<>(); map.put("status", true); map.put("msg", "保存商品成功"); map.put("product", product); return map; } } //3、ProductsClient //調用商品服務的openfeign組件 @FeignClient(value = "products")//標識當前接口是一個feign組件 value=調用服務的id public interface ProductsClient { @PostMapping("/product/update") Map<String, Object> update(@RequestBody Product product); } //4、UserController @RestController public class UserController { @Autowired private ProductsClient productsClient; @GetMapping("/user/updateProduct") public Map<String, Object> updateProduct(Product product) { return productsClient.update(product); } }
訪問:http://127.0.0.1:8100/user/updateProduct?id=1001&name=zhansan&upate=2021/04/21
(3)超時設置
默認情況下,openfeign在進行服務調用時,要求服務提供方處理業務邏輯時間必須在1S內,如果超過1S沒有返回則openfeign會直接報錯,不會等待服務執行,但是往往在處理復雜業務邏輯是會超過1S,因此需要修改openfeign的默認服務調用超時時間。
#配置指定服務 feign.client.config.PRODUCTS.connectTimeout=5000 feign.client.config.PRODUCTS.readTimeout=5000 #配置所有服務 feign.client.config.default.connectTimeout=5000 feign.client.config.default.readTimeout=5000
(4)日志配置
往往在服務調用時我們需要詳細的展示feign的日志,默認feign在調用時並不是最詳細日志輸出,因此在調試程序時應該開啟feign的詳細日志展示,feign對日志的處理非常靈活,可為每個feign客戶端指定日志記錄策略,每個客戶端都會創建一個logger,默認情況下logger的名稱是feign的全限定名,需要注意的是,feign日志的打印只會DEBUG級別做出響應。
我們可以為feign客戶端配置各自的logger.level對象,告訴feign記錄哪些日志。logger.lever有以下幾種值:
- NONE:不記錄任何日志
- BASIC:僅僅記錄請求方法,url,響應狀態代碼及執行時間
- HEADERS:記錄Basic級別的基礎上,記錄請求和響應的header
- FULL:記錄請求和響應的header,body和元數據
#開啟指定服務日志展示 feign.client.config.PRODUCTS.loggerLevel=full #全局開啟服務日志展示 feign.client.config.default.loggerLevel=full #指定feign調用客戶端對象所在包,必須是debug級別 logging.level.com.icucoder.feignclients=debug
四、Hystrix組件
在分布式環境中,許多服務依賴項不可避免地會失敗。Hystrix是一個庫,它通過添加延遲容忍和容錯邏輯來幫助控制這些分布式服務直接的交互。Hystrix通過隔離服務之間的訪問點、停止它們之間的級聯故障以及提供后備選項來實現這一點,所有這些都可以提高系統的整體彈性。
1、服務雪崩、服務熔斷、服務降級
1)服務雪崩
在微服務直接進行服務調用時由於一個服務故障,導致級聯服務故障的現象,稱為雪崩效應。雪崩效應描述的是提供方不可用,導致消費放不可用並將不可用逐漸放大的過程。
2)服務熔斷
“熔斷器”本身是一種開關裝置,當某個服務單元發生故障之后,通過熔斷器的故障監控,某個異常條件被觸發,直接熔斷整個服務(對調用鏈路的保護)。向調用方法返回一個符合預期的、可處理的備選響應(FallBack),而不是長時間的等待或者拋出調用方法無法處理的異常,就保證了服務調用方的線程不會被長時間占用,避免在分布式系統中蔓延,乃至雪崩。如果目標服務情況好轉則恢復調用。服務熔斷是解決服務雪崩的重要手段。
3)服務降級
服務壓力劇增的時候根據當前的業務情況及流量對一些服務和頁面有策略的降級,以此緩解服務器的壓力,以保證核心任務的進行。同時保證部分甚至大部分任務客戶能得到正確的響應。也就是當前的請求處理不了了或者出錯了,給一個默認的返回。
4)降級和熔斷總結
(1)共同點
目的很一致,都是從可用性可靠性着想,為防止系統的整體緩慢甚至崩潰,采用的技術手段;
最終表現類似,對於兩者來說,最終讓用戶體驗到的是某些功能暫時不可達或不可用;
粒度一般都是服務級別,當然,業界也有不少更細粒度的做法,比如做到數據持久層(允許查詢,不允許增刪改);
自治性要求很高,熔斷模式一般都是服務基於策略的自動觸發,降級雖說可人工干預,但在微服務架構下,完全靠人顯然不可能,開關預置、配置中心都是必要手段。
(2)不同點
觸發原因不一樣,服務熔斷一般是某個服務(下游服務)故障引起,而服務降級一般是整體負荷考慮;
管理目標的層次不太一樣,熔斷其實是一個框架級的處理,每個微服務都需要(無層級之分),而降級一般需要對業務有層級之分(比如降級一般是從最外圍服務開始);
(3)總結
熔斷必會觸發降級,所以熔斷也是降級的一種,區別在於熔斷是對調用鏈路的保護,而降級是對系統過敏的一種保護處理。
2、實現服務熔斷
1)步驟
(1)引入hystrix依賴(如果引入了openfeign依賴,則不需要再引入hystrix依賴了)
<!--引入hystrix:熔斷器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
(2)在Application上添加@EnableCircuitBreaker注解,開啟斷路器
@SpringBootApplication @EnableCircuitBreaker//開啟斷路器 public class ProductsApplication { public static void main(String[] args) { SpringApplication.run(ProductsApplication.class, args); } }
(3)編寫ProductController(在要熔斷的方法上添加@HystrixCommand),並回調fallbackMethod
@RestController public class ProductController { @GetMapping("/product/testBreak") @HystrixCommand(fallbackMethod = "testFallBack") public String testBreak(Integer id) { if (id < 0) { throw new RuntimeException("非常參數,id不能小於0。"); } return "訪問成功,當前查詢id為:" + id; } //觸發熔斷的fallback方法 public String testFallBack(Integer id) { return "當前傳入的參數id:" + id + "不是有效參數,觸發熔斷"; } }
(4)測試:http://127.0.0.1:8200/product/testBreak?id=-1
2)斷路器打開條件
- 如果觸發一定條件斷路器會自動打開,過了一段時間之后,正常之后又會關閉。
(1)當滿足一定的閾值的時候(默認10秒內超過20個請求次數);
(2)當失敗率達到一定的時候(默認10秒內超過50%的請求失敗);
(3)到達以上閾值,斷路器會開啟;
(4)當開啟的時候,所有請求都不會進行轉發;
(5)一段時間之后(默認是5秒),這個時候斷路器是半開狀態,會讓其中一個請求進行轉發。如果成功,斷路器會關閉,若失敗,繼續開啟,重復(4)、(5)。
3)默認的服務FallBack處理方法
如果為每個服務方法都開發一個fallback方法,對於我們來說,可能會出現大量的代碼冗余,不利於維護,這個時候就需要加入默認fallback方法;
@RestController public class ProductController { @GetMapping("/product/testBreak") @HystrixCommand(defaultFallback = "testDefaultFallBack") public String testBreak(Integer id) { if (id < 0) { throw new RuntimeException("非常參數,id不能小於0。"); } return "訪問成功,當前查詢id為:" + id; } //默認觸發熔斷的fallback方法 public String testDefaultFallBack() { return "觸發熔斷"; } }
3、實現服務降級
客戶端openfein+hystrix實現服務降級,調用者users,被調用者product,當滿足降級條件,如product服務不可用時,則降級。當滿足product熔斷條件時,則熔斷。
1)步驟
(1)引入hystrix依賴
(2)開啟openfeign支持服務降級
#開啟openfeign支持降級 feign.hystrix.enabled=true
(3)在openfeign客戶端中加入hystrix
//調用商品服務的openfeign組件 @FeignClient(value = "products")//標識當前接口是一個feign組件 value=調用服務的id public interface ProductsClient { @GetMapping("/product/testBreak") String testBreak(@PathVariable("id") Integer id); }
(4)開發fallback處理類
//因為使用了openfeign 所以要先開發client接口 //調用商品服務的openfeign組件 @FeignClient(value = "products", fallback = ProductFallback.class)//標識當前接口是一個feign組件 value=調用服務的id public interface ProductsClient { @GetMapping("/product/testBreak") String testBreak(@RequestParam("id") Integer id); } //開發fallback處理類實現client接口 @Component public class ProductFallback implements ProductsClient{ @Override public String testBreak(Integer id) { return "hystrix熔斷,id:"+id; } }
(5)使用openfeign調用,並訪問:http://127.0.0.1:9200/user/showProductTestBreak
@RestController public class UserController { @Autowired private ProductsClient productsClient; /*** * product服務可用時,如果滿足熔斷條件則熔斷,product服務不可用時,則降級。 * @return */ @GetMapping("/user/showProductTestBreak") public String showProductTestBreak() { String rst=productsClient.testBreak(-1); return rst; } }
4、Hystrix DashBoard
Hystrix DashBoard的一個主要優點是它收集了關於每個HystrixCommand的一組度量。Hystrix儀表盤以高效的方式顯示每個斷路器的運行情況。
1)使用方法
(1)引入hystrix dashboard依賴
<!--引入hystrix dashboard依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
(2)在application上開啟hystrix dashboard注解
@SpringBootApplication @EnableCircuitBreaker//開啟斷路器 @EnableHystrixDashboard//開啟hystrix dashboard public class ProductsApplication { public static void main(String[] args) { SpringApplication.run(ProductsApplication.class, args); } }
(3)訪問:http://127.0.0.1:8200/hystrix
未完待續
五、Gateway組件使用
1、什么是服務網關
1)說明
網關統一服務入口,可方便實現對平台眾多服務接口進行管控,對訪問服務的身份認證、防報文重放與防數據篡改、功能調用的業務鑒權、響應數據的脫敏、流量與並發控制,甚至基於API調用的計算或者計費等等。
網關=路由轉發+過濾器
路由轉發:接收一切外界請求,轉發熬后端的微服務上去;
在服務網關中可以完成一系列的橫切功能,例如權限校驗、限流以及監控等,這些都可以通過過濾器完成。
2)為什么需要網關
網關可以實現服務的統一管理;網關可以解決微服務中通用代碼的冗余問題(如權限控制,流量監控,限流等)
3)網關組件在微服務中架構
4)服務網關組件分類
(1)zuul
zuul是從設備和網站到Netflix流媒體應用程序后端的所有請求的前門。作為一個邊緣應用程序,zuul被構建為支持動態路由、監視、彈性和安全性。目前zuul組件以及從1.0更新到2.0,但是作為springcloud官方不再推薦使用zuul2.0,但是依賴支持zuul2.0。
(2)gateway
這個項目提供了一個在springmvc之上構建API網關的庫。springcloud gateway旨在提供一種簡單而有效的方法來路由到api,並為api提供橫切關注點,比如:安全性、監控/度量和彈性。
特性:基於springboot2.x和spring webFlux和Reactor構建響應式異步非阻塞IO模型;動態路由;請求過濾。
2、服務網關組件-gateway
網關配置有兩種方式:一種是快捷方式,一種是完全展開方式。
1)示例
(1)引入依賴
<!--引入gateway網關依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
另外還需要引入consul依賴(也可以引入其他的服務中心依賴)、consul健康檢查依賴。(因為要向服務中心注冊)
在啟動日志中發現,gateway為了效率使用webflux進行異步非阻塞模型的實現,因此和原來的web包沖突,使用gateway時不能引入spring-boot-starter-web包。
(2)配置路由
- 使用配置文件(application.yaml)配置路由(推薦)
server: port: 7500 spring: application: name: gateway cloud: consul: port: 8500 host: localhost discovery: service-name: ${spring.application.name} gateway: routes: - id: users #指定路由唯一標識 uri: http://localhost:8100/ #指定路由服務的地址 predicates: - Path=/user/** #指定路由規則
- 使用java類方式配置路由
@Configuration public class GateWayConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { return routeLocatorBuilder.routes(). route("users_route", r -> r.path("/user/**"). uri("http://localhost:8100/")).build(); } }
(3)測試
http://127.0.0.1:8100/user/findProductAll 和 http://127.0.0.1:7500/user/findProductAll
2)查看網關路由規則列表
gateway提供路由訪問列表的web頁面,但是默認是關閉的,如果想要查看服務器的路由規則可以在配置文件中開啟。
management: endpoints: web: exposure: include: "*" #開啟所有web端點暴露
訪問路由管理列表地址:http://localhost:7500/actuator/gateway/routes
3)實現負載均衡路由轉發
將原來配置文件中配置的uri: http://localhost:9999/改寫成uri: lb://users【其中:lb即loadbalance代表轉發后台服務時使用負載均衡,users代表服務注冊中心上的服務名】
然后開啟根據服務名動態獲取路由;
spring: cloud: gateway: discovery: locator: enabled: true #開啟根據服務名動態獲取路由
4)常用路由predicate(斷言、驗證)
spring: cloud: routes: predicates: - Path=/product/** #指定路由規則 - After=2021-05-01T08:00:00.993+09:00[Asia/Shanghai] #指定日期之后的請求進行路由 - Before=2021-05-02T08:00:00.993+09:00[Asia/Shanghai] #指定日期之前的請求進行路由 - Between=2021-05-01T08:00:00.993+09:00[Asia/Shanghai],2021-05-02T08:00:00.993+09:00[Asia/Shanghai] - Cookie=username,zhansan #基於指定 cookie的請求進行路由 curl http://127.0.0.1:7500/product/showMsg --cookie "username=zhansan" - Cookie=username,[A-Za-z0-9]+ - Header=X-Request-Id,\d+ #基於請求頭總的指定屬性的正則匹配路由 curl http://127.0.0.1:7500/product/showMsg -H "X-Request-Id:121" - Method=GET,POST # 基於指定的請求方式請求進行路由
5)常用的Filter以及自定義filter
路由過濾器允許以某種方式修改傳入的HTTP請求或傳出的HTTP響應。路由篩選器的作用域是特定路由。SpringCloud Gateway包括許多內置的GateWayFilter工廠。
當我們有很多個服務時,客戶端請求各個服務的API時,每個服務都需要做相同的事情,比如鑒權、限流、日志輸出等。
(1)內置過濾器
spring: cloud: gateway: routes: filters: - AddRequestParameter=productId,0001 #增加請求參數 - AddResponseHeader=isGray,true #增加響應參數 - AddRequestHeader=isGray,true #增加請求頭
(2)自定義過濾器
@Configuration public class CustomGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { if(exchange.getRequest().getQueryParams().get("username")!=null){ System.out.println("用戶身份信息合法,請求放行。"); System.out.println("經過全局Filter處理"); Mono<Void> filter=chain.filter(exchange);//放行 放行后繼續向后執行 System.out.println("響應回來Filter處理"); return filter; } System.out.println("用戶身份信息非法,請求攔截。"); return exchange.getResponse().setComplete(); } @Override public int getOrder() { return 0;//數字越小越先執行 } }
六、Config組件使用
1、什么是Config
1)定義
config(配置)又稱為統一配置中心,顧名思義,就是將配置統一管理,配置統一管理的好處是在日后大規模集群部署應用時,相同的服務配置一致,日后再修改配置只需要統一修改全部同步,不需要一個一個服務手動維護。
2、ConfigServer開發
1)開發步驟
(1)引入config server依賴
<!--引入統一配置中心依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
另外還需要引入consul依賴(也可以引入其他的服務中心依賴)、consul健康檢查依賴。(因為要向服務中心注冊)
(2)開啟統一配置中心服務
@SpringBootApplication @EnableConfigServer public class Configserver6500Application { public static void main(String[] args) { SpringApplication.run(Configserver6500Application.class, args); } }
(3)編寫配置文件(application.properties)
- 服務基本配置
server.port=6500 spring.application.name=configserver spring.cloud.consul.host=localhost spring.cloud.consul.port=8500 spring.cloud.consul.discovery.service-name=${spring.application.name}
- 配置遠端倉庫地址
#配置倉庫地址
spring.cloud.config.server.git.uri=https://gitlab.com/**/springcloud-config.git
#私有庫訪問
#spring.cloud.config.server.git.username=****
#spring.cloud.config.server.git.password=****
- 指定分支和本地倉庫位置
#指定分支和倉庫位置 spring.cloud.config.server.git.basedir=D:\\git #一定要是一個空目錄,在首次會將該目錄清空 spring.cloud.config.server.git.default-label=master
2)查看
(1)拉取遠端配置(三種方式)
http://localhost:6500/test-XXXX.properties 或 http://localhost:6500/test-XXXX.json 或 http://localhost:6500/test-XXXX.yaml
(2)查看拉取配置詳細信息
http://localhost:6500/client/dev (client:代表遠端配置的名稱,dev:代表遠端配置的環境),如:http://127.0.0.1:6500/test/dev
3、ConfigClient開發
1)開發步驟
(1)引入config client依賴
<!--引入統一配置中心client依賴--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
另外還需要引入consul依賴(也可以引入其他的服務中心依賴)、consul健康檢查依賴。(因為要向服務中心注冊)
(2)編寫配置文件(application.properties)
- 服務基本配置(要放在遠程倉庫的)
server.port=9100 spring.application.name=configclient spring.cloud.consul.host=localhost spring.cloud.consul.port=8500 spring.cloud.consul.discovery.service-name=${spring.application.name}
- 配置客戶端(原來是application.properties,現在要改成bootstrap.properties,見下面說明)
#服務名放在遠端 #spring.application.name=configclient #開啟統一配置中心服務 spring.cloud.config.discovery.enabled=true #指定統一配置服務中心的服務標識 spring.cloud.config.discovery.service-id=configserver #指定從倉庫的哪個分支拉取配置 spring.cloud.config.label=master #指定拉取配置文件的名稱 spring.cloud.config.name=test #指定拉取配置文件的環境 spring.cloud.config.profile=dev
(3)遠程倉庫創建配置文件
公共配置:test.properties
dev配置:test-dev.properties
prod配置:test-prod.properties
2)使用application.properties報錯
(1)說明:項目目前使用的是application.properties啟動項目,使用這個配置文件在springboot項目啟動過程中不會等待遠程配置拉取,直接根據配置文件中內容啟動,因此當需要注冊中心、服務端口等信息時,遠程配置還沒有拉取到,所以直接報錯。
(2)解決方案:
應該在項目啟動時先等待拉取遠程配置,拉取遠程配置成功之后再根據遠程配置信息啟動即可,為了完成上述要求springboot官方提供了一種解決方案,就是在使用統一配置中心時,應該將微服務的配置文件名修改為bootstrap.(properties|yml),bootstrap.properties作為配置啟動項目時,會優先拉取遠程配置,遠程配置拉取成功之后根據遠程配置啟動當前應用。
4、配置修改配置文件自動生效
在生產環境中,微服務可能非常多,每次修改完遠端配置后,不可能對所有服務進行重新啟動,這個時候需要讓修改配置文件的服務能夠刷新遠程修改之后的配置,從而不要每次重啟服務才能生效,進一步提高微服務系統的維護效率。在springcloud中也為我們提供了手動刷新配置和自動刷新配置兩種策略。
1)手動配置刷新
(1)在config client端加入刷新暴露端點
#開啟所有web端點暴露 management.endpoints.web.exposure.include=*
(2)在需要刷新代碼的類中加入刷新配置的注解:@RefreshScope
@RestController @RefreshScope public class InitController { @Value("${server.port}") private int port; @GetMapping("/configclient/init") public String init(){ return "當前服務的端口為:"+port; } }
(3)使用post請求向config client刷新配置
curl -X POST http://localhost:6500/actuator/refresh #6500 config server端口
2)自動刷新配置
見bus組件使用
七、Bus組件使用
1、簡介
2、實現配置刷新原理
3、自動配置刷新
spring.cloud.config.fail-fast=true
(6)修改遠程配置后在配置中心服務通過執行post接口刷新配置
curl -X POST http://localhost:8080/actuator/bus-refresh
3)指定服務刷新配置
curl -X POST http://localhost:8080/actuator/bus-refresh/configclient:9090
curl -X POST http://localhost:8080/actuator/bus-refresh/configclient
其中configclient代表屬性服務的唯一標識。
4)集成webhook實現自動刷新