微服務之三:Hystrix(1)

一: 概述

很多系統中應考慮橫向擴展,單點故障問題。在出現故障時,爲了較少故障的影響,保障集羣的高可用,應增加保護機制,Hystrix就是其中的一種保護機制

  • Hystrix通過添加延遲閾值以及容錯的邏輯,來幫助我們控制分佈式系統間組件的交互。
  • Hystrix通過隔離服務間的訪問點、停止他們之間的級聯故障,提供可回退操作來實現容錯。

Hystrix的功能:

  • 當所依賴的網絡服務發生延遲或者失敗時,對訪問的客戶端程序進行保護。
  • 在分佈式系統中,停止級聯故障
  • 網絡服務恢復正常時,可以快速恢復客戶端的訪問能力
  • 調用失敗時執行服務回退
  • 可支持實時監控、報警、和其他操作

1.1 Hystrix程序

1.2 Hystrix的使用

內部流程
從之前的例子看,當服務器出現無響應現象的時候,Hystrix會自動使用容錯機制,看似簡單,其實有一套較爲複雜的執行邏輯

  • 在命令開始執行時,會做一些準備工作(如:爲命令創建相應的線程池等)
  • 判斷是否打開了緩存,打開了緩存就直接查找緩存並返回結果
  • 判斷斷路器是否打開,如果打開了,就表示鏈路不可以用,直接執行回退方法
  • 判斷線程池、信號量等條件(如:線程池超負荷,則返回回退方法,否則,就去執行命令的內容)
  • 執行命令,判斷是否要對斷路器進行處理,執行完成後,如果滿足一定條件,則需要開啓斷路器
    整個流程最主要的點,就是在於斷路器是否被打開

