松花皮蛋的黑板報
  • 分享在京東工作的技術感悟,還有JAVA技術和業內最佳實踐,大部分都是務實的、能看懂的、可復現的

掃一掃
關注公眾號

微服務架構之容錯Hystrix

博客首頁文章列表 松花皮蛋me 2019-04-26 21:55

一、容錯的必要性

假設單體應用可用率為99.99%,即使拆分后每個微服務的可用率還是保持在99.99%,總體的可用率還是下降的。因為凡是依賴都可能會失敗,凡是資源都是有限制的,另外網絡并不可靠。有可能一個很不起眼的微服務模塊高延遲最后導致整體服務不可用

二、容錯的基本模塊

  1. 1、主動超時,一般設置成2秒或者5秒超時時間
  2. 2、服務降級,一般會降級成直接跳轉到靜態CDN托底頁或者提示活動太火爆,以免開天窗
  3. 3、限流,一般使用令牌機制限制最大并發數
  4. 4、隔離,對不同依賴進行隔離,容器CPU綁核就是一種隔離措施
  5. 5、彈性熔斷,錯誤數達到一定閥值后,開始拒絕請求,健康檢查發現恢復后再次接受請求

三、Hystrix主要概念

Hystrix流程

想要使用Hystrix,只需要繼承HystrixCommand或者HystrixObservableCommand并重寫業務邏輯方法即可,區別在于HystrixCommand.run()返回一個結果或者異常,HystrixObservableCommand.construct()返回一個Observable對象

編者按:關于反應式編程可參考文章Flux反應式編程結合多線程實現任務編排

Hystrix真正執行命令邏輯是通過execute()、queue()、observe()、toObservable()的其中一種,區別在于execute是同步阻塞的,queue通過myObservable.toList().toBlocking().toFuture()實現異步非阻塞,observe是事件注冊前執行,toObservable是事件注冊后執行,后兩者是基于發布和訂閱響應式的調用

每個熔斷器默認維護10個bucket,每秒一個bucket,每個bucket記錄成功,失敗,超時,拒絕的狀態,默認錯誤超過50%且10秒內超過20個請求才進行中斷攔截。當斷路器打開時,維護一個窗口,每過一個窗口時間,會放過一個請求以探測后端服務健康狀態,如果已經恢復則斷路器會恢復到關閉狀態

當斷路器打開、線程池提交任務被拒絕、信號量獲得被拒絕、執行異常、執行超時任一情況發生都會觸發降級fallBack,Hystrix提供兩種fallBack方式,HystrixCommand.getFallback()和HystrixObservableCommand.resumeWithFallback()

四、線程和信號量隔離

1、線程隔離,針對不同的服務依賴創建線程池
2、信號量隔離,本質是一個共享鎖。當信號量中有可用的許可時,線程能獲取該許可(seaphore.acquire()),否則線程必須等待,直到有可用的許可為止。線程用完必須釋放(seaphore.release())否則其他線程永久等待

類型優點不足適用
線程支持排隊和超時、支持異步調用線程調用和切換產生額外開銷不受信客戶(比如第三方服務穩定性是無法推測的)
信號量輕量且無額外開銷不支持任務排隊和超時,不支持異步受信客戶、高頻高速調用服務(網關、cache)

五、Hystrix主要配置項

配置項(前綴hystrix.command.*.)含義
execution.isolation.strategy線程“THREAD”或信號量“SEMAPHORE”隔離(Default: THREAD)
execution.isolation.thread.timeoutInMillisecondsrun()方法執行超時時間(Default: 1000)
execution.isolation.semaphore.maxConcurrentRequests信號量隔離最大并發數(Default:10)
circuitBreaker.errorThresholdPercentage熔斷的錯誤百分比閥值(Default:50)
circuitBreaker.requestVolumeThreshold斷路器生效必須滿足的流量閥值(Default:20)
circuitBreaker.sleepWindowInMilliseconds熔斷后重置斷路器的時間間隔(Default:5000)
circuitBreaker.forceOpen設true表示強制熔斷器進入打開狀態(Default: false)
circuitBreaker.forceClosed設true表示強制熔斷器進入關閉狀態(Default: false)
配置項(前綴hystrix.threadpool.*.)含義
coreSize使用線程池時的最大并發請求(Default: 10)
maxQueueSize最大LinkedBlockingQueue大小,-1表示用SynchronousQueue(Default:-1)
default.queueSizeRejectionThreshold隊列大小閥值,超過則拒絕(Default:5)

六、使用

1、請求上下文,下面將要提到的請求緩存、請求合并都依賴請求上下文,我們可以在攔截器中進行管理

public class HystrixRequestContextServletFilter implements Filter {

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
     throws IOException, ServletException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            chain.doFilter(request, response);
        } finally {
            context.shutdown();
        }
    }
}

2、請求緩存,減少相同參數請求后端服務的開銷,需要重寫getCacheKey方法返回緩存key

public class CommandUsingRequestCache extends HystrixCommand<Boolean> {

    private final int value;

    protected CommandUsingRequestCache(int value) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.value = value;
    }

    @Override
    protected Boolean run() {
        return value == 0 || value % 2 == 0;
    }

    @Override
    protected String getCacheKey() {
        return String.valueOf(value);
    }
}

3、請求合并。請求合并在Nginx靜態資源加載中也很常見,Nginx使用的是nginx-http-concat擴展模塊。但是在Hystric中請求合并會導致延遲增加,所以要求兩者啟動執行間隔時長足夠小,減少等待合并的時間,超過10ms間隔不會自動合并

public class CommandCollapserGetValueForKey extends HystrixCollapser<List<String>, String, Integer> {

    private final Integer key;

    public CommandCollapserGetValueForKey(Integer key) {
        this.key = key;
    }

    @Override
    public Integer getRequestArgument() {
        return key;
    }

    @Override
    protected HystrixCommand<List<String>> createCommand(final Collection<CollapsedRequest<String, Integer>> requests) {
        return new BatchCommand(requests);
    }

    @Override
    protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, Integer>> requests) {
        int count = 0;
        for (CollapsedRequest<String, Integer> request : requests) {
            request.setResponse(batchResponse.get(count++));
        }
    }

    private static final class BatchCommand extends HystrixCommand<List<String>> {
        private final Collection<CollapsedRequest<String, Integer>> requests;

        private BatchCommand(Collection<CollapsedRequest<String, Integer>> requests) {
                super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey")));
            this.requests = requests;
        }

        @Override
        protected List<String> run() {
            ArrayList<String> response = new ArrayList<String>();
            for (CollapsedRequest<String, Integer> request : requests) {
                // artificial response for each argument received in the batch
                response.add("ValueForKey: " + request.getArgument());
            }
            return response;
        }
    }
}

4、快速失敗,不走降級邏輯,直接拋出異常,通常用于非冪等性的寫操作。冪等性是指一次和多次請求某一個資源應該具有同樣的副作用,比如bool take(ticket_id, account_id, amount)取錢操作,不管任何時候請求失敗或超時,調用方都可以重試,當然把參數ticket_id去掉就是非冪等性的了。注意:在Hystrix可以輕松實現重試,只需降級時判斷isCircuitBreakerOpen斷路器狀態可用然后重試即可,不會使問題雪上加霜

public class CommandThatFailsFast extends HystrixCommand<String> {

private final boolean throwException;

public CommandThatFailsFast(boolean throwException) {
    super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
    this.throwException = throwException;
}

@Override
protected String run() {
    if (throwException) {
        throw new RuntimeException("failure from CommandThatFailsFast");
    } else {
        return "success";
    }
}
黑龙江6+1开奖结果查询