(1) 相關博文地址:
學習一下 SpringCloud (一)-- 從單體架構到微服務架構、代碼拆分(maven 聚合): https://www.cnblogs.com/l-y-h/p/14105682.html 學習一下 SpringCloud (二)-- 服務注冊中心 Eureka、Zookeeper、Consul、Nacos :https://www.cnblogs.com/l-y-h/p/14193443.html 學習一下 SpringCloud (三)-- 服務呼叫、負載均衡 Ribbon、OpenFeign : https://www.cnblogs.com/l-y-h/p/14238203.html
(2)代碼地址:
https://github.com/lyh-man/SpringCloudDemo
一、引入 服務降級、熔斷
1、問題 與 解決
【問題:】
通過前面幾篇博客介紹,完成了基本專案創建、服務注冊中心、服務呼叫 以及 負載均衡(也即 各個模塊 已經能正常通信、共同對外提供服務了),
對于一個復雜的分布式系統來說,可能存在數十個模塊,且模塊之間可能會相互呼叫(嵌套),
這就帶來了一個問題:
如果某個核心模塊突然宕機(或者不能提供服務了),那么所有呼叫該 核心模塊服務 的模塊 將會出現問題,
類似于 病毒感染,一個模塊出現問題,將逐步感染其他模塊出現問題,最終導致系統崩潰(也即服務雪崩),
【服務雪崩:】
服務雪崩 指的是 服務提供者 不可用(不能提供服務) 而導致 服務消費者不可用,并逐級放大的程序,
比如:
多個微服務之間形成鏈式呼叫,A、B 呼叫 C,C 呼叫 D,D 呼叫其他服務等,,,
如果 D 因某種原因(宕機、網路延遲等) 不能對外提供服務了,將導致 C 訪問出現問題,而 C 出現問題,將可能導致 A、B 出現問題,也即 問題逐級放大(最終可能引起系統崩潰),
【解決:】
服務降級、服務熔斷 是解決 服務雪崩的 常用手段,
相關技術:
Hystrix(維護狀態,不推薦使用)
Sentienl(推薦使用)

2、服務降級 與 服務熔斷
(1) 服務降級
【服務降級:】
服務降級 指的是 當服務器壓力 劇增 時,根據當前 業務、流量 情況 對一些服務(一般為非核心業務)進行有策略的降級,確保核心業務正常執行,
即 釋放非核心服務 占用的服務器資源 確保 核心任務正常執行,
注:
可以理解為 損失一部分業務能力,保證系統整體正常運行,從而防止 服務雪崩,
資源是有限的,請求并發高時,若不對服務進行降級處理,系統可能花費大量資源進行非核心業務處理,導致 核心業務 效率降低,進而影響整體服務性能,
此處的降級可以理解為 不提供服務 或者 延時提供服務(服務執行暫時不正常,給一個默認的回傳結果,等一段時間后,正常提供服務),
【服務降級分類:】
手動降級:
可以通過修改配置中心配置,并根據事先定義好的邏輯,執行降級邏輯,
自動降級:
超時降級:設定超時時間、超時重試次數,請求超時則服務降級,并使用異步機制檢測 進行 服務恢復,
失敗次數降級:當請求失敗達到一定次數則服務降級,同樣使用異步機制檢測 進行服務恢復,
故障降級:服務宕機了則服務降級,
限流降級:請求訪問量過大則服務降級,
(2)服務熔斷
【服務熔斷:】 服務熔斷 指的是 目標服務不可用 或者 請求回應超時時,為了保證整體服務可用, 不再呼叫目標服務,而是直接回傳默認處理(釋放系統資源),通過某種演算法檢測到目標服務可用后,則恢復其呼叫, 注: 在一定時間內,服務呼叫失敗次數達到一定比例,則認為 當前服務不可用, 服務熔斷 可以理解為 特殊的 服務降級(即 服務不可用 --> 服務降級 --> 服務呼叫恢復), 【martinfowler 相關博客地址:】 https://martinfowler.com/bliki/CircuitBreaker.html

(3)服務降級 和 服務熔斷 的區別
【相同點:】
目標相同:均從 可靠性、可用性 觸發,避免系統崩潰(服務雪崩),
效果相同:均屬于 某功能暫不可用,
【不同點:】
服務降級 一般 是從整體考慮,可以手動關閉 非核心業務,確保 核心業務正常執行,
服務熔斷 一般 是某個服務不可用,自動關閉 服務呼叫,并在一定時間內 重新嘗試 恢復該服務呼叫,
注(個人理解(僅供參考)):
服務降級 可以作為 預防措施(手動降級),即 服務并沒有出錯,但是為了提升系統效率,我主動放棄 一部分非核心業務,保證系統資源足夠用于 執行 核心業務,
服務熔斷 就是 服務出錯的 解決方案(自動降級),即 服務出錯后 的一系列處理,
二、服務降級、熔斷 -- Hystrix
1、什么是 Hystrix ?
【Hystrix:】 Hystrix 是一個用于處理分布式系統 延遲 和 容錯的 開源庫, 目的是 隔離遠程系統、服務和第三方庫的訪問點,停止級聯故障,并在不可避免發生故障的復雜分布式系統中實作恢復能力, 注: 分布式系統難免出現 阻塞、超時、例外 等問題,Hystrix 可以保證在一個服務出問題時,不影響整個系統使用(避免服務雪崩),提高系統的可用性, 雖然 Hystrix 已進入維護模式,不再更新,但還是可以學習一下思想、基本使用, 【常用特性:】 服務降級 服務熔斷 服務監控 【相關地址:】 https://github.com/Netflix/Hystrix

