(1) 相關博文地址:html
學習一下 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)代碼地址:java
https://github.com/lyh-man/SpringCloudDemo
【問題:】
經過前面幾篇博客介紹,完成了基本項目建立、服務註冊中心、服務調用 以及 負載均衡(也即 各個模塊 已經能正常通訊、共同對外提供服務了)。
對於一個複雜的分佈式系統來講,可能存在數十個模塊,且模塊之間可能會相互調用(嵌套),
這就帶來了一個問題:
若是某個核心模塊忽然宕機(或者不能提供服務了),那麼全部調用該 核心模塊服務 的模塊 將會出現問題,
相似於 病毒感染,一個模塊出現問題,將逐步感染其餘模塊出現問題,最終致使系統崩潰(也即服務雪崩)。
【服務雪崩:】
服務雪崩 指的是 服務提供者 不可用(不能提供服務) 而致使 服務消費者不可用,並逐級放大的過程。
好比:
多個微服務之間造成鏈式調用,A、B 調用 C,C 調用 D,D 調用其餘服務等。。。
若是 D 因某種緣由(宕機、網絡延遲等) 不能對外提供服務了,將致使 C 訪問出現問題,而 C 出現問題,將可能致使 A、B 出現問題,也即 問題逐級放大(最終可能引發系統崩潰)。
【解決:】
服務降級、服務熔斷 是解決 服務雪崩的 經常使用手段。
相關技術:
Hystrix(維護狀態,不推薦使用)
Sentienl(推薦使用)
(1) 服務降級git
【服務降級:】
服務降級 指的是 當服務器壓力 劇增 時,根據當前 業務、流量 狀況 對一些服務(通常爲非核心業務)進行有策略的降級,確保核心業務正常執行。
即 釋放非核心服務 佔用的服務器資源 確保 核心任務正常執行。
注:
能夠理解爲 損失一部分業務能力,保證系統總體正常運行,從而防止 服務雪崩。
資源是有限的,請求併發高時,若不對服務進行降級處理,系統可能花費大量資源進行非核心業務處理,致使 核心業務 效率下降,進而影響總體服務性能。
此處的降級能夠理解爲 不提供服務 或者 延時提供服務(服務執行暫時不正常,給一個默認的返回結果,等一段時間後,正常提供服務)。
【服務降級分類:】
手動降級:
能夠經過修改配置中心配置,並根據事先定義好的邏輯,執行降級邏輯。
自動降級:
超時降級:設置超時時間、超時重試次數,請求超時則服務降級,並使用異步機制檢測 進行 服務恢復。
失敗次數降級:當請求失敗達到必定次數則服務降級,一樣使用異步機制檢測 進行服務恢復。
故障降級:服務宕機了則服務降級。
限流降級:請求訪問量過大則服務降級。
(2)服務熔斷github
【服務熔斷:】 服務熔斷 指的是 目標服務不可用 或者 請求響應超時時,爲了保證總體服務可用, 再也不調用目標服務,而是直接返回默認處理(釋放系統資源),經過某種算法檢測到目標服務可用後,則恢復其調用。 注: 在必定時間內,服務調用失敗次數達到必定比例,則認爲 當前服務不可用。 服務熔斷 能夠理解爲 特殊的 服務降級(即 服務不可用 --> 服務降級 --> 服務調用恢復)。 【martinfowler 相關博客地址:】 https://martinfowler.com/bliki/CircuitBreaker.html
(3)服務降級 和 服務熔斷 的區別web
【相同點:】
目標相同:均從 可靠性、可用性 觸發,避免系統崩潰(服務雪崩)。
效果相同:均屬於 某功能暫不可用。
【不一樣點:】
服務降級 通常 是從總體考慮,能夠手動關閉 非核心業務,確保 核心業務正常執行。
服務熔斷 通常 是某個服務不可用,自動關閉 服務調用,並在必定時間內 從新嘗試 恢復該服務調用。
注(我的理解(僅供參考)):
服務降級 能夠做爲 預防措施(手動降級),即 服務並無出錯,可是爲了提高系統效率,我主動放棄 一部分非核心業務,保證系統資源足夠用於 執行 核心業務。
服務熔斷 就是 服務出錯的 解決方案(自動降級),即 服務出錯後 的一系列處理。
【Hystrix:】 Hystrix 是一個用於處理分佈式系統 延遲 和 容錯的 開源庫, 目的是 隔離遠程系統、服務和第三方庫的訪問點,中止級聯故障,並在不可避免發生故障的複雜分佈式系統中實現恢復能力。 注: 分佈式系統不免出現 阻塞、超時、異常 等問題,Hystrix 能夠保證在一個服務出問題時,不影響整個系統使用(避免服務雪崩),提升系統的可用性。 雖然 Hystrix 已進入維護模式,再也不更新,但仍是能夠學習一下思想、基本使用。 【經常使用特性:】 服務降級 服務熔斷 服務監控 【相關地址:】 https://github.com/Netflix/Hystrix
(1)什麼是 JMeter ?算法
【JMeter】 Apache 的一款基於 Java 的壓力測試工具。 注: 有興趣的自行研究,此處不過多贅述。 【官網下載地址:】 http://jmeter.apache.org/download_jmeter.cgi 【JMeter 簡單使用:】 https://www.cnblogs.com/stulzq/p/8971531.html
(2)說明spring
【說明:】 此處僅簡單演示,不須要啓動集羣(單機版 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} 能夠瞬間返回結果。
注:
看頁面的刷新按鈕。express
併發度高時:
使用 JMeter 模擬 200 個線程,循環 100 次,訪問 /consumer/user/testTimeout。
此時再訪問 /consumer/user/get/{id} 時,不能瞬間返回結果(等待一段時間)。apache
(5)超時故障
前面已經演示了高併發狀況下可能出現超時等待狀況,而若 業務執行時間過長 或者 服務調用設置了超時時間,那麼當訪問被阻塞時,將有可能引發故障。
【在聲明 RestTemplate 時,定義超時時間】 @Bean @LoadBalanced // 使用 @LoadBalanced 註解賦予 RestTemplate 負載均衡的能力 public RestTemplate getRestTemplate() { SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory(); httpRequestFactory.setConnectTimeout(2000); httpRequestFactory.setReadTimeout(2000); return new RestTemplate(httpRequestFactory); }
(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="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(); } }
(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:
簡單測試一下。
當服務提供者 宕機時,此時服務調用失敗,將會執行 實現類中的邏輯。
而服務提供者正常提供服務時,因爲上面已經在 服務提供者 處配置了 服務降級,則執行 服務提供者的服務降級策略。
(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 = "true"), // 在必定時間內,請求總數達到了閾值,纔有資格進行熔斷。默認 20 個請求。 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 熔斷以後,從新嘗試恢復服務調用的時間,在此期間,會執行 fallbackMethod 定義的邏輯。默認 5 秒。 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 出錯閾值,請求總數超過了閾值,而且調用失敗率達到必定比率,會熔斷。默認 50%。 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60") }) public Result testCircuitBreaker(@PathVariable Integer id) { if (id < 0) { throw new RuntimeException("ID 不能爲負數"); } return Result.ok().message("調用成功, ID = " + id); }
(3)直接訪問該服務測試一下(使用 JMeter 測試亦可)。
以下圖所示,當請求失敗達到必定比率,將會開啓斷路器。
一段時間後,嘗試恢復服務調用,關閉斷路器。
(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 註解的服務時,將會觸發監控。
(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"
(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 注: 詳情請自行查閱官網文檔。
(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 中。
(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 【實際場景舉例:】 一般用於處理 間隔性請求。 好比: 消息隊列,某瞬間的請求不少,但以後卻沒有請求,此時可使用排隊等待,將請求延遲執行(而不是直接拒絕)。
以下圖所示,快速刷新頁面時,請求將會排隊等待執行,超時後將會報錯。
(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 回調函數參數 與 原方法相同,能夠調用成功。
(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、 併發線程數 等。
(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() 將失敗,從而執行本地添加的邏輯。
後續使用到 Nacos 再介紹,此處暫時省略。。。