設計模式--狀態模式(分佈式中間件熔斷器Java實現)

 最近在作分佈式服務熔斷,由於要實現一個熔斷器狀態機,因此想到狀態模式。狀態模式是當一個對象的內在狀態改變時容許改變其行爲,這個對象看起來像是改變了其類。狀態模式主要解決的是當控制一個對象狀態的條件表達式過於複雜時的狀況。把狀態的判斷邏輯轉移到表示不一樣狀態的一系列類中,能夠把複雜的判斷邏輯簡化。html

 

先舉個簡單的例子,以紅綠燈模型,說明狀態模式是怎麼一回事:java

一般狀況下咱們是這樣實現的:併發

public class TrafficLight {
    private static enum State {
        RED, GREEN, YELLOW
    }

    private static State state = State.RED;

    public static void change() {
        switch (state) {
            case RED:
                System.out.println("--------\n紅燈(5s)");
                sleep(5000);
                state = State.GREEN;
                break;
            case GREEN:
                System.out.println("綠燈(5s)");
                sleep(5000);
                state = State.YELLOW;
                break;
            case YELLOW:
                System.out.println("黃燈(2s)");
                sleep(2000);
                state = State.RED;
        }
    }

    private static void sleep(int second) {
        try {
            Thread.sleep(second);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        while (true) {
            TrafficLight.change();
        }
    }
}

輸出:分佈式

以上這種寫法,當增長一種燈控邏輯的時候就須要再添加一個分支,致使咱們須要不斷的去修改代碼邏輯,下面使用狀態模式實現:ide

定義狀態接口測試

public interface LightState {
    void showLight(LightManager manager, LightState nextState);
}

咱們但願顯示燈亮的時候程序休眠幾秒,但咱們不但願在LightState的每一個實現類裏面去定義一個sleep方法,因此咱們定義一個抽象類Light來實現上面的接口ui

public abstract class Light implements LightState {

    protected void sleep(int second) {
        try {
            Thread.sleep(second);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

而後下面分別寫LightState接口的三個實現類this

public class RedLight extends Light {

    @Override
    public void showLight(LightManager manager, LightState nextState) {
        System.out.println("---------\n紅燈!(5s)");
        sleep(5000);
        manager.setLightState(nextState);
    }
}
public class GreenLight extends Light {

    @Override
    public void showLight(LightManager manager, LightState nextState) {
        System.out.println("綠燈!(5s)");
        sleep(5000);
        manager.setLightState(nextState);
    }
}
public class YellowLight extends Light {

    @Override
    public void showLight(LightManager manager, LightState nextState) {
        System.out.println("黃燈!(2s)");
        sleep(2000);
        manager.setLightState(nextState);
    }
}

接下來咱們要實現一個燈控的管理類spa

public class LightManager {

    private LightState lightState;

    public LightManager(LightState lightState) {
        this.lightState = lightState;
    }

    public void setLightState(LightState lightState) {
        this.lightState = lightState;
    }

    public void changeLight(LightState nextState) {
        lightState.showLight(this, nextState);
    }
}

測試類3d

public class Test {
    public static void main(String[] args) {

        LightState[] states = {new RedLight(), new GreenLight(), new YellowLight()};

        int index = 0;
        LightManager manager = new LightManager(states[index++]);
        while (true) {
            manager.changeLight(states[index++]);
            if (index == states.length)
                index = 0;
        }
    }
}

輸出結果:

這樣若是後面咱們須要新增一個燈控顏色的好比藍色的話,只須要實現LightState的實現類接口,不用修改到代碼邏輯。

 

----------------------------------------------熔斷器實現------------------------------------------------

 

接下來用狀態模式實現下熔斷器

整個狀態機的邏輯大體以下:

 

 

  服務熔斷主要是參考電路熔斷,若是一條線路電壓太高,保險絲會熔斷,防止火災。放到咱們的系統中,若是某個目標服務調用慢或者有大量超時,此時,熔斷該服務的調用,對於後續調用請求,不在繼續調用目標服務,直接返回,快速釋放資源。若是目標服務狀況好轉則恢復調用。熔斷器可使用狀態機來實現,內部模擬如下幾種狀態。

  • 閉合(Closed)狀態: 對應用程序的請求可以直接引發方法的調用。代理類維護了最近調用失敗的次數,若是某次調用失敗,則使失敗次數加1。若是最近失敗次數超過了在給定時間內容許失敗的閾值,則代理類切換到斷開(Open)狀態。此時代理開啓了一個超時時鐘,當該時鐘超過了該時間,則切換到半斷開(Half-Open)狀態。該超時時間的設定是給了系統一次機會來修正致使調用失敗的錯誤。
  • 斷開(Open)狀態:在該狀態下,對應用程序的請求會當即返回錯誤響應。
  • 半斷開(Half-Open)狀態:容許對應用程序的必定數量的請求能夠去調用服務。若是這些請求對服務的調用成功,那麼能夠認爲以前致使調用失敗的錯誤已經修正,此時熔斷器切換到閉合狀態(而且將錯誤計數器重置);若是這必定數量的請求有調用失敗的狀況,則認爲致使以前調用失敗的問題仍然存在,熔斷器切回到斷開方式,而後開始重置計時器來給系統必定的時間來修正錯誤。半斷開狀態可以有效防止正在恢復中的服務被忽然而來的大量請求再次拖垮。

 

  熔斷器核心功能其實是維護一個狀態機,並定義好狀態轉移的規則,這裏定義一個狀態轉移操做的抽象類AbstractBreakerState,狀態機的抽象類圖以下:

 

/**
 * 熔斷器狀態轉移操做的抽象類
 */
public abstract class AbstractBreakerState {

    protected BreakerManager manager;

    public AbstractBreakerState(BreakerManager manager) {
        this.manager = manager;
    }

    /**
     * 調用方法以前處理的操做
     */
    public void protectedCodeIsAboutToBeCalled() {
        //若是是斷開狀態,直接返回,而後等超時轉換到半斷開狀態
        if (manager.isOpen()) {
            throw new RuntimeException("服務已熔斷,請稍等重試!");
        }
    }

    /**
     * 方法調用成功以後的操做
     */
    public void protectedCodeHasBeenCalled() {
        manager.increaseSuccessCount();
    }

    /**
     * 方法調用發生異常操做後的操做
     */
    public void ActUponException() {
        //增長失敗次數計數器,而且保存錯誤信息
        manager.increaseFailureCount();
        //重置連續成功次數
        manager.resetConsecutiveSuccessCount();
    }

}
/**
 * 熔斷器閉合狀態
 * 在閉合狀態下,若是發生錯誤,而且錯誤次數達到閾值,則狀態機切換到斷開狀態
 */
public class ClosedState extends AbstractBreakerState {

    public ClosedState(BreakerManager manager) {
        super(manager);

        //重置失敗計數器
        manager.resetFailureCount();
    }

    @Override
    public void ActUponException() {
        super.ActUponException();

        //若是失敗次數達到閾值,則切換到斷開狀態
        if (manager.failureThresholdReached()) {
            manager.moveToOpenState();
        }
    }
}
/**
 * 熔斷器斷開狀態
 * 斷開狀態內部維護一個計數器,若是斷開達到必定的時間,則自動切換到半斷開狀態,而且,在斷開狀態下,若是須要執行操做,則直接拋出異常。
 */
public class OpenState extends AbstractBreakerState {

    public OpenState(BreakerManager manager) {
        super(manager);

        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                timeoutHasBeenReached();
                timer.cancel();
            }
        }, manager.timeout);
    }

