服務(wù)容錯保護:Spring Cloud Hystrix

Spring Cloud Hystrix實現(xiàn)了斷路器、線程隔離等一系列服務(wù)保護措施采驻,它也是基于Netflix的開源框架Hystrix實現(xiàn)的墓毒,該框架的目標是通過控制那些訪問遠程系統(tǒng)动羽、服務(wù)和第三方庫的節(jié)點,從而對延遲和故障提供更強大的容錯能力镣典。Hystrix具備服務(wù)降級兔毙、服務(wù)熔斷、線程和信號隔離兄春、請求緩存澎剥、請求合并以及服務(wù)監(jiān)控等強大功能。

快速入門

-在服務(wù)消費者工程的pom.xml中添加spring-cloud-starter-hystrix依賴

<dependency>
  <groupId>org.springframework.cloud<groupId>
  <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

-在消費者工程的主類ConsumerApplication中使用@EnableCircuitBreaker注解開啟斷路器功能赶舆。

@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
  @Bean
  @LoadBalanced
  RestTemplate restTemplate() {
    return new RestTemplate();
  }

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

-改造服務(wù)消費方式哑姚,新增HelloService類,注入RestTemplate實例芜茵,然后叙量,將在ConsumerController中對RestTemplate的使用遷移到HelloService函數(shù)中,最后在HelloService函數(shù)上增加@HystrixCommand注解來指定回調(diào)方法:

@Service
public class HelloService {

  @Autowired
  RestTemplate restTemplate;

  @HystrixCommand(fallbackMethod="helloFallback")
  public String helloService() {
    return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
  }

  public String helloFallback() {
    return "error";
  }
}

-修改ConsumerController類九串,注入上面實現(xiàn)的HelloService實例绞佩,并在helloConsumer中進行調(diào)用

@RestController
public class ConsumerController {
  @Autowired
  HelloService helloService;

  @RequestMapping(value="/ribbon-consumer", method=RequestMethod.GET)
  public String helloConsumer() {
    return helloService.helloService();
  }
}

原理分析

工作流程

1寺鸥、創(chuàng)建HystrixCommand或者HystrixObservableCommand對象
-HystrixCommand:用在依賴的服務(wù)返回單個操作結(jié)果的時候。
-HystrixObservableCommand:用在依賴的服務(wù)返回多個操作結(jié)果的時候品山。
2胆建、命令執(zhí)行
Hystrix共有四種命令的執(zhí)行方式,HystrixCommand實現(xiàn)了兩種執(zhí)行方式肘交,分別為:
-execute():同步執(zhí)行笆载,從依賴的服務(wù)返回一個單一的結(jié)果對象,或是在發(fā)生錯誤的時候拋出異常
-queue():異步執(zhí)行酸些,直接返回一個Future對象宰译,其中包含了服務(wù)執(zhí)行結(jié)束時要返回的單一結(jié)果的對象

R value = command.execute();
Future<R> fValue = command.queue();

HystrixObservableCommand實現(xiàn)了另外兩種執(zhí)行方式
-observe():返回Observable對象檐蚜,它代表了操作的多個結(jié)果魄懂,它是一個HotObservable。
-toObservable():同樣會返回Observable對象闯第,也代表了操作的多個結(jié)果市栗,但它返回的是一個Cold Observable。

Observable<R> ohValue = command.observe();
Observable<R> ocValue = command.toObservable();

3咳短、結(jié)果是否被緩存
4填帽、斷路器是否打開
5、線程池/請求隊列/信號量是否占滿

依賴隔離

Hystrix使用“艙壁隔離”模式實現(xiàn)線程池的隔離咙好,它會為每一個依賴服務(wù)創(chuàng)建一個獨立的線程池篡腌,這樣就算某個依賴服務(wù)出現(xiàn)延遲過高的情況,也只是對該依賴服務(wù)的調(diào)用產(chǎn)生影響勾效,而不會陀滿其他的依賴服務(wù)嘹悼。

使用詳解

Hystrix的核心注解是@HystrixCommand,通過它創(chuàng)建了HystrixCommand的實現(xiàn),同時利用fallback屬性指定了服務(wù)降級的實現(xiàn)方法层宫。

創(chuàng)建請求命令

Hystraix命令就是HystrixCommand杨伙,用于封裝具體的依賴服務(wù)調(diào)用邏輯。通過繼承的方式來實現(xiàn)

public class UserCommand extends HystrixCommand<User> {
  private RestTemplate restTemplate;
  private Long id;
  public UserCommand(Setter setter, RestTemplate restTemplate萌腿, Long id) {
    super(setter);
    this.restTemplate = restTemplate;
    this.id = id;
  }