1.3 命令執行

  • toObservable:返回一個最原始的可觀察的實例,是一個RxJava的類,可觀察命令的執行過程,並且將執行的信息轉遞給訂閱者(命令不會馬上執行,只有當返回的可觀察實例被訂閱之後,纔會真正執行)
  • observe: 用toObservable獲得最原始的可觀察實例後,再使用一個replay subject作爲原始toObservable的訂閱者(會立刻執行
  • queue
  • execute

1.4 配置屬性

1.5 回退

列舉三種情況下觸發回退

  • 斷路器被打開
  • 線程池、隊列、信號量滿載
  • 實際執行命令失敗
package com.atm.cloud.config;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;

public class MyTimeOutConfig extends HystrixCommand<String> {

    public MyTimeOutConfig() {
        // 第二種方法:
        // super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));

        // 超時時間設置成2s
        super(Setter.withGroupKey(
                HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter()
                                .withExecutionTimeoutInMilliseconds(2000)));

        // 第一種方法:設置超時時間(設置成2s)
        HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(
                20000);
    }

    @Override
    protected String run() throws Exception {
        // 手動延遲3s
        // Thread.sleep(3000);
        System.out.println("執行命令...");
        return "Success";
    }

    // 回退方法
    @Override
    protected String getFallback() {
        System.out.println("執行回退...");
        // return super.getFallback();
        return "Fallback...";
    }

}

如果斷路器打開,則會執行回退方法,
如果斷路器打開但沒有提供回退方法,系統則會報異常

1.6 回退的模式

Hystrix回退機制比較靈活,
你可以在A命令的回退方法中執行B命令,如果B命令執行也失敗,同意也可以觸發B命令的回退

1.7 斷路器開啓

斷路器一旦開啓,就會直接

  • 調用回退方法,
  • 不再執行命令,
  • 也不會更新鏈路的健康狀況

斷路器開啓要滿足兩個條件

  • 整個鏈路達到一定閾值,默認情況下,10秒內產生超過20次請求,則符合第一個條件
  • 滿足第一個條件下,如果請求的錯誤百分比大於閾值,則會打開斷路器,默認爲50%
package com.atm.cloud.open;

import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts;
import com.netflix.hystrix.HystrixCommandProperties;

public class CloseMain {

    public static void main(String[] args) throws Exception {
        // 10秒內大於3次請求,滿足第一個條件
        ConfigurationManager
                .getConfigInstance()
                .setProperty(
                        "hystrix.command.default.circuitBreaker.requestVolumeThreshold",
                        3);
        boolean isTimeout = true;
        for (int i = 0; i < 10; i++) {// 發送10次請求,一開始均爲失敗,則會打開斷路器
            TestCommand c = new TestCommand(isTimeout);// 總會超時
            c.execute();
            // 輸出健康信息
            HealthCounts hc = c.getMetrics().getHealthCounts();
            System.out.println("斷路器狀態:" + c.isCircuitBreakerOpen() + ", 請求數量:"
                    + hc.getTotalRequests());
            if (c.isCircuitBreakerOpen()) {// 如果斷路器打開
                isTimeout = false;//
                System.out.println("============  斷路器打開了,等待休眠期結束");
                Thread.sleep(6000);// 等待休眠期結束,6s之後嘗試性發一次請求
            }
        }
    }

    static class TestCommand extends HystrixCommand<String> {

        private boolean isTimeout;

        public TestCommand(boolean isTimeout) {
            super(Setter.withGroupKey(
                    HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                    .andCommandPropertiesDefaults(
                            HystrixCommandProperties.Setter()
                                    .withExecutionTimeoutInMilliseconds(500)));// 500毫秒沒有響應,則打開斷路器
            this.isTimeout = isTimeout;
        }

        @Override
        protected String run() throws Exception {
            if (isTimeout) {
                Thread.sleep(800);// 方法執行時間爲800毫秒
            } else {
                Thread.sleep(200);
            }
            return "";
        }

        @Override
        protected String getFallback() {
            return "fallback";
        }
    }
}

1.8 斷路器關閉

  • 當斷路器打開的時候,不再執行命令,直接進行回退方法,這段時間稱爲休眠期,默認時間爲5s
  • 休眠期結束之後,會嘗試性執行一次命令。
    • 此時,斷路器狀態處於半開狀態,嘗試執行成功之後,就會關閉斷路器並且清空健康信息,
    • 如果失敗,斷路器會繼續保持打開的狀態

1.9 隔離機制

命令的真正執行,除了斷路器要關閉外,還需要再過一關

  • 執行命令的線程池或者信號量是否滿載
    • 如果滿載,命令就不會執行,而是直接出發回退

Hystrix提供了兩種隔離策略

  • thread(線程,消耗可能大點,異步超時)
  • semaphore(信號量,不支持超時、異步)

1.9.1 編寫命令

package com.atm.cloud;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

public class MyCommand extends HystrixCommand<String> {

    public int index;

    public MyCommand(int index) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
                .asKey("ExampleGroup")));
        this.index = index;
    }

    @Override
    protected String run() throws Exception {
        Thread.sleep(500);
        System.out.println("run(),當前索引:" + index);
        return "success";
    }

    @Override
    protected String getFallback() {
        System.out.println("getFallback(),當前索引:" + index);
        return "fallback";
    }

}

  • 線程隔離測試,編寫測試類
package com.atm.cloud;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

public class MyCommand extends HystrixCommand<String> {

    public int index;

    public MyCommand(int index) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
                .asKey("ExampleGroup")));
        this.index = index;
    }

    @Override
    protected String run() throws Exception {
        Thread.sleep(500);
        System.out.println("run(),當前索引:" + index);
        return "success";
    }

    @Override
    protected String getFallback() {
        System.out.println("getFallback(),當前索引:" + index);
        return "fallback";
    }

}

  • 信號量隔離測試
package com.atm.cloud;

import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;

public class SemaphoreMain {

    public static void main(String[] args) throws Exception {
        // 信號量策略,默認最大併發數10
        ConfigurationManager.getConfigInstance().setProperty(
                "hystrix.command.default.execution.isolation.strategy",
                ExecutionIsolationStrategy.SEMAPHORE);

        // 設置最大併發數爲2
        ConfigurationManager
                .getConfigInstance()
                .setProperty(
                        "hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests",
                        2);

        for (int i = 0; i < 6; i++) {
            final int index = i;
            Thread t = new Thread() {
                public void run() {
                    MyCommand c = new MyCommand(index);
                    c.execute();
                }
            };
            t.start();
        }

        Thread.sleep(5000);
    }
}

1.10 請求緩存

在一起請求中,多個地方調用同一個接口,可考慮使用緩存
需要用到CommandKey
合併請求,請求緩存,在一次請求的過程中才能實現,因此需要先初始化請求上下文

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