認識Hystrixjava
Hystrix是Netflix開源的一款容錯框架,包含經常使用的容錯方法:線程隔離、信號量隔離、降級策略、熔斷技術。
在高併發訪問下,系統所依賴的服務的穩定性對系統的影響很是大,依賴有不少不可控的因素,好比網絡鏈接變慢,資源忽然繁忙,暫時不可用,服務脫機等。咱們要構建穩定、可靠的分佈式系統,就必需要有這樣一套容錯方法。
本文主要討論線程隔離技術。編程
爲何要作線程隔離緩存
好比咱們如今有3個業務調用分別是查詢訂單、查詢商品、查詢用戶,且這三個業務請求都是依賴第三方服務-訂單服務、商品服務、用戶服務。三個服務均是經過RPC調用。當查詢訂單服務,假如線程阻塞了,這個時候後續有大量的查詢訂單請求過來,那麼容器中的線程數量則會持續增長直致CPU資源耗盡到100%,整個服務對外不可用,集羣環境下就是雪崩。以下圖tomcat
訂單服務不可用.png性能優化
:網絡
整個tomcat容器不可用.png架構
Hystrix是如何經過線程池實現線程隔離的併發
Hystrix經過命令模式,將每一個類型的業務請求封裝成對應的命令請求,好比查詢訂單->訂單Command,查詢商品->商品Command,查詢用戶->用戶Command。每一個類型的Command對應一個線程池。建立好的線程池是被放入到ConcurrentHashMap中,好比查詢訂單:框架
final static ConcurrentHashMap<String, HystrixThreadPool> threadPools = new ConcurrentHashMap<String, HystrixThreadPool>(); threadPools.put(「hystrix-order」, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));
當第二次查詢訂單請求過來的時候,則能夠直接從Map中獲取該線程池。具體流程以下圖:異步
hystrix線程執行過程和異步化.png
建立線程池中的線程的方法,查看源代碼以下:
public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { ThreadFactory threadFactory = null; if (!PlatformSpecific.isAppEngineStandardEnvironment()) { threadFactory = new ThreadFactory() { protected final AtomicInteger threadNumber = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r, "hystrix-" + threadPoolKey.name() + "-" + threadNumber.incrementAndGet()); thread.setDaemon(true); return thread; } }; } else { threadFactory = PlatformSpecific.getAppEngineThreadFactory(); } final int dynamicCoreSize = corePoolSize.get(); final int dynamicMaximumSize = maximumPoolSize.get(); if (dynamicCoreSize > dynamicMaximumSize) { logger.error("Hystrix ThreadPool configuration at startup for : " + threadPoolKey.name() + " is trying to set coreSize = " + dynamicCoreSize + " and maximumSize = " + dynamicMaximumSize + ". Maximum size will be set to " + dynamicCoreSize + ", the coreSize value, since it must be equal to or greater than the coreSize value"); return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime.get(), unit, workQueue, threadFactory); } else { return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime.get(), unit, workQueue, threadFactory); } }
執行Command的方式一共四種,具體區別以下:
execute():以同步堵塞方式執行run()。調用execute()後,hystrix先建立一個新線程運行run(),接着調用程序要在execute()調用處一直堵塞着,直到run()運行完成。
queue():以異步非堵塞方式執行run()。調用queue()就直接返回一個Future對象,同時hystrix建立一個新線程運行run(),調用程序經過Future.get()拿到run()的返回結果,而Future.get()是堵塞執行的。
observe():事件註冊前執行run()/construct()。第一步是事件註冊前,先調用observe()自動觸發執行run()/construct()(若是繼承的是HystrixCommand,hystrix將建立新線程非堵塞執行run();若是繼承的是HystrixObservableCommand,將以調用程序線程堵塞執行construct()),第二步是從observe()返回後調用程序調用subscribe()完成事件註冊,若是run()/construct()執行成功則觸發onNext()和onCompleted(),若是執行異常則觸發onError()。
toObservable():事件註冊後執行run()/construct()。第一步是事件註冊前,調用toObservable()就直接返回一個Observable<String>對象,第二步調用subscribe()完成事件註冊後自動觸發執行run()/construct()(若是繼承的是HystrixCommand,hystrix將建立新線程非堵塞執行run(),調用程序沒必要等待run();若是繼承的是HystrixObservableCommand,將以調用程序線程堵塞執行construct(),調用程序等待construct()執行完才能繼續往下走),若是run()/construct()執行成功則觸發onNext()和onCompleted(),若是執行異常則觸發onError()
注:
execute()和queue()是在HystrixCommand中,observe()和toObservable()是在HystrixObservableCommand 中。從底層實現來說,HystrixCommand其實也是利用Observable實現的(看Hystrix源碼,能夠發現裏面大量使用了RxJava),儘管它只返回單個結果。HystrixCommand的queue方法其實是調用了toObservable().toBlocking().toFuture(),而execute方法其實是調用了queue().get()。
如何應用到實際代碼中
package myHystrix.threadpool; import com.netflix.hystrix.*; import org.junit.Test; import java.util.List; import java.util.concurrent.Future; /** * Created by wangxindong on 2017/8/4. */ public class GetOrderCommand extends HystrixCommand<List> { OrderService orderService; public GetOrderCommand(String name){ super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup")) .andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(name)) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter() .withExecutionTimeoutInMilliseconds(5000) ) .andThreadPoolPropertiesDefaults( HystrixThreadPoolProperties.Setter() .withMaxQueueSize(10) //配置隊列大小 .withCoreSize(2) // 配置線程池裏的線程數 ) ); } @Override protected List run() throws Exception { return orderService.getOrderList(); } public static class UnitTest { @Test public void testGetOrder(){ // new GetOrderCommand("hystrix-order").execute(); Future<List> future =new GetOrderCommand("hystrix-order").queue(); } } }
總結
執行依賴代碼的線程與請求線程(好比Tomcat線程)分離,請求線程能夠自由控制離開的時間,這也是咱們一般說的異步編程,Hystrix是結合RxJava來實現的異步編程。經過設置線程池大小來控制併發訪問量,當線程飽和的時候能夠拒絕服務,防止依賴問題擴散。
線程隔離.png
線程隔離的優勢:
[1]:應用程序會被徹底保護起來,即便依賴的一個服務的線程池滿了,也不會影響到應用程序的其餘部分。
[2]:咱們給應用程序引入一個新的風險較低的客戶端lib的時候,若是發生問題,也是在本lib中,並不會影響到其餘內容,所以咱們能夠大膽的引入新lib庫。
[3]:當依賴的一個失敗的服務恢復正常時,應用程序會當即恢復正常的性能。
[4]:若是咱們的應用程序一些參數配置錯誤了,線程池的運行情況將會很快顯示出來,好比延遲、超時、拒絕等。同時能夠經過動態屬性實時執行來處理糾正錯誤的參數配置。
[5]:若是服務的性能有變化,從而須要調整,好比增長或者減小超時時間,更改重試次數,就能夠經過線程池指標動態屬性修改,並且不會影響到其餘調用請求。
[6]:除了隔離優點外,hystrix擁有專門的線程池可提供內置的併發功能,使得能夠在同步調用之上構建異步的外觀模式,這樣就能夠很方便的作異步編程(Hystrix引入了Rxjava異步框架)。
線程隔離的缺點:
[1]:線程池的主要缺點就是它增長了計算的開銷,每一個業務請求(被包裝成命令)在執行的時候,會涉及到請求排隊,調度和上下文切換。不過Netflix公司內部認爲線程隔離開銷足夠小,不會產生重大的成本或性能的影響。
The Netflix API processes 10+ billion Hystrix Command executions per day using thread isolation. Each API instance has 40+ thread-pools with 5–20 threads in each (most are set to 10).
Netflix API天天使用線程隔離處理10億次Hystrix Command執行。 每一個API實例都有40多個線程池,每一個線程池中有5-20個線程(大多數設置爲10個)。
對於不依賴網絡訪問的服務,好比只依賴內存緩存這種狀況下,就不適合用線程池隔離技術,而是採用信號量隔離,後面文章會介紹。
所以咱們能夠放心使用Hystrix的線程隔離技術,來防止雪崩這種可怕的致命性線上故障。
在此我向你們推薦一個架構學習交流羣。交流學習羣號: 744642380, 裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良