2、使用 JMeter 模擬超時故障發生
(1)什么是 JMeter ?
【JMeter】 Apache 的一款基于 Java 的壓力測驗工具, 注: 有興趣的自行研究,此處不過多贅述, 【官網下載地址:】 http://jmeter.apache.org/download_jmeter.cgi 【JMeter 簡單使用:】 https://www.cnblogs.com/stulzq/p/8971531.html

(2)說明
【說明:】 此處僅簡單演示,不需要啟動集群(單機版 Eureka 即可), eureka_server_7000 作為 服務注冊中心, eureka_client_producer_8001 作為服務提供者, eureka_client_consumer_9001 作為服務提供者, 注: 單機版 Eureka 可參考:https://www.cnblogs.com/l-y-h/p/14193443.html#_label2_1 此處使用 RestTemplate 發送請求,使用上一篇 講到的 OpenFeign 技術亦可, 【演示說明:】 在 eureka_client_producer_8001 新定義一個介面 testTimeout(),內部暫停 2 秒模擬業務處理所需時間, 一般情況下,訪問 eureka_client_producer_8001 提供的 getUser() 介面時,會立即回應, 但是如果大量請求訪問 testTimeout(),而將系統資源(執行緒)耗盡時, 此時若有請求訪問 getUser() 就需要等待 前面請求執行完成后,才能繼續處理, 而此時就可能造成 超時等待 的情況,從而引起一系列問題, 即: 并發度低時: 先訪問 /consumer/user/testTimeout,再訪問 /consumer/user/get/{id} 可以瞬間回傳結果, 并發度高時: 若有大量請求訪問 /consumer/user/testTimeout,導致系統資源(執行緒)暫時耗盡, 此時再訪問 /consumer/user/get/{id} 就需要等待一些時間才能回傳結果, 嚴重時請求會出現超時故障,從而引起系統例外,
(3)定義介面
在 eureka_client_producer_8001 中定義一個新介面 testTimeout(),
在 eureka_client_consumer_9001 中定義一個新介面 呼叫 testTimeout(),
【eureka_client_producer_8001:】 @GetMapping("/testTimeout") public Result testTimeout() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return Result.ok(); } 【eureka_client_consumer_9001:】 @GetMapping("/testTimeout") public Result testTimeout() { return restTemplate.getForObject(PRODUCER_URL + "/producer/user/testTimeout", Result.class); }


(4)啟動服務,并使用 JMeter 測驗
并發度低時:
先訪問 /consumer/user/testTimeout,再訪問 /consumer/user/get/{id} 可以瞬間回傳結果,
注:
看頁面的重繪按鈕,

并發度高時:
使用 JMeter 模擬 200 個執行緒,回圈 100 次,訪問 /consumer/user/testTimeout,
此時再訪問 /consumer/user/get/{id} 時,不能瞬間回傳結果(等待一段時間),


(5)超時故障
前面已經演示了高并發情況下可能出現超時等待情況,而若 業務執行時間過長 或者 服務呼叫設定了超時時間,那么當訪問被阻塞時,將有可能引起故障,
【在宣告 RestTemplate 時,定義超時時間】 @Bean @LoadBalanced // 使用 @LoadBalanced 注解賦予 RestTemplate 負載均衡的能力 public RestTemplate getRestTemplate() { SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory(); httpRequestFactory.setConnectTimeout(2000); httpRequestFactory.setReadTimeout(2000); return new RestTemplate(httpRequestFactory); }


3、Hystrix 實作服務降級
(1)服務降級使用場景
服務降級 目的是 防止 服務雪崩,本質也就是在 服務呼叫 出問題時,應該如何處理,
【服務降級使用場景:】
服務器資源耗盡,請求回應慢,導致請求超時,
服務器宕機 或者 程式執行出錯,導致請求出錯,
即:
服務提供者 回應請求超時了,服務消費者 不能一直等待,需要 服務提供者進行 服務降級,保證 請求在一定的時間內被處理,
服務提供者 宕機了,服務消費者 不能一直等待,需要 服務消費者進行 服務降級,保證 請求在一定的時間內被處理,
服務提供者正常,但 服務消費者 出現問題了,需要服務消費者 自行 服務降級,
注:
服務降級一般在 服務消費者 中處理,服務提供者 也可以 進行處理,
(2)在 服務提供者 上實作服務降級(超時自動降級)
在 eureka_client_producer_8001 代碼基礎上進行補充,
Step1:
引入 hystrix 依賴,
【引入依賴:】
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

Step2:
通過 @HystrixCommand 注解 撰寫 服務降級策略,
【簡單說明:】 @HystrixCommand 表示指定 服務降級 或者 服務熔斷的策略, fallbackMethod 表示服務呼叫失敗(請求超時 或者 程式執行例外)后執行的方法(方法引數要與 原方法一致), commandProperties 表示配置引數, @HystrixProperty 設定具體引數, 注: 詳細引數情況可以參考 HystrixCommandProperties 類, com.netflix.hystrix.HystrixCommandProperties 【定義服務降級策略:】 public Result testTimeoutReserveCase() { return Result.ok().message("當前服務器繁忙,請稍后再試!!!"); } // 定義服務降級策略 @HystrixCommand( // 當請求超時 或者 介面例外時,會呼叫 fallbackMethod 宣告的方法(方法引數要一致) fallbackMethod = "testTimeoutReserveCase", commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="https://www.cnblogs.com/l-y-h/archive/2021/02/02/1500") } ) @GetMapping("/testTimeout") public Result testTimeout() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } return Result.ok(); }

Step3:
在啟動類上添加 @EnableCircuitBreaker 注解,開啟服務降級、熔斷,

Step4:
運行測驗(此處演示的是 超時自動降級),
此處定義介面超時時間為 1.5 秒,模擬 0.5 秒業務處理時間,使用 JMeter 壓測該介面時,與上面演示的類似,會出現請求超時的情況,而一旦請求超時,則會觸發 fallbackMethod 方法,直接回傳資料,而不會持續等待,
如下圖所示,

