引入Hystrix
分佈式微服務場景下,調用服務調用依賴服務,是通過RPC進行調用的。在高併發訪問下,依賴服務的穩定性與否對調用服務影響非常大,即依賴服務存在着很多不可控的問題,如:網絡延遲、服務繁忙、服務阻賽、服務不可用等。
User-request服務依賴I服務出現不可用,但是其他依賴仍然可用。當依賴I服務 阻塞時,大多數服務器的線程池就出現阻塞(BLOCK),影響整個線上服務的穩定性。
1、Hystrix簡介
Hystrix:通過服務熔斷(也可以稱爲斷路)、降級、限流(隔離)、異步RPC等手段控制依賴服務的延遲與失敗。
Circuit Breaker :熔斷器
- 熔斷只是作用在服務調用這一端,只需改consumer端。
- 1)熔斷器開關相互轉換的邏輯
- : a.服務的健康狀況 = 請求失敗數 / 請求總數.
- : b.熔斷器開關由關閉到打開的狀態轉換是通過當前服務健康狀況和設定閾值比較決定的
- : : b1.關閉時, 請求被允許通過熔斷器. 如果當前健康狀況高於設定閾值, 開關繼續保持關閉. 如果當前健康狀況低於設定閾 值, 開關則切換爲打開狀態
- : : b2.打開狀態, 經過一段時間後, 熔斷器會自動進入半開狀態, 這時熔斷器只允許一個請求通過. 當該請求調用成功時, 熔斷器恢復到關閉狀態. 若該請求失敗, 熔斷器繼續保持打開狀態, 接下來的請求被禁止通過
- : c.保證服務調用者在調用異常服務時, 快速返回結果, 避免大量的同步等待
- : d.在一段時間後繼續偵測請求執行結果, 提供恢復服務調用的可能
- 2)參數設值
- : a.circuitBreaker.requestVolumeThreshold //滑動窗口的大小,默認爲20
- : b.circuitBreaker.sleepWindowInMilliseconds //過多長時間,熔斷器再次檢測是否開啓,默認爲5000,即5s鍾
- : c.circuitBreaker.errorThresholdPercentage //錯誤率,默認50%每當20個請求中,有50%失敗時,熔斷器就會打開,此時再調用此服務,將會直接返回失敗,不再調遠程服務。直到5s鍾之後,重新檢測該觸發條件,判斷是否把熔斷器關閉,或者繼續打開。
降級:fallback
當某個服務熔斷之後,服務器將不再被調用,此時客戶端可以自己準備一個本地的fallback回調,返回一個缺省值
Isolation:限流(隔離)
線程隔離:
即將每個依賴服務分配獨立的線程池進行資源隔離, 從而避免服務雪崩
線上建議線程池不要設置過大,否則大量堵塞線程有可能會拖慢服務器
信號隔離:
用於限制併發訪問,防止阻塞擴散, 與線程隔離最大不同在於執行依賴代碼的線程依然是請求線程(該線程需要通過信號申請)
如果客戶端是可信的且可以快速返回,可以使用信號隔離替換線程隔離,降低開銷。
2、Hystrix執行流程
- ①、每次調用創建一個新的HystrixCommand,把依賴調用封裝在run()方法中.
- ②、執行execute()/queue做同步或異步調用.
- ③、判斷熔斷器(circuit-breaker)是否打開,如果打開跳到步驟8,進行降級策略,如果關閉進入步驟.
- ④、判斷線程池/隊列/信號量是否跑滿,如果跑滿進入降級步驟8,否則繼續後續步驟.
- ⑤、調用HystrixCommand的run方法.運行依賴邏輯
- : 5a:依賴邏輯調用超時,進入步驟8.
- ⑥:判斷邏輯是否調用成功
- : 6a:返回成功調用結果
:: 6b:調用出錯,進入步驟8. - ⑦:計算熔斷器狀態,所有的運行狀態(成功, 失敗, 拒絕,超時)上報給熔斷器,用於統計從而判斷熔斷器狀態.
- ⑧:getFallback()降級邏輯.
- : 以下四種情況將觸發getFallback調用:
- : : : (1):run()方法拋出非HystrixBadRequestException異常。
- : : : (2):run()方法調用超時
- : : : (3):熔斷器開啓攔截調用
- : : : (4):線程池/隊列/信號量是否跑滿
- : 8a:沒有實現getFallback的Command將直接拋出異常
- : 8b:fallback降級邏輯調用成功直接返回
- : 8c:降級邏輯調用失敗拋出異常
- ⑨:返回執行成功結果
3、Hystrix參數介紹
超時時間(默認1000ms,單位:ms)
(1)hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
在調用方配置,被該調用方的所有方法的超時時間都是該值,優先級低於下邊的指定配置
(2)hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds
在調用方配置,被該調用方的指定方法(HystrixCommandKey方法名)的超時時間是該值
線程池核心線程數
hystrix.threadpool.default.coreSize(默認爲10)
Queue
(1)hystrix.threadpool.default.maxQueueSize(最大排隊長度。默認-1,使用SynchronousQueue。其他值則使用 LinkedBlockingQueue。如果要從-1換成其他值則需重啓,即該值不能動態調整,若要動態調整,需要使用到下邊這個配置)
(2)hystrix.threadpool.default.queueSizeRejectionThreshold(排隊線程數量閾值,默認爲5,達到時拒絕,如果配置了該選項,隊列的大小是該隊列)
注意:如果maxQueueSize=-1的話,則該選項不起作用
斷路器
(1)hystrix.command.default.circuitBreaker.requestVolumeThreshold(當在配置時間窗口內達到此數量的失敗後,進行短路。默認20個)
For example, if the value is 20, then if only 19 requests are received in the rolling window (say a window of 10 seconds) the circuit will not trip open even if all 19 failed.
簡言之,10s內請求失敗數量達到20個,斷路器開。
(2)hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds(短路多久以後開始嘗試是否恢復,默認5s)
(3)hystrix.command.default.circuitBreaker.errorThresholdPercentage(出錯百分比閾值,當達到此閾值後,開始短路。默認50%)
fallback
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests
(調用線程允許請求HystrixCommand.GetFallback()的最大數量,默認10。超出時將會有異常拋出。注意:該項配置對於THREAD隔離模式也起作用)
整合Hystrix
1、技術框架
項目框架:Spring boot
分佈式協調:Dubbo
中間件:Zookeeper
日誌工具:Sf4j
構建工具:Maven
開發工具:IDEA
2、環境準備
2.1 添加依賴
<!--hystrix -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>${hystrix-version}</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-metrics-event-stream</artifactId>
<version>${hystrix-version}</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>${hystrix-version}</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-servo-metrics-publisher</artifactId>
<version>${hystrix-version}</version>
</dependency>
hystrix-version:1.5.18,添加需要的版本。
2.2 Hystrix配置
Hystrix配置在服務調用者
@Configuration
public class HystrixConfig {
//設置Aspect
@Bean
public HystrixCommandAspect hystrixCommandAspect(){
return new HystrixCommandAspect();
}
//注入servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
return new ServletRegistrationBean(new HystrixMetricsStreamServlet(),"/hystrix.stream");
}
}
hystrixCommandAspect()配置hystrixCommandAspect爲Spring 對象,hystrixMetricsStreamServlet()配置hystrix-dashboard的監控。
2.3 Hystrix代碼實現
在調用依賴服務的方法上加上@HystrixCommand註解,並配置相關參數。
@Service
public class ComsumerServiceImpl implements ConsumerService {
private static final Logger LOGGER = LoggerFactory.getLogger(ComsumerServiceImpl.class);
@Autowired
private OrderService orderService;
private AtomicInteger successCount = new AtomicInteger(0);
private AtomicInteger failCount = new AtomicInteger(0);
@Override
@HystrixCommand(groupKey="BestGroup", commandKey = "BestCommand",threadPoolKey = "best", fallbackMethod = "getNameFallback",
commandProperties = {
//@HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE"),//指定隔離策略爲信號量SEMAPHORE,默認THREAD
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),//指定多久超時,單位毫秒。超時進fallback
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//一個統計窗口內熔斷觸發的最小個數/10s,默認是20
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000"),//熔斷多少秒後去嘗試請求,默認是5000ms
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "10"),//失敗率達到多少百分比後熔斷,默認值50
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "50")//fallback最大併發度,默認10
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "10"),// 設置線程池的core size,這是最大的併發執行數量,默認是10
@HystrixProperty(name = "maxQueueSize", value = "5"),// 最大隊列長度。設置BlockingQueue的最大長度,默認是-1
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "3")// 此屬性設置隊列大小拒絕閾值 - 即使未達到maxQueueSize也將發生拒絕的人爲最大隊列大小。
// 此屬性存在,因爲BlockingQueue的maxQueueSize不能動態更改,我們希望允許您動態更改影響拒絕的隊列大小
// 默認值:5, 注意:如果maxQueueSize == -1,則此屬性不適用
})
public String getName(String id) {
LOGGER.info("consumer->service->thread:{},id:{}", Thread.currentThread().getName(),id);
String result = orderService.getOrder(id);
int i = successCount.incrementAndGet();
LOGGER.info("成功次數:{}" , i);
return result;
}
public String getNameFallback(String id) {
LOGGER.info("consumer->service->fallback->thread:{},id:{}", Thread.currentThread().getName(),id);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int i = failCount.incrementAndGet();
LOGGER.info("失敗次數==={}" , i);
return "回退";
}
}
以下是模擬依賴服務超時的場景:
1、配置服務調用方法的超時時間爲1000毫秒
@HystrixProperty(name = “execution.isolation.thread.timeoutInMilliseconds”, value = “1000”)
注意:此處依賴服務超時和Dubbo超時會取小者,此處dubbo服務的超時設置爲2000毫秒
<dubbo:reference id=“orderService” retries=“0” timeout=“2000” interface=“com.bestpay.service.OrderService” check=“false” filter=“traceFilter”/>
2、配置斷路器打開的閾值爲50%
@HystrixProperty(name = “circuitBreaker.errorThresholdPercentage”, value = “50”)
3、配置服務提供方方法隨機超時,超過1000毫秒
public class OrderServiceImpl implements OrderService {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderServiceImpl.class);
@Override
public String getOrder(String orderid) {
LOGGER.info("thread:" + Thread.currentThread().getName());
try{ boolean flag = new Random().nextBoolean();
if(flag){
Thread.sleep(2000);
}else{
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.info("provider->service->param:{}", orderid);
return orderid + ",OK";
}
}
3、啓動相關服務,使用Jmeter模擬併發,監控Hystrix斷路器的狀態。
錯誤率()達到50%以上的時候,斷路器狀態打開,之後的請求在調用依賴服務的時候被短路,服務降級,對調用服務起到保護的作用,提高調用的可用性。
以上只演示Hystrix斷路器熔斷的場景,關於Hystrix的隔離的情況,可自行研究。
備註:錯誤率計算如下:Timed-out + Threadpool Rejected + Failuer / Total
作者簡介:就職於甜橙金融信息技術部,負責服務端開發,專注於微服務、分佈式、性能調優、高可用,歡迎各位同仁溝通交流。