最近在做分布式服務熔斷,因為要實現一個熔斷器狀態機,所以想到狀態模式。狀態模式是當一個對象的內在狀態改變時允許改變其行為,這個對象看起來像是改變了其類。狀態模式主要解決的是當控制一個對象狀態的條件表達式過於復雜時的情況。把狀態的判斷邏輯轉移到表示不同狀態的一系列類中,可以把復雜的判斷邏輯簡化。
先舉個簡單的例子,以紅綠燈模型,說明狀態模式是怎么一回事:
通常情況下我們是這樣實現的:
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(); } } }
輸出:
以上這種寫法,當增加一種燈控邏輯的時候就需要再添加一個分支,導致我們需要不斷的去修改代碼邏輯,下面使用狀態模式實現:
定義狀態接口
public interface LightState { void showLight(LightManager manager, LightState nextState); }
我們希望顯示燈亮的時候程序休眠幾秒,但我們不希望在LightState的每個實現類里面去定義一個sleep方法,所以我們定義一個抽象類Light來實現上面的接口
public abstract class Light implements LightState { protected void sleep(int second) { try { Thread.sleep(second); } catch (InterruptedException e) { e.printStackTrace(); } } }
然后下面分別寫LightState接口的三個實現類
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); } }
接下來我們要實現一個燈控的管理類
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); } }
測試類
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