(3)配置默認服務降級方法
通過上面簡單演示可以完成 服務降級,但是存在一個問題,如果為每一個介面都系結一個 fallbackMethod,那么代碼將非常冗余,
通過 @DefaultProperties 注解 定義一個默認的 defaultFallback 方法,介面例外時呼叫默認的方法,并僅對特殊的介面進行單獨處理,從而減少代碼冗余,
如下,新增一個 運行時例外,訪問介面時,將會呼叫 globalFallBackMethod() 方法,
而前面特殊定義的 testTimeout 超時后,仍呼叫 testTimeout_reserve_case() 方法,
@DefaultProperties(defaultFallback = "globalFallBackMethod") public class UserController { public Result globalFallBackMethod() { return Result.ok().message("系統例外,請稍后再試!!!"); } @GetMapping("/testRuntimeError") @HystrixCommand public Result testRuntimeError() { int temp = 10 / 0; return Result.ok(); } }


4、OpenFeign 實作服務降級
(1)說明
【說明:】 上面使用 Hystrix 簡單演示了 服務提供者 的服務降級, 這里使用 OpenFeign 演示 服務消費者 的服務降級, 注: 重新新建一個模塊 eureka_openfeign_client_consumer_9007 作為服務消費者用于演示, 可參考上一篇 OpenFeign 的使用:https://www.cnblogs.com/l-y-h/p/14238203.html#_label3_2 服務提供者仍然是 eureka_client_producer_8001,
(2)配置 OpenFeign 基本代碼環境
Step1:
創建模塊 eureka_openfeign_client_consumer_9007,
修改父工程 與 當前工程 pom.xml 檔案,
修改配置類,
在啟動類上添加 @EnableFeignClients 注解,
【依賴:】 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> 【application.yml】 server: port: 9007 spring: application: name: eureka-openfeign-client-consumer eureka: instance: appname: eureka-openfeign-client-consumer-9007 # 優先級比 spring.application.name 高 instance-id: ${eureka.instance.appname} # 設定當前實體 ID client: register-with-eureka: true # 默認為 true,注冊到 注冊中心 fetch-registry: true # 默認為 true,從注冊中心 獲取 注冊資訊 service-url: # 指向 注冊中心 地址,也即 eureka_server_7000 的地址, defaultZone: http://localhost:7000/eureka # 設定 OpenFeign 超時時間(OpenFeign 默認支持 Ribbon) ribbon: # 指的是建立連接所用的超時時間 ConnectTimeout: 2000 # 指的是建立連接后從服務器獲取資源的超時時間(即請求處理的超時時間) ReadTimeout: 2000

Step2:
使用 @FeignClient 撰寫服務呼叫,
【ProducerFeignService:】 package com.lyh.springcloud.eureka_openfeign_client_consumer_9007.service; import com.lyh.springcloud.common.tools.Result; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(value = "EUREKA-CLIENT-PRODUCER-8001") @Component public interface ProducerFeignService { @GetMapping("/producer/user/get/{id}") Result getUser(@PathVariable Integer id); @GetMapping("/producer/user/testTimeout") Result testFeignTimeout(); @GetMapping("/producer/user/testRuntimeError") Result testRuntimeError(); }

Step3:
撰寫 controller,并進行測驗 openfeign 是否能成功訪問服務,
【ConsumerController】 package com.lyh.springcloud.eureka_openfeign_client_consumer_9007.controller; import com.lyh.springcloud.common.tools.Result; import com.lyh.springcloud.eureka_openfeign_client_consumer_9007.service.ProducerFeignService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/consumer/user") public class ConsumerController { @Autowired private ProducerFeignService producerFeignService; @GetMapping("/get/{id}") public Result getUser(@PathVariable Integer id) { return producerFeignService.getUser(id); } @GetMapping("/testTimeout") public Result testFeignTimeout() { return producerFeignService.testFeignTimeout(); } @GetMapping("/testRuntimeError") public Result testFeignRuntimeError() { return producerFeignService.testRuntimeError(); } }


(3)OpenFeign 實作服務降級
【步驟:】 Step1:在組態檔中,配置 feign.feign.enabled=true,開啟服務降級, Step2:定義一個 實作類,實作 服務呼叫的 介面,并為每個方法重寫 呼叫失敗的邏輯, Step3:在 @FeignClient 注解中,通過 fallback 引數指定 該實作類,
Step1:
在組態檔中,開啟服務降級,
【application.yml】 # 開啟服務降級 feign: hystrix: enabled: true

Step2:
定義一個實作類,實作 服務呼叫的介面,
@Component 注解不要忘了,啟動時可能會報錯,
【ProducerFeignServiceImpl:】 package com.lyh.springcloud.eureka_openfeign_client_consumer_9007.service.impl; import com.lyh.springcloud.common.tools.Result; import com.lyh.springcloud.eureka_openfeign_client_consumer_9007.service.ProducerFeignService; import org.springframework.stereotype.Component; @Component public class ProducerFeignServiceImpl implements ProducerFeignService { @Override public Result getUser(Integer id) { return Result.ok().message("系統例外,請稍后再試 -- 11111111111"); } @Override public Result testFeignTimeout() { return Result.ok().message("系統例外,請稍后再試 -- 222222222222"); } @Override public Result testRuntimeError() { return Result.ok().message("系統例外,請稍后再試 -- 333333333333"); } }

注:
未添加 @Component 注解,啟動會報下面的錯誤,
【報錯資訊:】 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'consumerController': Unsatisfied dependency expressed through field 'producerFeignService'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.lyh.springcloud.eureka_openfeign_client_consumer_9007.service.ProducerFeignService': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: No fallback instance of type class com.lyh.springcloud.eureka_openfeign_client_consumer_9007.service.impl.ProducerFeignServiceImpl found for feign client EUREKA-CLIENT-PRODUCER-8001

