在之前的文章中,咱們介紹過使用Gauva實現限流的功能,如今咱們來了解一下如何在服務框架中實現熔斷和降級的方法。html
簡介Hystrixjava
大型系統架構的演進基本上都是這樣一個方向:從單體應用到分佈式架構。這個演進過程離不開一個字「拆」,咱們會把一個系統拆成不少子系統,子系統之間必定會存在依賴,有的是強依賴(好比Http調用),有的是弱依賴(好比,使用消息中間件)。「拆」的主要意義之一就是一個子系統掛了不影響其餘的服務正常運行,而對於那麼咱們拆出來強依賴的服務若是掛了,仍是會影響整個系統,因此咱們光「拆」是沒有達到效果的。後端
Hystrix源於Netflix,該庫能夠對那些在訪問遠程系統、服務、第三方庫或者其餘方法調用時所產生的延遲或者故障提供更強大的容錯能力。說白了就是提供了一種簡潔、靈活和強大的框架去處理依賴異常,一個服務掛了不影響其餘的服務正常運行,也是微服務概念中熔斷,降級的具體框架實現。服務器
它主要的功能以下:網絡
1.對第三方組件進行訪問(特別是網絡訪問)時出現的延遲或者的異常進行保護和控制。架構
2.在複雜的分佈式系統中,中止級聯故障。框架
3.快速失敗,快速恢復。分佈式
4.能夠優雅的降級。ide
5.能夠提供近實時的監控、告警和操做控制。函數
典型場景:雪崩效應
Hystrix典型的場景就是處理複雜的分佈式調用過程當中依賴服務延遲或者異常的場景。下圖是一張服務依賴圖,用戶的請求依賴於跨網絡的A、H、I、P四個其餘的服務。
若是各個服務運行的都很好,那麼不會有問題。在大型系統中,隨着服務愈來愈多,網絡或者性能問題引發的超時,邏輯的異常帶來的不可訪問,都是必然會出現的狀況。
如上圖,一個用戶請求的時候,其中的一個依賴服務I出現了延遲,那麼整個的這個用戶請求將被阻塞。
隨着大量流量的增長,一個單一的後端依賴會成爲潛在風險,可能致使整個環境的軟硬件資源出現問題。不僅僅這個服務會掛掉,這些問題還有可能,影響環境中的消息隊列、線程等其餘資源,從而致使系統上的級聯故障。最終出現下圖的狀況。
網絡鏈接失敗或降級,服務和服務器失敗或變慢,新的庫或服務部署改變行爲或性能特徵,客戶端庫有bug等等,全部這些都有可能會形成上述的狀況,這就須要咱們要隔離和管理這些故障和延遲,以便一個失敗的依賴項不能影響整個應用程序或系統。而Hystrix正是解決這類問題的。
Hystrix怎樣解決這些問題
1.Hystrix使用命令模式HystrixCommand(Command)包裝依賴調用邏輯,每一個命令在單獨線程中/信號受權下執行。
2.可配置依賴調用超時時間,超時時間通常設爲比99.5%平均時間略高便可.當調用超時時,直接返回或執行fallback邏輯。
3.爲每一個依賴提供一個小的線程池(或信號),若是線程池已滿調用將被當即拒絕,默認不採用排隊.加速失敗斷定時間。
4.依賴調用結果分:成功,失敗(拋出異常),超時,線程拒絕,短路。 請求失敗(異常,拒絕,超時,短路)時執行fallback(降級)邏輯。
5.提供熔斷器組件,能夠自動運行或手動調用,中止當前依賴一段時間(10秒),熔斷器默認錯誤率閾值爲50%,超過將自動運行。
6.提供近實時依賴的統計和監控
Hystrix把每一個潛在的依賴性進行包裝,把每個依賴項都與另外一個依賴項隔離開來,當潛在的問題發生時能限制它的資源,並在調用發生異常時可以決定如何應答給調用方,從而快速的恢復處理,不影響下面的調用。以下圖,Hystrix對依賴I配置了5個線程。當調用依賴I的時候,Hystrix配置了兩種策略,異常和超時,那麼只要有同樣觸發,或者線程被佔滿,就能夠立刻進入咱們預設的異常處理邏輯。
示例程序HelloWorld
下面咱們用一個簡單的例子來展現怎樣使用Hystrix來處理上面的場景。一個接口調用超過5秒鐘或者出現異常的時候,走另外的降級邏輯。
1.pom文件,引用jar包
<dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-core</artifactId> <version>1.5.12</version> </dependency>
2.使用Hystrix的命令基類HystrixCommand封裝調用邏輯,在執行方法體中,我模擬了3種調用結果:分別是正常調用、調用超時(5s)、和接口中出現異常。
import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; import com.netflix.hystrix.HystrixCommandProperties; import java.util.concurrent.TimeUnit; /** * 使用HystrixCommand封裝執行方法。繼承Hystrix命令基類,泛型參數即調用方法的返回參數 */ public class HelloWorldCommand extends HystrixCommand<String> { private final String name; /** * 定義構造函數,參數即被包裝方法的輸入參數 * @param name */ public HelloWorldCommand(String name) { //定義命令組 和 方法調用超時時間 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup")) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(5000))); this.name = name; } /** * 封裝業務邏輯的方法體,在這裏執行真正的業務邏輯。 * @return * @throws Exception */ @Override protected String run() throws Exception { //例子中,模擬執行超時 if (name.equals("TimeoutCommand")) { TimeUnit.SECONDS.sleep(6); } //例子中,模擬出現異常 if (name.equals("ExceptionCommand")) { throw new Exception("ExceptionCommand"); } //例子中,模擬正常返回 return "Hello " + name + "!"; } /** * 降級方法定義,即,run方法中出現異常後應該執行的方法。包括例子中的超時。 * @return */ @Override protected String getFallback() { return "Command failed!"; } }
3.調用方法
public class HelloWorld
{
public static void main(String[] args)
{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = new HelloWorldCommand("Nick").execute();
System.out.println(sdf.format(new Date()) + "->" + s);
s = new HelloWorldCommand("TimeoutCommand").execute();
System.out.println(sdf.format(new Date()) + "->" + s);
s = new HelloWorldCommand("ExceptionCommand").execute();
System.out.println(sdf.format(new Date()) + "->" + s);
}
}
調用方法模擬了3中不一樣的結果:正常調用、調用超時(5s)、和接口中出現異常。結果以下:
2017-12-24 17:58:18->Hello Nick!
2017-12-24 17:58:23->Command failed!
2017-12-24 17:58:23->Command failed!