Spring Cloud Hystrix —— 服務(wù)容錯(cuò)保護(hù)

Spring Cloud Hystrix實(shí)現(xiàn)了斷路器、線程隔離等一系列服務(wù)保護(hù)功能啤呼。它是基于Netflix的開(kāi)源框架Hystrix實(shí)現(xiàn)的局服,該框架的目標(biāo)在于通過(guò)控制那些訪問(wèn)遠(yuǎn)程系統(tǒng)磷仰、服務(wù)和第三方庫(kù)的節(jié)點(diǎn)优炬,從而對(duì)延遲和故障提供更強(qiáng)大的容錯(cuò)能力颁井。Hystrix具備服務(wù)降級(jí)(fallback)、服務(wù)熔斷蠢护、線程和信號(hào)隔離雅宾、請(qǐng)求緩存、請(qǐng)求合并以及服務(wù)監(jiān)控等強(qiáng)大功能葵硕。

What Is Hystrix For?

  • 保護(hù)和控制對(duì)依賴項(xiàng)的網(wǎng)絡(luò)延時(shí)和失敗情況
  • 防止級(jí)聯(lián)故障
  • 快速失敗眉抬、快速恢復(fù)
  • fallback和服務(wù)降級(jí)
  • 接近實(shí)時(shí)級(jí)的監(jiān)控、報(bào)警和控制

How Does Hystrix Accomplish Its Goals?

  • 使用命令模式將所有對(duì)外部服務(wù)(或依賴關(guān)系)的調(diào)用包裝在HystrixCommand或HystrixObservableCommand對(duì)象中懈凹,并將該對(duì)象放在單獨(dú)的線程中執(zhí)行蜀变;
  • 支持自定義依賴項(xiàng)的超時(shí)時(shí)間,一般稍高于99.5%的訪問(wèn)時(shí)間介评;
  • 每個(gè)依賴都維護(hù)著一個(gè)線程池(或信號(hào)量)库北,線程池被耗盡則拒絕請(qǐng)求(而不是讓請(qǐng)求排隊(duì))。
  • 記錄請(qǐng)求成功们陆,失敗寒瓦,超時(shí)和線程拒絕。
  • 服務(wù)錯(cuò)誤百分比超過(guò)了閾值坪仇,熔斷器開(kāi)關(guān)自動(dòng)打開(kāi)杂腰,一段時(shí)間內(nèi)停止對(duì)該服務(wù)的所有請(qǐng)求。
  • 請(qǐng)求失敗烟很,被拒絕颈墅,超時(shí)或熔斷時(shí)執(zhí)行降級(jí)邏輯。
  • 近實(shí)時(shí)地監(jiān)控指標(biāo)和配置的修改雾袱。


    實(shí)現(xiàn)方法

設(shè)計(jì)模式

命令模式

觀察者模式

艙壁模式

原理分析

工作流程

工作流程
  1. 構(gòu)造一個(gè) HystrixCommand或HystrixObservableCommand對(duì)象恤筛,用于封裝請(qǐng)求,并在構(gòu)造方法配置請(qǐng)求被執(zhí)行需要的參數(shù)芹橡;
  2. 執(zhí)行命令毒坛,Hystrix提供了4種執(zhí)行命令的方法,后面詳述林说;
  3. 判斷是否使用緩存響應(yīng)請(qǐng)求煎殷,若啟用了緩存,且緩存可用腿箩,直接使用緩存響應(yīng)請(qǐng)求豪直。Hystrix支持請(qǐng)求緩存,但需要用戶自定義啟動(dòng)珠移;
  4. 判斷熔斷器是否打開(kāi)弓乙,如果打開(kāi)末融,跳到第8步;
  5. 判斷線程池/隊(duì)列/信號(hào)量是否已滿暇韧,已滿則跳到第8步勾习;
  6. 執(zhí)行HystrixObservableCommand.construct()或HystrixCommand.run(),如果執(zhí)行失敗或者超時(shí)懈玻,跳到第8步巧婶;否則,跳到第9步涂乌;
  7. 統(tǒng)計(jì)熔斷器監(jiān)控指標(biāo)艺栈;
  8. 走Fallback備用邏輯
  9. 返回請(qǐng)求響應(yīng)