  @Override
  protected User run() {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
  }
}

-同步執(zhí)行:User u = new UserCommand(restTemplate, 1L).execute();
-異步執(zhí)行:Future<User> futureUser = new UserFuture(restTemplate,1L).queue();異步執(zhí)行的時候限匣,可以通過對返回的futureUser調(diào)用get方法來獲得結(jié)果。
另外毁菱,也可以通過@HystrixCommand注解來實現(xiàn)Hystrix命令

public class UserService {
  @Autowired
  private RestTemplate restTemplate米死;

  @HystrixCommand
  public User getUserById(Long id) {
    return restTemplate.getForObject()
  }
}

雖然@HystrixCommand注解可以定義Hystrix命令的實現(xiàn),但是如上定義的getUserById方式只是同步執(zhí)行的實現(xiàn)贮庞,若要實現(xiàn)異步執(zhí)行則還需要另外定義

@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);
    }
  }
}

在使用@HystrixCommand注解實現(xiàn)響應(yīng)式命令時哲身,可以通過observableExecutionMode參數(shù)來控制使用observe()還是toObservable()的執(zhí)行方式。該參數(shù)有兩種設(shè)置方式贸伐。
-@HystrixCommand(observableExecutionMode=ObservableExecutionMode.EAGER);EAGER是該參數(shù)的模式值勘天,表示toObserve()執(zhí)行方式
-@HystrixCommand(observableExecutionMode=ObservableExecutionMode.LAZY);表示使用toObservable()執(zhí)行方式。

定義服務(wù)降級

fallback是Hystrix命令執(zhí)行失敗時使用的后備方法,用來實現(xiàn)服務(wù)的降級處理邏輯脯丝。在HystrixCommand中可以通過重載getFallback()方法來實現(xiàn)服務(wù)降級邏輯商膊,Hystrix會在run()執(zhí)行過程中出現(xiàn)錯誤、超時宠进、線程池拒絕晕拆、斷路器等情況時,執(zhí)行g(shù)etFallback()方法內(nèi)的邏輯

public class UserCommand extends HystrixCommand<User> {
  private RestTemplate restTemplate;
  private Long id;

  public UserCommand(Setter setter, RestTemplate restTemplate,Long id) {
    super(setter);
    this.restTemplate = restTemplate材蹬;
    this.id = id;
  }

  @Override
  protected User run() {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
  }

  @Override
  protected User getFallback() {
    return new User();
  }
}

使用注解實現(xiàn)降級服務(wù)只需要使用@HystrixCommand中的fallbackMethod參數(shù)來指定具體的服務(wù)降級實現(xiàn)方法

public class UserService {
  @Autowired
  private RestTemplate restTemplate实幕;

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

  public User defaultUser() {
    return new User();
  } 
}

在使用注解定義服務(wù)降級邏輯時,我們需要將具體的Hystrix命令與fallback的實現(xiàn)函數(shù)定義在同一個類中堤器,并且fallbackMethod的值必須與實現(xiàn)fallback方法的名字相同昆庇。由于必須定義在一個類中,所以對于fallback的訪問修飾符沒有特定的要求闸溃,定義為private整吆、protected、public均可辉川。

異常處理

異常傳播

當(dāng)HystrixCommand實現(xiàn)的run()方法拋出異常時表蝙,這些異常會被Hystrix認為是命令執(zhí)行失敗并觸發(fā)服務(wù)降級的處理邏輯。
使用注解配置實現(xiàn)Hystrix命令時乓旗,它還支持忽略指定異常類型功能

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

異常處理

命令名稱府蛇、分組以及線程池劃分

以繼承方式實現(xiàn)的Hystrix命令使用類名作為作為默認名稱,我們也可以在構(gòu)造函數(shù)中通過Setter靜態(tài)類來設(shè)置

public UserCommand() {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("Groupname")).andCommandKey(HystrixCommandKey.Factory.asKey("CommandName")))
}

使用注解的時候設(shè)置命令名稱屿愚、分組以及線程池

@HystrixCommand(commandKey="getByUserId", groupKey="UserGroup", threadPoolKey="getUserByIdThread")
public User getUserById(Long id) {
  return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
}

請求緩存

開啟請求緩存的功能:在實現(xiàn)HystrixCommand或HystrixObservableCommand時汇跨,通過重載getCacheKey()方法來開啟請求緩存。
清理失效緩存功能:在Hystrix中渺鹦,通過HystrixRequestCache.clear()方法進行緩存清理扰法。

工作原理