Step3:
在 @FeignClient 注解上,通過 fallback 引數指定上面定義的實作類,
package com.lyh.springcloud.eureka_openfeign_client_consumer_9007.service; import com.lyh.springcloud.common.tools.Result; import com.lyh.springcloud.eureka_openfeign_client_consumer_9007.service.impl.ProducerFeignServiceImpl; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(value = "EUREKA-CLIENT-PRODUCER-8001", fallback = ProducerFeignServiceImpl.class) @Component public interface ProducerFeignService { @GetMapping("/producer/user/get/{id}") Result getUser(@PathVariable Integer id); @GetMapping("/producer/user/testTimeout") Result testFeignTimeout(); @GetMapping("/producer/user/testRuntimeError") Result testRuntimeError(); }

Step4:
簡單測驗一下,
當服務提供者 宕機時,此時服務呼叫失敗,將會執行 實作類中的邏輯,
而服務提供者正常提供服務時,由于上面已經在 服務提供者 處配置了 服務降級,則執行 服務提供者的服務降級策略,

5、Hystrix 實作服務熔斷
(1)說明
【服務熔斷:】 服務熔斷可以理解為特殊的服務降級,當某個服務出錯或者回應時間長時,將會進行服務降級處理, 從而熔斷該服務的呼叫,但其會檢測服務是否正常,若正常,則恢復服務呼叫, 注: 代碼方面 與 上面配置 超時服務自動降級 類似(在其基礎上進行代碼擴充), 【斷路器開啟、關閉條件:】 開啟條件: 滿足一定的請求閾值(默認 10 秒內請求數超過 20),且失敗率達到閾值(默認 10 秒內 50% 的請求失敗),此時將會開啟斷路器, 關閉條件: 斷路器開啟一段時間后(默認 5 秒),此時斷路器處于半開狀態,會對其中一部分請求進行轉發,如果成功訪問,則斷路器關閉, 若請求仍然失敗,則再次進入開啟狀態,