執(zhí)行命令的幾種方法

Hystrix提供了4種執(zhí)行命令的方法,execute()和queue() 適用于HystrixCommand對(duì)象湾盒,而observe()和toObservable()適用于HystrixObservableCommand對(duì)象眼滤。

  • execute()

以同步堵塞方式執(zhí)行run(),只支持接收一個(gè)值對(duì)象历涝。hystrix會(huì)從線程池中取一個(gè)線程來(lái)執(zhí)行run(),并等待返回值漾唉。

  • queue()

以異步非阻塞方式執(zhí)行run()荧库,只支持接收一個(gè)值對(duì)象。調(diào)用queue()就直接返回一個(gè)Future對(duì)象赵刑》稚溃可通過(guò) Future.get()拿到run()的返回結(jié)果,但Future.get()是阻塞執(zhí)行的般此。若執(zhí)行成功蚪战,F(xiàn)uture.get()返回單個(gè)返回值。當(dāng)執(zhí)行失敗時(shí)铐懊,如果沒(méi)有重寫(xiě)fallback邀桑,F(xiàn)uture.get()拋出異常。

  • observe()

事件注冊(cè)前執(zhí)行run()/construct()科乎,支持接收多個(gè)值對(duì)象壁畸,取決于發(fā)射源。調(diào)用observe()會(huì)返回一個(gè)hot Observable茅茂,也就是說(shuō)捏萍,調(diào)用observe()自動(dòng)觸發(fā)執(zhí)行run()/construct()若专,無(wú)論是否存在訂閱者唾戚。

如果繼承的是HystrixCommand,hystrix會(huì)從線程池中取一個(gè)線程以非阻塞方式執(zhí)行run()匀借;如果繼承的是HystrixObservableCommand碴倾,將以調(diào)用線程阻塞執(zhí)行construct()逗噩。

observe()使用方法:

  1. 調(diào)用observe()會(huì)返回一個(gè)Observable對(duì)象
  2. 調(diào)用這個(gè)Observable對(duì)象的subscribe()方法完成事件注冊(cè)掉丽,從而獲取結(jié)果
  • toObservable()

事件注冊(cè)后執(zhí)行run()/construct(),支持接收多個(gè)值對(duì)象给赞,取決于發(fā)射源机打。調(diào)用toObservable()會(huì)返回一個(gè)cold Observable,也就是說(shuō)片迅,調(diào)用toObservable()不會(huì)立即觸發(fā)執(zhí)行run()/construct()残邀,必須有訂閱者訂閱Observable時(shí)才會(huì)執(zhí)行。

如果繼承的是HystrixCommand柑蛇,hystrix會(huì)從線程池中取一個(gè)線程以非阻塞方式執(zhí)行run()芥挣,調(diào)用線程不必等待run();如果繼承的是HystrixObservableCommand耻台,將以調(diào)用線程堵塞執(zhí)行construct()空免,調(diào)用線程需等待construct()執(zhí)行完才能繼續(xù)往下走。

toObservable()使用方法:

  1. 調(diào)用observe()會(huì)返回一個(gè)Observable對(duì)象
  2. 調(diào)用這個(gè)Observable對(duì)象的subscribe()方法完成事件注冊(cè)盆耽,從而獲取結(jié)果

需注意的是蹋砚,HystrixCommand也支持toObservable()和observe(),但是即使將HystrixCommand轉(zhuǎn)換成Observable摄杂,它也只能發(fā)射一個(gè)值對(duì)象坝咐。只有HystrixObservableCommand才支持發(fā)射多個(gè)值對(duì)象。

斷路器原理

circute breaker

依賴隔離

Hystrix為每個(gè)以來(lái)的服務(wù)創(chuàng)建一個(gè)獨(dú)立的線程池析恢,這樣就算某個(gè)依賴服務(wù)出現(xiàn)延遲過(guò)高的情況墨坚,也只是對(duì)該依賴服務(wù)的調(diào)用有影響,而不會(huì)拖慢其他依賴服務(wù)映挂。