    @Override
    public void protectedCodeIsAboutToBeCalled() {
        super.protectedCodeIsAboutToBeCalled();
        throw new RuntimeException("服務已熔斷,請稍等重試!");
    }

    /**
     * 斷開超過設定的閾值,自動切換到半斷開狀態
     */
    private void timeoutHasBeenReached()
    {
        manager.moveToHalfOpenState();
    }
}
/**
 * 熔斷器半斷開狀態
 * 切換到半斷開狀態時,將連續成功調用計數重置爲0,當執行成功的時候,自增該字段,當達到連讀調用成功次數的閾值時,切換到閉合狀態。
 * 若是調用失敗,當即切換到斷開模式。
 */
public class HalfOpenState extends AbstractBreakerState {

    public HalfOpenState(BreakerManager manager) {
        super(manager);

        //重置連續成功計數
        manager.resetConsecutiveSuccessCount();
    }

    @Override
    public void ActUponException() {
        super.ActUponException();

        //只要有失敗,當即切換到斷開模式
        manager.moveToOpenState();
    }

    @Override
    public void protectedCodeHasBeenCalled() {
        super.protectedCodeHasBeenCalled();

        //若是連續成功次數達到閾值,切換到閉合狀態
        if (manager.consecutiveSuccessThresholdReached()) {
            manager.moveToClosedState();
        }
    }
}

狀態管理器:

/**
 * 熔斷器管理類
 */
public class BreakerManager {

    public int failureCount; //失敗次數
    public int consecutiveSuccessCount; //連續成功次數
    public int failureThreshold; //最大調用失敗次數
    public int consecutiveSuccessThreshold; //連續調用成功次數
    public int timeout;

    private AbstractBreakerState state; //當前熔斷器狀態

    public boolean isClosed() {
        return state instanceof ClosedState;
    }

    public boolean isOpen() {
        return state instanceof OpenState;
    }

    public boolean isHalfOpen() {
        return state instanceof  HalfOpenState;
    }

    protected void moveToClosedState() {
        state = new ClosedState(this);
    }

    protected void moveToOpenState() {
        state = new OpenState(this);
    }

    protected void moveToHalfOpenState() {
        state = new HalfOpenState(this);
    }

    protected void increaseFailureCount() {
        failureCount++;
    }

    public void resetFailureCount() {
        failureCount = 0;
    }

    protected boolean failureThresholdReached() {
        return failureCount >= failureThreshold;
    }

    protected void increaseSuccessCount() {
        consecutiveSuccessCount++;
    }

    protected void resetConsecutiveSuccessCount() {
        consecutiveSuccessCount = 0;
    }

