二、Hystrix線程池隔離技術的應用

 

2.1 集成Hystrix線程池依賴POM

pom.xml

<dependency>

    <groupId>com.netflix.hystrix</groupId>

    <artifactId>hystrix-core</artifactId>

    <version>1.5.12</version>

</dependency>

 

2.2 將Http接口調用的邏輯進行封裝Hystrix

hystrix進行資源隔離,其實是提供了一個抽象,叫做command,就是說,你如果要把對某一個依賴服務的所有調用請求,全部隔離在同一份資源池內,對這個依賴服務的所有調用請求,全部走這個資源池內的資源,不會去用其他的資源了,這個就叫做資源隔離.

 

對某一個依賴服務,商品服務,所有的調用請求,全部隔離到一個線程池內,對商品服務的每次調用請求都封裝在一個command裏面,每個command(每次服務調用請求)都是使用線程池內的一個線程去執行的,所以哪怕是對這個依賴服務,商品服務,現在同時發起的調用量已經到了1000了,但是線程池內就10個線程,最多就只會用這10個線程去執行,不會說,對Http的請求,因爲接口調用延遲,將tomcat內部所有的線程資源全部耗盡,不會出現了

 

2.3 使用HystrixCommand封裝Http請求獲取一條數據

/**

 * 資源隔離:command的創建和執行

 * 請求緩存:request cache[getCacheKey][HystrixRequestContextFilter]

 * 優雅降級:fall back[getFallback]

 * 短路器:circuit breaker[circuit breaker短路器的配置]

 * 限流:線程池或者信號量的容量是否已滿[withFallbackIsolationSemaphoreMaxConcurrentRequests][withExecutionTimeoutInMilliseconds][withCoreSize][withMaxQueueSize][withQueueSizeRejectionThreshold]

 * 超時:[withExecutionTimeoutEnabled][withExecutionTimeoutInMilliseconds]

 * 彈性線程調度:[withCoreSize][withMaximumSize][withKeepAliveTimeMinutes]

 * @author mawenbo

 *

 */

public class JointExamWholeAnalysisCommand extends  HystrixCommand<String>{

    public static Logger log = LoggerFactory.getLogger(JointExamWholeAnalysisCommand.class);

 

    private String associatedId;

    private String url;

   

   

    private static final HystrixCommandKey COMMAND_KEY = HystrixCommandKey.Factory.asKey("jointExamWholeAnalysis");

   

    public JointExamWholeAnalysisCommand(String associatedId,String url) {

        super(Setter

                    //CommondGroup:默認情況下,因爲就是通過command group來定義一個線程池的,統計信息,成功次數,[timeout(thread)]超時次數,失敗次數,可以看到某一個服務整體的一些訪問情況

                    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("AssociatedExamAllCourses"))

                    //CommondKey:代表了一類command,一般來說代表了底層的依賴服務的一個接口,,多個command key屬於一個command group,在做統計的時候,會放在一起統計

                    .andCommandKey(COMMAND_KEY)

                    //CommondThreadpool:對於thread pool資源隔離來說,可能是希望能夠拆分的更加一致一些,比如在一個功能模塊內,對不同的請求可以使用不同的thread pool

                    //同一個服務的不同接口,做一個細粒度的資源隔離,每個command key有自己的線程池,每個接口有自己的線程池,去做資源隔離和限流

                    .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("associatedExamAllCoursesThreadPool"))

                    .andCommandPropertiesDefaults(

                            HystrixCommandProperties.Setter()

                            //hystrix資源隔離,兩種技術:線程池的資源隔離;信號量的資源隔離

                            .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)

                            //這個參數設置了HystrixCommand.getFallback()最大允許的併發請求數量,默認值是10,也是通過semaphore信號量的機制去限流

                            //如果超出了這個最大值,那麼直接被reject   ==>fallback.isolation.semaphore.maxConcurrentRequests

                            .withFallbackIsolationSemaphoreMaxConcurrentRequests(10)

                            //控制是否要打開timeout機制,默認是(true),讓一個command執行timeout,然後看是否會調用fallback降級

                            .withExecutionTimeoutEnabled(true)

                            //execution.isolation.thread.timeoutInMilliseconds(默認1s):如果請求放等待隊列中時間太長了,直接就會timeout(fallback),等不到去線程池裏執行了