另外Hystrix中除了可使用線程池之外泽篮,還可以使用信號(hào)量來(lái)控制單個(gè)以來(lái)服務(wù)的并發(fā)度,信號(hào)量的開(kāi)銷遠(yuǎn)比線程池的開(kāi)銷小柑船,但是它不能設(shè)置超時(shí)和實(shí)現(xiàn)異步訪問(wèn)帽撑。

使用詳解

構(gòu)建一個(gè)如下架構(gòu)圖的服務(wù)調(diào)用關(guān)系

example

分析上述架構(gòu)圖,主要有以下幾項(xiàng)工作:

  1. eureka-server工程: 服務(wù)注冊(cè)中心椎组,端口1111
  2. hello-service工程: HELLO-SERVICE服務(wù)單元油狂,啟動(dòng)兩個(gè)實(shí)例,端口分別為8081和8082
  3. ribbon-consumer工程: 使用Ribbon實(shí)現(xiàn)的服務(wù)消費(fèi)者寸癌,端口9000

    下面我們開(kāi)始引入Spring Cloud Hystrix专筷。
  • 在ribbeon-consumer工程中引入依賴:
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
  • 在ribbon-consumer工程主類ConsumerApplication中使用@Enable-CircuitBreaker注釋開(kāi)啟斷路器功能:
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {

  @Bean
  @LoadBalanced
  RestTemplate restTemplate() {
    return new RestTemplate();
  }

  public static void main (String[] args) {
    SpringApplication.run(ConsumerApplication.class, args);
  }
}

HystrixCommand支持繼承和注釋兩種寫(xiě)法,本文只介紹注釋方法蒸苇,對(duì)繼承方法感興趣的同學(xué)可以baidu一些資料磷蛹。

  • 同步執(zhí)行
public class UserService {
  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand
  public User getUserById(Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
  }
}
  • 異步執(zhí)行
public class UserService {
  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand
  public Future<User> getUserByIdAsync(final String id) {
    return new AsyncResult<User>() {
      @Override
      public User invoke() {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
      }
    }
  }
}

除了傳統(tǒng)的同步執(zhí)行與異步執(zhí)行之外,我們還可以將HystrixCommand通過(guò)Observale來(lái)實(shí)現(xiàn)響應(yīng)式執(zhí)行方式溪烤。

public class UserService {

  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER)
  public Observable<String> getUserById(final String id) {
    return Observable.create(new Observable.OnSubscribe<String>() {
      @Override
      public void call(Subscriber<? super String> observer) {
        try {
          if (!observer.isUnsubscribed()) {
            String user = restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
            observer.onNext(user);
            observer.onCompleted();
          }
        } catch (Exception e) {
          observer.onError(e);
        }
      }
    });
  }
}

下面是一個(gè)簡(jiǎn)單的Observable的使用示例味咳。關(guān)于響應(yīng)式編程庇勃,有興趣的可以研究一下RxJava。

public void useObservable () {
  Observable<String> observable = userService.getUserById("1");
  observable.subscribe(new Action1<String>() {
    @Override
    public void call(String s) {
      //
    }
  });
}
  • 定義服務(wù)降級(jí)

fallback是Hystrix命令執(zhí)行失敗時(shí)使用的后備方法槽驶,用來(lái)實(shí)現(xiàn)服務(wù)的降級(jí)處理邏輯责嚷。在HystrixCommand中可以通過(guò)重載getFallBack()方法來(lái)實(shí)現(xiàn)服務(wù)降級(jí)邏輯,Hystrix會(huì)再run()執(zhí)行過(guò)程中出現(xiàn)錯(cuò)誤掂铐、超時(shí)罕拂、線程池拒絕、斷路器熔斷等情況全陨,執(zhí)行g(shù)etFallback()方法內(nèi)的邏輯爆班。

public class UserService {

  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand(fallbackMethod = "defaultUser")
  public String getUserById(Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
  }

  public String defaultUser() {
    return "default user";
  }

}

如果fallback邏輯還是不穩(wěn)定,可以添加多級(jí)fallback邏輯辱姨。