    protected boolean consecutiveSuccessThresholdReached() {
        return consecutiveSuccessCount >= consecutiveSuccessThreshold;
    }

    /**
     * Close狀態下最大失敗次數,HalfOpen狀態下使用的最大連續成功次數,以及Open狀態下的超時時間
     * 在初始狀態下,熔斷器切換到閉合狀態
     * @param failureThreshold
     * @param consecutiveSuccessThreshold
     * @param timeout
     */
    public BreakerManager(int failureThreshold, int consecutiveSuccessThreshold, int timeout)
    {
        if (failureThreshold < 1 || consecutiveSuccessThreshold < 1) {
            throw new RuntimeException("熔斷器閉合狀態的最大失敗次數和半熔斷狀態的最大連續成功次數必須大於0!");
        }
        if (timeout < 1) {
            throw new RuntimeException("熔斷器斷開狀態超時時間必須大於0!");
        }
        this.failureThreshold = failureThreshold;
        this.consecutiveSuccessThreshold = consecutiveSuccessThreshold;
        this.timeout = timeout;
        moveToClosedState();
    }

    /**
     * 該方法用於測試
     * 經過AttempCall調用,傳入指望執行的代理方法,該方法的執行受熔斷器保護。這裏使用了鎖來處理併發問題
     */
    public void attemptCall(boolean rs, int times) {

        for(int i=0; i<times; i++) {

            //須要加同步鎖
            state.protectedCodeIsAboutToBeCalled();

            try {
                //調用服務
                if(!rs) {
                    throw new Exception();
                } else {
                    System.out.println("第"+(i+1)+"服務調用成功!");
                }

            } catch (Exception e) {
                //須要加同步鎖
                System.out.println("第"+(i+1)+"服務調用超時!");
                state.ActUponException();
            }

            //須要加同步鎖
            state.protectedCodeHasBeenCalled();
        }
    }

    /**
     * 手動切換到閉合狀態
     */
    public void close() {
        //須要加同步鎖
        moveToClosedState();
    }

    /**
     * 手動切換到斷開狀態
     */
    public void open() {
        //須要加同步鎖
        moveToOpenState();
    }
}

測試類:

public class Test {
    public static void main(String[] args) {

        //定義熔斷器,失敗10次進入斷開狀態
        //在半斷開狀態下,連續成功15次,進入閉合狀態
        //5秒後進入半斷開狀態
        BreakerManager manager = new BreakerManager(10, 15, 5000);
        showState(manager);

        //模擬失敗10次調用
        manager.attemptCall(false, 10);
        System.out.println(manager.failureCount);
        showState(manager);

        //這裏若是再調用一次服務,正常會拋出「服務已熔斷」的異常
        //manager.attemptCall(true, 1);

        //等待熔斷器超時,從Open轉到HalfOpen
        try {
            System.out.println("等待熔斷器超時(6s)。。。");
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        showState(manager);

        //模擬成功調用15次
        manager.attemptCall(true, 10);
        //這裏若是出現一次調用服務失敗,熔斷器會立刻進入熔斷狀體,接下來的調用會拋出「服務已熔斷」的異常
        //manager.attemptCall(false, 1);
        manager.attemptCall(true, 5);

        System.out.println(manager.consecutiveSuccessCount);
        System.out.println(manager.failureCount);
        showState(manager);

    }

    public static void showState(BreakerManager manager) {
        System.out.println("Breaker is Closed:" + manager.isClosed());
        System.out.println("Breaker is Open:" + manager.isOpen());
        System.out.println("Breaker is isHalfOpen:" + manager.isHalfOpen());
    }
}

測試結果:

Breaker is Closed:true
Breaker is Open:false
Breaker is isHalfOpen:false
第1服務調用超時!
第2服務調用超時!
第3服務調用超時!
第4服務調用超時!
第5服務調用超時!
第6服務調用超時!
第7服務調用超時!
第8服務調用超時!
第9服務調用超時!
第10服務調用超時!
10
Breaker is Closed:false
Breaker is Open:true
Breaker is isHalfOpen:false
等待熔斷器超時(6s)。。。
Breaker is Closed:false
Breaker is Open:false
Breaker is isHalfOpen:true
第1服務調用成功!
第2服務調用成功!
第3服務調用成功!
第4服務調用成功!
第5服務調用成功!
第6服務調用成功!
第7服務調用成功!
第8服務調用成功!
第9服務調用成功!
第10服務調用成功!
第1服務調用成功!
第2服務調用成功!
第3服務調用成功!
第4服務調用成功!
第5服務調用成功!
15
0
Breaker is Closed:true
Breaker is Open:false
Breaker is isHalfOpen:false

參考文章:

http://www.cnblogs.com/yangecnu/p/Introduce-Circuit-Breaker-Pattern.html

http://martinfowler.com/bliki/CircuitBreaker.html

http://msdn.microsoft.com/en-us/library/dn589784.aspx

https://yq.aliyun.com/articles/7443

http://www.tuicool.com/articles/AbiqEnn

相關文章
相關標籤/搜索