嘗試獲取請求:Hystrix命令在執(zhí)行前會根據(jù)之前提到的isRequestCachingEnabled方法來判斷當(dāng)前命令是否啟用了請求緩存。如果開啟了請求緩存且重寫了getCacheKey方法毅厚,并返回了一個非null的緩存Key值塞颁,那么就使用getCacheKey返回的Key值調(diào)用HystrixRequestCache的get(String cacheKey)獲取緩存的HystrixCachedObservable對象。
將請求結(jié)果加入緩存
-設(shè)置請求緩存:添加@CacheResult注解吸耿,Hystrix會將結(jié)果加入請求緩存中祠锣,而它的緩存Key值會使用所有的參數(shù)

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

-定義緩存key:可以使用@CacheResult和@CacheRemove注解的cacheKeymethod方法來指定具體的生成函數(shù);也可以通過使用@CacheKey注解在方法中指定用于組裝緩存Key的元素咽安。

@CacheResult(cacheKeyMethod="getUserByIdCacheKey")
@HystrixCommand
public User getUserById(Long id) {
  return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
}
private Long getUserByIdCacheKey(Long id) {
  return id;
}

使用@CacheKey注解實現(xiàn)方式更簡單伴网,但是它的優(yōu)先級比cacheKeyMethod的優(yōu)先級低,如果已經(jīng)使用了cacheKeyMethod指定了緩存key的生成函數(shù)妆棒,那么@CacheKey注解將不會生效

@CacheResult
@HystrixCommand
public User getUserById(@CacheKey("id") Long id) {
 return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
}

@CacheKey注解除了可以指定方法參數(shù)作為緩存Key之外澡腾,它還允許訪問參數(shù)對象的內(nèi)部屬性作為緩存Key

@CacheKey
@HystrixCommand
public User getUserById(@CacheKey("id") User user) {
  return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,user.getId())
}

-緩存清理沸伏,通過@CacheRemove注解來實現(xiàn)失效緩存的清理

@CacheResult
@HystrixCommand
public User getUserById (@CacheKey ("id") Long id) {
  return restTemplate.getForObject("http://USER-SERVICE/users/{1} ", User. class);
 }

@CacheRemove(commandKey="getUserById")
@HystrixCommand
public void update(@CacheKey("id") User user) {
  return restTemplate.postForObject("http://USER-SERVICE/users",user,User.class);
}

@CacheRemove注解的commandKey屬性必須要指定的,它用來指明需要使用請求緩存的請求命令

請求合并

屬性詳解

command屬性

command屬性主要用來控制的是HystrixCommand命令的行為动分,主要有五種不同類型的屬性配置
-execution配置毅糟,主要用來控制HystrixCommand.run()的執(zhí)行
-fallback配置,主要用于控制HystrixCommand.getFallback()的執(zhí)行澜公,這些屬性同時適用于線程池的信號量的隔離策略姆另。
-circuitBreaker配置,主要用于HystrixCircuitBreaker斷路器的行為
-metrics配置坟乾,與HystrixCommand和HystrixObservableCommand執(zhí)行中捕獲的指標信息有關(guān)迹辐。
-requestContext配置,與HystrixCommand使用的HystrixRequestContext的設(shè)置甚侣。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末明吩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子渺绒,更是在濱河造成了極大的恐慌贺喝,老刑警劉巖菱鸥,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宗兼,死亡現(xiàn)場離奇詭異,居然都是意外死亡氮采,警方通過查閱死者的電腦和手機殷绍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹊漠,“玉大人主到,你說我怎么就攤上這事∏牛” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵娶靡,是天一觀的道長。 經(jīng)常有香客問我姿锭,道長,這世上最難降的妖魔是什么呻此? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任轮纫,我火速辦了婚禮焚鲜,結(jié)果婚禮上放前,老公的妹妹穿的比我還像新娘。我一直安慰自己犀斋,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布叽粹。 她就那樣靜靜地躺著,像睡著了一般却舀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挽拔,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天辆脸,我揣著相機與錄音,去河邊找鬼螃诅。 笑死啡氢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的术裸。 我是一名探鬼主播倘是,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼袭艺!你這毒婦竟也來了搀崭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤猾编,失蹤者是張志新(化名)和其女友劉穎瘤睹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體答倡,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡轰传,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瘪撇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片获茬。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖设江,靈堂內(nèi)的尸體忽然破棺而出锦茁,到底是詐尸還是另有隱情,我是刑警寧澤叉存,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布码俩,位于F島的核電站,受9級特大地震影響歼捏,放射性物質(zhì)發(fā)生泄漏稿存。R本人自食惡果不足惜笨篷,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瓣履。 院中可真熱鬧率翅,春花似錦、人聲如沸袖迎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽燕锥。三九已至辜贵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間归形,已是汗流浹背托慨。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留暇榴,地道東北人厚棵。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像蔼紧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子歉井,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

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