public class UserService {

  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand(fallbackMethod = "defaultUser")
  public String getUserById(Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
  }

  @HystrixCommand(fallbackMethod = "defaultUserSec")
  public String defaultUser() {
    return "first default user";
  }

  public String defaultUserSec() {
    return "second default user";
  }

}

不論Hystrix命令是否實(shí)現(xiàn)了服務(wù)降級(jí)柿菩,命令狀態(tài)和斷路器狀態(tài)都會(huì)更新,并且我們可以由此了解到命令執(zhí)行的失敗情況雨涛。

  • 忽略異常
public class UserService {

  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand(fallbackMethod = "defaultUser", ignoreExceptions = {HystrixBadRequestException.class})
  public String getUserById(Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
  }

  public String defaultUser(Long id, Throwable e) {
    return "default user";
  }

}
  • 命名名稱枢舶、分組以及線程池劃分
@HystrixCommand(commandKey = "getUserById", groupKey = "UserGroup", threadPoolKey = "getUserByIdThread")
public String getUserById(Long id) {
  return restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
}
  • 請(qǐng)求緩存
@CacheResult
@HystrixCommand
public String getUserById(@CacheKey("id") Long id) {
  return restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
}

@CacheRemove(commandKey = "getUserById")
@HystrixCommand
public void update(@CacheKey("id") User user) {
  return restTemplate.postForObject("http://USER-SERVICE/users/{1}", user, User.class);
}
  • 請(qǐng)求合并
    HystrixCollapser可以實(shí)現(xiàn)對(duì)以來(lái)服務(wù)請(qǐng)求的合并,以減少通信小號(hào)和線程數(shù)的占用替久。
@Service
public class UserService {

  @Autowired
  private RestTemplate restTemplate;

  @HystrixCollapser(batchMethod = "findAll", collapserProperties = {
      @HystrixProperty(name = "timeDelayInMilliseconds", value = "100")
  })
  public String find(Long id) {
    return null;
  }

  @HystrixCommand
  public List<String> findAll(List<Long> ids) {
    return restTemplate.getForObject("http://USER-SERVICE/users?ids={1}", List.class,
        StringUtils.join(ids, ","));
  }

}

屬性詳解

Hystrix儀表盤(pán)

Turbine集群監(jiān)控

Reference
Netflix/Hystrix Wiki
Netflix/Hystrix Wiki How-To-Use
Hystrix原理與實(shí)戰(zhàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末祟辟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子侣肄,更是在濱河造成了極大的恐慌,老刑警劉巖醇份,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稼锅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡僚纷,警方通過(guò)查閱死者的電腦和手機(jī)矩距,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)怖竭,“玉大人锥债,你說(shuō)我怎么就攤上這事∪簦” “怎么了哮肚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)广匙。 經(jīng)常有香客問(wèn)我允趟,道長(zhǎng),這世上最難降的妖魔是什么鸦致? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任潮剪,我火速辦了婚禮涣楷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抗碰。我一直安慰自己狮斗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布弧蝇。 她就那樣靜靜地躺著碳褒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捍壤。 梳的紋絲不亂的頭發(fā)上骤视,一...
    開(kāi)封第一講書(shū)人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音鹃觉,去河邊找鬼专酗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛盗扇,可吹牛的內(nèi)容都是我干的祷肯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼疗隶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼佑笋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起斑鼻,我...
    開(kāi)封第一講書(shū)人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蒋纬,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后坚弱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蜀备,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年荒叶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碾阁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡些楣,死狀恐怖脂凶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情愁茁,我是刑警寧澤蚕钦,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站鹅很,受9級(jí)特大地震影響冠桃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜道宅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一食听、第九天 我趴在偏房一處隱蔽的房頂上張望胸蛛。 院中可真熱鬧,春花似錦樱报、人聲如沸葬项。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)民珍。三九已至,卻和暖如春盗飒,著一層夾襖步出監(jiān)牢的瞬間嚷量,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工逆趣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝶溶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓宣渗,卻偏偏與公主長(zhǎng)得像抖所,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痕囱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容