                            //手動設置timeout時長,一個command運行超出這個時間,就被認爲是timeout,然後將hystrix command標識爲timeout,同時執行fallback降級邏輯

                            .withExecutionTimeoutInMilliseconds(1000)

                            //circuit breaker短路器的配置

                            //(1)circuitBreaker.enabled:控制短路器是否允許工作

                            .withCircuitBreakerEnabled(true)

                            //(2)circuitBreaker.requestVolumeThreshold:經過短路器的流量超過了一定的閾值(默認10s 20個)

                            .withCircuitBreakerRequestVolumeThreshold(20)

                            //(3)circuitBreaker.errorThresholdPercentage:短路器統計到的異常調用的佔比超過了一定的閾值(50)

                            .withCircuitBreakerErrorThresholdPercentage(50)

                            //(4)circuitBreaker.sleepWindowInMilliseconds:短路器,會自動恢復的,half-open,半開狀態,默認5s

                            .withCircuitBreakerSleepWindowInMilliseconds(5000)

                            //(5)circuitBreaker.forceClosed:強迫關閉短路器

                            //.withCircuitBreakerForceClosed(true)

                            //(6)circuitBreaker.forceOpen:直接強迫打開短路器

                            //.withCircuitBreakerForceOpen(true)

                    )

                    .andThreadPoolPropertiesDefaults(

                            HystrixThreadPoolProperties.Setter()

                            //CoreSize:設置線程池的大小,默認是10,如果你不設置另外兩個queue相關的參數,等待隊列是關閉

                            //先進去線程池的是10個請求,5個請求進入等待隊列,線程池裏有空閒,等待隊列中的請求如果還沒有timeout,那麼就進去線程池去執行

                            .withCoreSize(10)

                            //設置線程池的最大大小,只有在設置allowMaximumSizeToDivergeFromCoreSize的時候才能生效

                            .withMaximumSize(15)

                            //允許線程池大小自動動態調整(默認爲false),設置爲true之後,maxSize就生效了,此時如果一開始是coreSize個線程,隨着併發量上來,那麼就會自動獲取新的線程,

                            //但是如果線程在keepAliveTimeMinutes內空閒,就會被自動釋放掉

                            .withAllowMaximumSizeToDivergeFromCoreSize(true)

                            //如果coreSize < maxSize,那麼這個參數就設置了一個線程多長時間空閒之後,就會被釋放掉

                            .withKeepAliveTimeMinutes(1)

                            //設置的是你的等待隊列,緩衝隊列的大小

                            .withMaxQueueSize(5)

                            //QueueSizeRejectionThreshold:控制隊列的最大大小HystrixCommand在提交到線程池之前,其實會先進入一個隊列中,這個隊列滿了之後,纔會reject

                            //如果withMaxQueueSize<withQueueSizeRejectionThreshold,那麼取的是withMaxQueueSize,反之,取得是withQueueSizeRejectionThreshold

                            .withQueueSizeRejectionThreshold(5)

                    )

              );

        this.associatedId = associatedId;

        this.url = url;

    }

 

    @Override

    protected String run() throws Exception {

        log.info(Thread.currentThread().getName()+"is running......");

        Map<String,String> param =Maps.newHashMap();

        param.put("associatedId", associatedId);

        try {

            String result = HttpClientUtils.simpleGetInvoke(url, param);

            return result;

        } catch (Exception e) {

            return null;

        }

    }

    /**

     * HystrixCommand和HystrixObservableCommand都可以指定一個緩存key,

     * 然後hystrix會自動進行緩存,接着在同一個request context內,再次訪問的時候,就會直接取用緩存用請求緩存,

     * 可以避免重複執行網絡請求

     */

    @Override

    protected String getCacheKey() {

        return "joint_examWholeAnalysis_" + associatedId;

    }

   

    /**

     * 降級機制四種情況

     * error(異常) reject(資源池已滿) timeout(超時) circuit breaker(開啓斷路器)

     * HystrixObservableCommand,是實現resumeWithFallback方法

     */

    @Override

    protected String getFallback() {

        return "associatedId="+associatedId+"信息Error";

    }

   

   

    /**

     * 緩存的手動清理

     * @param associatedId

     */

    public static void flushCache(String associatedId) {

        HystrixRequestCache.getInstance(COMMAND_KEY, HystrixConcurrencyStrategyDefault.getInstance()).clear(associatedId);

    }

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2.4 使用HystrixObservableCommand封裝Http請求批量查詢獲取多條數據