(2)添加介面配置服務熔斷
在 eureka_client_producer_8001 中新增一個介面,并配置服務熔斷邏輯,
【服務熔斷:】 public Result testCircuitBreakerFallBack(@PathVariable Integer id) { return Result.ok().message("呼叫失敗, ID 不能為負數"); } @GetMapping("/testCircuitBreaker/{id}") @HystrixCommand(fallbackMethod = "testCircuitBreakerFallBack", commandProperties = { // 是否開啟斷路器,默認為 true, @HystrixProperty(name = "circuitBreaker.enabled", value = "https://www.cnblogs.com/l-y-h/archive/2021/02/02/true"), // 在一定時間內,請求總數達到了閾值,才有資格進行熔斷,默認 20 個請求, @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "https://www.cnblogs.com/l-y-h/archive/2021/02/02/10"), // 熔斷之后,重新嘗試恢復服務呼叫的時間,在此期間,會執行 fallbackMethod 定義的邏輯,默認 5 秒, @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "https://www.cnblogs.com/l-y-h/archive/2021/02/02/10000"), // 出錯閾值,請求總數超過了閾值,并且呼叫失敗率達到一定比率,會熔斷,默認 50%, @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "https://www.cnblogs.com/l-y-h/archive/2021/02/02/60") }) public Result testCircuitBreaker(@PathVariable Integer id) { if (id < 0) { throw new RuntimeException("ID 不能為負數"); } return Result.ok().message("呼叫成功, ID = " + id); }

(3)直接訪問該服務測驗一下(使用 JMeter 測驗亦可),
如下圖所示,當請求失敗達到一定比率,將會開啟斷路器,
一段時間后,嘗試恢復服務呼叫,關閉斷路器,

6、Hystrix Dashboard
(1)Dashboard
Hystrix 提供了圖形化的監控工具(Hystrix Dashboard)進行準實時的呼叫監控,其可以持續的記錄通過 Hystrix 發送的請求執行資訊,并以圖形、統計報表的形式呈現給用戶,
SpringCloud 對其進行了整合,匯入相關依賴即可,
(2)使用
Step1:
引入依賴(hystrix-dashboard 以及 actuator),
【依賴:】
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Step2:
在啟動類上添加 @EnableHystrixDashboard 注解,開啟 Dashboard,

Step3:
啟動服務后,訪問 http://localhost:8001/hystrix,填寫需要監控的地址即可,
開啟監控后,訪問配置了 @HystrixCommand 注解的服務時,將會觸發監控,


7、Dashboard 錯誤解決(Unable to connect to Command Metric Stream.)
(1)錯誤
使用 Dashboard 最常見的錯誤就是 Unable to connect to Command Metric Stream,
根據控制臺列印的 日志進行相關配置即可解決此錯誤,
錯誤效果如下圖所示,

(2)錯誤一與解決:
【錯誤一:】 控制臺列印錯誤如下: Proxy opening connection to: http://localhost:8001/hystrix.stream 【解決:】 在配置類中添加如下配置, /** * 錯誤 Unable to connect to Command Metric Stream. 本質是無法決議 "/hystrix.stream", * 自行配置一下即可, */ @Bean public ServletRegistrationBean getServlet() { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; }

(3)錯誤二與解決:
【錯誤二:】 上面解決了連接錯誤,但是仍然報錯, 控制臺列印錯誤如下: Origin parameter: http://localhost:8001/hystrix.stream is not in the allowed list of proxy host names. If it should be allowed add it to hystrix.dashboard.proxyStreamAllowList. 【解決:】 在組態檔中配置 proxyStreamAllowList 放行 host 即可, hystrix: dashboard: proxy-stream-allow-list: "*" # proxy-stream-allow-list: "localhost"

三、服務降級、熔斷 -- Sentinel
1、什么是 Sentinel?
(1)什么是 Sentinel?
【Sentinel:】 Sentinel 是阿里開源的專案,面向云原生微服務的高可用流控防護組件, 從流量控制、熔斷降級、系統負載保護等多個維度來保障服務之間的穩定性, 注: 官方檔案上寫的還是很詳細的,并提供了相應的中文檔案, 此處不過多描述,僅介紹使用,相關概念、原理 請自行翻閱檔案, 【官網地址:】 https://github.com/alibaba/Sentinel https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
Sentinel 主要特性如下(圖片來源于網路:)

(2)Sentinel 組成
【Sentinel 組成:】 Sentinel 可以分為兩部分:Java 客戶端 、Dashboard 控制臺, Java 客戶端(核心庫): 核心庫 不依賴于 任何框架、庫,能夠運行于 Java7 及以上版本的 Java 運行時環境, 同時對 Dubbo / Spring Cloud 等框架也有較好的支持, Dashboard 控制臺: 基于 SpringBoot 開發,打包后可直接運行,無需額外的 Tomcat 容器部署, 控制臺主要負責管理推送規則、監控、集群限流分配管理、機器發現等, 注: 控制臺使用參考檔案: https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0 控制臺 jar 包下載: https://github.com/alibaba/Sentinel/releases 注: 通過 Dashboard 控制臺,可以很輕松的通過 web 頁面設定 服務降級、熔斷等規則,也可以通過代碼的方式進行配置, 個人比較傾向于 web 頁面操作,省去了撰寫代碼的作業(視作業環境而定), 后面介紹的 Dashboard 操作均以 web 界面進行操作,若想通過代碼進行配置,請自行翻閱官方檔案, web 頁面編輯的規則 在 重啟服務后 會丟失,需要將其進行持久化處理,一般都是持久化到 nacos 中(后續介紹),
(3)Hystrix 與 Sentinel 區別
【官網地址:】 https://github.com/alibaba/Sentinel/wiki/Guideline:-%E4%BB%8E-Hystrix-%E8%BF%81%E7%A7%BB%E5%88%B0-Sentinel 注: 詳情請自行查閱官網檔案,


2、基本模塊構建
(1)說明
【說明:】 Sentinel 一般與 Nacos 一起使用,Nacos 使用后續介紹,此處仍使用 Eureka 進行整合, 新建一個 eureka_client_sentinel_producer_8010 模塊(引入核心庫依賴)進行演示, 從官網下載 sentinel-dashboard,用于啟動 Dashboard 界面, 注: 下載地址:https://github.com/alibaba/Sentinel/releases
(2)下載并啟動 sentinel-dashboard,
Step1:下載 sentinel-dashboard,

Step2:命令列啟動 jar 包,
【命令列啟動 jar 包:】 java -jar sentinel-dashboard-1.8.0.jar 注: 啟動后,默認訪問埠為 8080,可以通過 -Dserver.port 引數進行修改, 比如: java -jar -Dserver.port=8888 sentinel-dashboard-1.8.0.jar 【訪問地址:】 通過 IP + 埠 即可進入登錄界面,登錄用戶、密碼 都默認為 sentinel, 比如:http:localhost:8888


(3)基本模塊 eureka_client_sentinel_producer_8010 創建
Step1:
修改 父工程、當前工程 pom.xml 檔案,并引入相關依賴,
【直接引入依賴(帶上版本號):】 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.1.0.RELEASE</version> </dependency> 【或者在父工程中統一進行版本管理:】 <properties> <spring.cloud.alibaba.version>2.1.0.RELEASE</spring.cloud.alibaba.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring.cloud.alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> 注: spring-cloud-alibaba 各版本地址: https://github.com/alibaba/spring-cloud-alibaba/releases

Step2:
修改組態檔,配置 dashboard 資訊,
【application.yml】 server: port: 8010 spring: application: name: eureka_client_sentinel_producer_8010 # 配置 sentinel cloud: sentinel: transport: # 配置 sentinel-dashboard 地址,此處在本地啟動,所以 host 為 localhost dashboard: localhost:8888 # 應用與 dashboard 互動的埠,默認為 8719 埠 port: 8719 eureka: instance: appname: eureka_client_sentinel_producer_8010 # 優先級比 spring.application.name 高 instance-id: ${eureka.instance.appname} # 設定當前實體 ID client: register-with-eureka: true # 默認為 true,注冊到 注冊中心 fetch-registry: true # 默認為 true,從注冊中心 獲取 注冊資訊 service-url: # 指向 注冊中心 地址,也即 eureka_server_7000 的地址, defaultZone: http://localhost:7000/eureka

Step3:
撰寫測驗 controller,進行測驗,
【TestController】 package com.spring.cloud.eureka_client_sentinel_producer_8010.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/testSentinel") public class TestController { @GetMapping("/hello") public String hello() { return "Hello World"; } @GetMapping("/relation") public String relation() { return "relation"; } }
Step4:
啟動 eureka_server_7000、以及當前服務 ,測驗一下,
注:
即使服務啟動,Sentinel Dashboard 也是空白的,需要呼叫一下當前服務的介面,其相關資訊才會出現在 Sentinel 中,


3、Sentinel Dashboard 使用 -- 流控規則
(1)什么是流量控制(flow control)?
【流量控制:】 流量控制 本質上是 監控 應用流量的 QPS 或者 并發執行緒數等指標, 當監控的指標達到 閾值 后,將會對訪問進行限制,減少請求的正常回應, 從而避免應用 被瞬間的高并發請求擊垮而崩潰,進而保障應用的高可用性, 注: QPS(Query Per Second):每秒查詢率,即服務每秒能回應的查詢(請求)次數, 【檔案地址:】 https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
(2)流控規則基本引數
【相關類:】 com.alibaba.csp.sentinel.slots.block.flow.FlowRule 注: 通過代碼設定流控規則可以參考如下代碼: https://github.com/alibaba/Sentinel/blob/master/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/FlowQpsDemo.java 【流控規則頁面引數:】 資源名: 默認為 請求的資源路徑,唯一, 針對來源: 默認為 default,表示不區分來源, 注: 網上查閱的資料都說可以根據 微服務名 進行限流,有待確認, 此處未研究原理,留個坑,后續有時間再去研究, 閾值型別: QPS: 當呼叫該資源介面的 QPS 達到閾值后,將會限流, 執行緒數: 當呼叫該資源介面的 執行緒數 達到閾值后,將會限流, 單機閾值: 設定 閾值型別(QPS、執行緒數)的 閾值, 流控模式: 直接: 當資源介面達到限流條件時,會當前資源直接限流, 關聯: 當某個資源關聯的資源達到限流條件時,則 當前資源 被限流, 鏈路: 資源之間的呼叫形成呼叫鏈路,而 鏈路 模式僅記錄 指定入口的流量,如果達到限流條件,則限流, 流控效果: 快速失敗: 一旦達到限流條件,則直接拋例外(FlowException),然后走失敗的處理邏輯, Warm Up: 根據冷加載因子(coldFactor,默認 3),剛開始閾值請求數為 原閾值/coldFactor,經過一段時間后,閾值才會變為 原閾值, 排隊等待: 請求會排隊等待執行,勻速通過,此時的 閾值型別必須為 QPS,

(3)演示 -- 直接、快速失敗,
【說明:】 配置 /testSentinel/hello 的流控規則,不區分請求來源, 當 QPS 超過 1(即 1 秒鐘超過 1 次查詢)時,將會執行 直接限流,效果為 快速失敗(會顯示默認錯誤), 【設定流控規則如下:】 資源名: /testSentinel/hello 針對來源: default 閾值型別: QPS 單機閾值: 1 流控模式: 直接 流控效果: 快速失敗

如下圖所示,1 秒重繪一次是正常回傳的結果,而 1 秒重繪多次后,將會輸出默認的錯誤資訊,

(4)演示 -- 關聯、快速失敗,
【說明:】 現有資源 A、B,A 為 /testSentinel/hello,B 為 /testSentinel/relation, 配置 A 的流控規則,不區分請求來源,將 A 關聯 B, 當 B 的 QPS 超過 1(即 1 秒鐘超過 1 次查詢)時,A 將會被限流,效果為 快速失敗(會顯示默認錯誤), 【設定流控規則如下:】 資源名: /testSentinel/hello 針對來源: default 閾值型別: QPS 單機閾值: 1 流控模式: 關聯 關聯資源: /testSentinel/relation 流控效果: 快速失敗 【實際使用場景舉例:】 兩個資源之間具有依賴關系或者競爭資源時,可以使用關聯, 比如: 對資料庫 讀操作、寫操作 進行限制,可以設定寫操作優先,當 寫操作 過于頻繁時,讀操作將被限流,

如下圖所示,正常訪問 A 是沒問題的,但是 B 在 1 秒內多次重繪后,A 將會輸出默認出錯資訊,

(5)演示 -- 直接、Wram Up,
【說明:】 配置 /testSentinel/hello 的流控規則,不區分請求來源, 設定 QPS 為 6,預熱時間為 3 秒,則開始 QPS 閾值將為 2,預熱時間結束后,QPS 會恢復到 6, 【設定流控規則如下:】 資源名: /testSentinel/hello 針對來源: default 閾值型別: QPS 單機閾值: 6 流控模式: 直接 流控效果: Warm Up 預熱時長: 3 【實際場景舉例:】 秒殺系統開啟瞬間會有很多請求進行訪問,如果不做限制,可能一下子系統直接崩潰了, 采用 Warm Up 方式,給系統一個緩沖時間,慢慢的增大 QPS,

如下圖所示,開始 QPS 較小,重繪容易報錯,3 秒后,QPS 恢復原值,重繪不容易報錯,

(6)演示 -- 直接、排隊等待
【說明:】 配置 /testSentinel/hello 的流控規則,不區分請求來源, QPS 超過 1 時,請求將會排隊等待,超時時間為 2 秒,超時后將會輸出錯誤資訊, 【設定流控規則如下:】 資源名: /testSentinel/hello 針對來源: default 閾值型別: QPS 單機閾值: 1 流控模式: 直接 流控效果: 排隊等待 超時時間: 2000 【實際場景舉例:】 通常用于處理 間隔性請求, 比如: 訊息佇列,某瞬間的請求很多,但之后卻沒有請求,此時可以使用排隊等待,將請求延遲執行(而不是直接拒絕),

如下圖所示,快速重繪頁面時,請求將會排隊等待執行,超時后將會報錯,

4、@SentinelResource 注解
(1)@SentinelResource 注解
@SentinelResource 可以等同于 Hystrix 中的 @HystrixCommand 注解進行理解,
【相關地址:】 https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81 【@SentinelResource:】 @SentinelResource 注解用于定義資源,并提供了可選的例外處理 以及 fallback 配置項, value:資源名稱,必須項(不能為空), blockHandler: 指定限流例外(BlockException)發生后,應該執行的方法, 注意事項: 方法訪問權限修飾符為 public, 回傳值型別 與 原方法回傳值型別一致, 引數型別 與 原方法一致,并追加一個 額外引數,引數型別為 BlockException, blockHandler 指定的函式默認需要與 原方法在同一個類中, blockHandlerClass: 指定限流例外(BlockException)發生后,應該執行的方法(此方法可以位于 其他類), 注意事項: 方法訪問權限修飾符為 public, 方法必須是 static 函式,否則無法決議, fallback: 指定例外(除了 exceptionsToIgnore 指定的例外外的例外)發生后,應該執行的方法, 注意事項: 回傳值型別 與 原方法回傳值型別一致, 引數型別 與 原方法一致,可以額外增加一個 Throwable 型別的引數用于接收對應的例外, fallback 指定的函式默認需要與 原方法在同一個類中, fallbackClass: 指定例外發生后,應該執行的方法(此方法可以位于 其他類), 注意事項: 方法訪問權限修飾符為 public, 方法必須是 static 函式,否則無法決議, defaultFallback: 指定例外發生后,執行默認的邏輯(即 通用處理邏輯), 與 fallback 類似,但 其指定的方法引數為空,可以額外增加一個 Throwable 型別的引數用于接收對應的例外, 當 fallback 與 defaultFallback 同時存在時,只有 fallback 會生效, exceptionsToIgnore: 用于指定哪些例外被排除掉,不會計入例外統計中,也不會進入 fallback 邏輯中(直接對外拋出原例外), 【@SentinelResource 使用注意事項:】 若 blockHandler 和 fallback 都配置了,當限流降級例外發生時(即 拋出 BlockException),只會執行 blockHandler 指定的方法, 若未配置 fallback、blockHandler 時,則限流降級時,則可能直接拋出 BlockException 例外,若方法本身沒定義 throws BlockException,則例外將會被 JVM 包裝為 UndeclaredThrowableException 例外, 可以簡單的理解為: blockHandler 用于指定 限流、降級 等例外(BlockException)發生后應該執行的規則, fallback 用于指定 其他例外(比如: RuntimeException)發生后應該執行的規則,

(2)@SentinelResource 使用舉例
【說明:】 在 controller 中新增如下代碼, fallback() 表示例外發生后的回呼函式, defaultFallback() 表示例外發生后默認的回呼函式, blockHandler()、blockHandler2() 表示 流控、降級 等 BlockException 例外發生后的回呼函式, testBlockHandler() 用于測驗 blockHandler 引數, testFallback() 用于測驗 fallback 引數, testDefaultFallback() 用于測驗 defaultFallback 引數, 【新增代碼:】 public String fallback(Integer id, Throwable ex) { return ex.getMessage(); } public String defaultFallback(Throwable ex) { return ex.getMessage(); } public String blockHandler(BlockException ex) { return "block Handler"; } public String blockHandler2(Integer id, BlockException ex) { return "block Handler --------- 2"; } @GetMapping("/testBlockHandler") @SentinelResource(value = "testBlockHandler", blockHandler = "blockHandler") public String testBlockHandler() { return "ok"; } @GetMapping("/testFallback/{id}") @SentinelResource(value = "testFallback", blockHandler = "blockHandler", fallback = "fallback") public String testFallback(@PathVariable Integer id) { if (id > 10) { throw new RuntimeException("fallback"); } return "ok"; } @GetMapping("/testDefaultFallback/{id}") @SentinelResource(value = "testDefaultFallback", blockHandler = "blockHandler2", defaultFallback = "defaultFallback") public String testDefaultFallback(@PathVariable Integer id) { if (id > 10) { throw new RuntimeException("defaultFallback"); } return "ok"; }

Step1:
訪問 testBlockHandler(),測驗 blockHandler 執行回呼函式,
如下,給 testBlockHandler 添加流控規則,當 QPS 大于 1 時,將進行限流,此時將會執行 blockHandler 指定的回呼函式,
注:
正常訪問 testBlockHandler() 時,sentinel dashboard 會監控到兩個資源名,此處應選擇 @SentinelResource 注解中 value 定義的資源名,并配置 流控、降級 規則,



Step2:
訪問 testFallback(),測驗 fallback 執行回呼函式,
當 id 小于等于 10 時,正常呼叫,
當 id 大于 10 時,拋出例外后被 fallback 接收并執行回呼函式,
同樣,設定 流控規則,QPS 大于 1 時,限流,但由于 blockHandler 引數指定的回呼方法引數 與 原方法不同,所以該回呼函式不生效(空白),
注:
限流、降級 等例外 執行的是 blockHandler 指定的回呼函式,
而其他例外 執行的是 fallback 指定的回調函式,


Step3:
訪問 testDefaultFallback(),測驗 defaultFallback 執行回呼函式,
與上例類似,只是此處 blockHandler 回呼函式引數 與 原方法相同,可以呼叫成功,


5、Sentinel Dashboard 使用 -- 降級規則、系統規則(系統自適應限流)
(1)熔斷降級
熔斷降級相關概念,前面在 Hystrix 已經介紹了,
此處僅演示 Sentinel 降級操作,
【降級策略:】 慢呼叫比例(SLOW_REQUEST_RATIO): 若選擇 慢呼叫比例 作為閾值,需外同時設定幾個引數, 引數: 慢呼叫最大回應時間(最大 RT),當請求回應時間大于該值時,將被統計為慢呼叫, 比例閾值,比率的閾值范圍是 [0.0, 1.0],代表 0% - 100%,當慢呼叫比例大于該值時,將會觸發熔斷機制, 最小請求數,單位統計時長內接收請求的最小數, 熔斷時長,熔斷執行的時間, 簡單解釋: 當單位統計時長(statIntervalMs)內請求數目 大于 最小請求數,且 慢呼叫比例(超時請求占總請求數的比例) 大于 比例閾值 時, 將會在一定的 熔斷時長 內熔斷請求(執行 熔斷的相關代碼), 熔斷時長結束后,會進入探測恢復狀態(即 Hystrix 中提到的 HALF-OPEN 狀態),若檢測到接下來的一個請求正常呼叫,則結束熔斷,若依舊超時,則再次熔斷, 注: 此處的 HALF-OPEN 狀態,來源于官網介紹(針對 Sentinel 1.8.0 及以上版本), 舊版本可能沒有 HALF-OPEN 狀態(沒實際驗證過), 例外比例(ERROR_RATIO): 例外比例 與 慢呼叫比例 類似,例外比例的引數少了個 最大回應時間, 簡單解釋: 當單位統計時長(statIntervalMs)內請求數目 大于 最小請求數,且 例外比例(例外請求占總請求數的比例) 大于 比例閾值 時, 將會在一定的 熔斷時長 內熔斷請求(執行 熔斷的相關代碼), 熔斷時長結束后,會進入探測恢復狀態(HALF-OPEN 狀態),若檢測到接下來的一個請求正常呼叫,則結束熔斷,否則會再次被熔斷, 例外數(ERROR_COUNT): 例外數 與 例外比例 類似,例外數 將引數 例外比例 變為 例外數(直接監控例外數,而非比例), 簡單解釋: 當單位統計時長(statIntervalMs)內 例外請求數 超過 例外數閾值 后, 將會在一定的 熔斷時長 內熔斷請求(執行 熔斷的相關代碼), 熔斷時長結束后,會進入探測恢復狀態(HALF-OPEN 狀態),若檢測到接下來的一個請求正常呼叫,則結束熔斷,否則會再次被熔斷,

(2)演示 -- 例外比例
在上面 testDefaultFallback() 基礎上,添加 降級 規則,演示 例外比例 降級,
注:
洗掉添加的流控規則,并指定 降級規則,
當降級發生時,將會觸發 blockHandler 指定的回呼方法,


(3)系統規則
【相關檔案:】 https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81 【什么是系統規則(系統自適應限流):】 系統自適應限流 是從 整體維度 對 應用程式 入口流量 進行控制,即 在呼叫應用程式的 方法(介面) 前,將請求攔截下來, 通過自適應的流控策略,讓 系統的入口流量 和 系統的負載 達到一個平衡,即 讓系統盡可能 在保證 最大吞吐量的同時 保證 系統整體的穩定性, 常用指標: 應用的負載(Load)、 CPU 使用率、 總體平均回應時間(RT)、 入口 QPS、 并發執行緒數 等,


6、OpenFeign 整合 Sentinel(引入 sentinel 依賴需要注意版本問題)
(1)說明
【說明:】
OpenFeign 一般用于消費端,此處以 eureka_client_consumer_9001 為基礎,整合 Sentinel,
注:
此處為了省事,直接用之前創建好的子模塊,亦可自行創建新的模塊,
使用流程 與 Hystrix 類似,
(2)整合 Sentinel
Step1:
在 eureka_client_consumer_9001 基礎上引入 依賴,
【依賴:】 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- alibaba-sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.1.RELEASE</version> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </exclusion> </exclusions> </dependency> 【注意事項:(巨坑)】 alibaba-sentinel 的版本可能會影響程式的執行, 此處使用的版本: springcloud Hoxton.SR9 spring.cloud.alibaba 2.1.0.RELEASE springboot 2.3.5.RELEASE 引入 alibaba-sentinel 依賴后,服務一直無法啟動, 報錯: nested exception is java.lang.AbstractMethodError: Receiver class com.alibaba.cloud.sentinel.feign.SentinelContractHolder does not define or inherit an implementation of the resolved method 'abstract java.util.List parseAndValidateMetadata(java.lang.Class)' of interface feign.Contract. 具體原因沒整明白,但是如上引入依賴后,可以解決問題(有時間再去研究),


Step2:
修改組態檔,開啟 Sentinel 對 Feign 的支持,
【application.yml】 # 開啟 sentinel 對 feign 的支持 feign: sentinel: enabled: true

Step3:
使用 @FeignClient 撰寫服務呼叫,
【ProducerFeignService】 package com.lyh.springcloud.eureka_client_consumer_9001.service; import com.lyh.springcloud.eureka_client_consumer_9001.service.impl.ProducerFeignServiceImpl; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(value = "EUREKA-CLIENT-SENTINEL-PRODUCER-8010", fallback = ProducerFeignServiceImpl.class) @Component public interface ProducerFeignService { @GetMapping("/testSentinel/testDefaultFallback/{id}") String testDefaultFallback(@PathVariable Integer id); @GetMapping("/testSentinel/hello") String hello(); } 【ProducerFeignServiceImpl】 package com.lyh.springcloud.eureka_client_consumer_9001.service.impl; import com.lyh.springcloud.eureka_client_consumer_9001.service.ProducerFeignService; import org.springframework.stereotype.Component; @Component public class ProducerFeignServiceImpl implements ProducerFeignService { @Override public String testDefaultFallback(Integer id) { return "系統例外,請稍后重試 --------- 1111111111111"; } @Override public String hello() { return "系統例外,請稍后重試 --------- 2222222222222"; } }

Step4:
撰寫 controller,
【TestController】 package com.lyh.springcloud.eureka_client_consumer_9001.controller; import com.lyh.springcloud.eureka_client_consumer_9001.service.ProducerFeignService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/consumer2") @RestController public class TestController { @Autowired private ProducerFeignService producerFeignService; @GetMapping("/testDefaultFallback/{id}") public String testDefaultFallback(@PathVariable Integer id) { return producerFeignService.testDefaultFallback(id); } @GetMapping("/hello") public String hello() { return producerFeignService.hello(); } }

Step5:
在啟動類上添加 @EnableFeignClients 注解,開啟 feign 功能,

Step6:
測驗,
給 testDefaultFallback() 添加流控規則,QPS 大于 1 時將限流,
QPS 小于等于 1 時,正常訪問,
若遠程服務斷開后,訪問 testDefaultFallback() 將失敗,從而執行本地添加的邏輯,


7、持久化配置資訊到 Nacos
后續使用到 Nacos 再介紹,此處暫時省略,,,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/255841.html
標籤:其他