//http://blog.csdn.net/liuchuanhong1/article/details/73293318

public class JointExamSingleAnalysisObservableCommand extends HystrixObservableCommand<String> {

 

    public static Logger log = LoggerFactory.getLogger(JointExamSingleAnalysisObservableCommand.class);

 

    private String[] courseIds;

    private String url;

    private String associatedId;

 

    public JointExamSingleAnalysisObservableCommand(String associatedId, String[] courseIds, String url) {

        super(HystrixCommandGroupKey.Factory.asKey("AssociatedExamAllCourses"));

        this.associatedId = associatedId;

        this.courseIds = courseIds;

        this.url = url;

    }

 

    @Override

    protected Observable<String> construct() {

        log.info(Thread.currentThread().getName()+"is running......"); 

        return Observable.create(new Observable.OnSubscribe<String>() {

            @Override

            public void call(Subscriber<? super String> observer) {

                try {

                    if (!observer.isUnsubscribed()) {

                        for (int i = 0; i < courseIds.length; i++) {

                            Map<String, String> param = Maps.newLinkedHashMap();

                            param.put("associatedId", associatedId);

                            param.put("courseId", courseIds[i]);

                            String result = HttpClientUtils.simpleGetInvoke(url, param);

                            log.info("聯合考試 學校對比 獲取成功:" + url + "?associatedId=" + associatedId + "&courseId=" + courseIds[i]);

                            observer.onNext(result);

                        }

                        observer.onCompleted();

                    }

                } catch (Exception e) {

                    observer.onError(e);

                }

 

            }

 

        }).subscribeOn(Schedulers.io());

    }

 

}

 

2.5 線程池兩種command的四種調用方式對比

實現

HystrixCommand

獲取一條數據

protected String run() throws Exception {}

HystrixObservableCommand

獲取多條數據

protected Observable<String> construct() {}

同步

command.execute()

Observable<K> observable = observablecommand.observe();

observable.subscribe(new Observer<String>(){

    @Override

     public void onCompleted() {    }

    @Override

     public void onError(Throwable e) {

       e.printStackTrace();

    }

@Override

     public void onNext(String t) { }

});

observable.toBlocking().toFuture().get()

如果你認爲observable command只會返回一條數據,那麼可以調用上面的模式,去同步執行,返回一條數據

observable.toBlocking().single()同上

異步

Future<String> future =command.queue()

String result = future.get()

 

Observable<K> observable  =observablecommand.toObservable();

observable.subscribe(new Observer<String>(){

    @Override

     public void onCompleted() {    }

    @Override

     public void onError(Throwable e) {

       e.printStackTrace();

    }

@Override

     public void onNext(String t) { }

});

 

同步:new CommandHelloWorld("World").execute(),new ObservableCommandHelloWorld("World").toBlocking().toFuture().get()

如果你認爲observable command只會返回一條數據,那麼可以調用上面的模式,去同步執行,返回一條數據

 

異步:new CommandHelloWorld("World").queue(),new ObservableCommandHelloWorld("World").toBlocking().toFuture()

對command調用queue(),僅僅將command放入線程池的一個等待隊列,就立即返回,拿到一個Future對象,後面可以做一些其他的事情,然後過一段時間對future調用get()方法獲取數據

// observe():hot,已經執行過了

// toObservable(): cold,還沒執行過

 

Observable<String> fWorld = new CommandHelloWorld("World").observe();

assertEquals("Hello World!", fWorld.toBlocking().single());

fWorld.subscribe(new Observer<String>() {

    @Override

    public void onCompleted() {

    }

    @Override

    public void onError(Throwable e) {

        e.printStackTrace();

    }

    @Override

    public void onNext(String v) {

        System.out.println("onNext: " + v);

    }

});

 

Observable<String> fWorld = new ObservableCommandHelloWorld("World").toObservable();

assertEquals("Hello World!", fWorld.toBlocking().single());

fWorld.subscribe(new Observer<String>() {

    @Override

    public void onCompleted() {

    }

    @Override

    public void onError(Throwable e) {

        e.printStackTrace();

    }

    @Override

    public void onNext(String v) {

        System.out.println("onNext: " + v);

    }

});

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章