服务容错保护:Spring Cloud Hystrix

Spring Cloud Hystrix实现了断路器、线程隔离等一系列服务保护措施,它也是基于Netflix的开源框架Hystrix实现的,该框架的目标是通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。

快速入门

-在服务消费者工程的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);
  }
}

-改造服务消费方式,新增HelloService类,注入RestTemplate实例,然后,将在ConsumerController中对RestTemplate的使用迁移到HelloService函数中,最后在HelloService函数上增加@HystrixCommand注解来指定回调方法:

@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类,注入上面实现的HelloService实例,并在helloConsumer中进行调用

@RestController
public class ConsumerController {
  @Autowired
  HelloService helloService;

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

原理分析

工作流程

1、创建HystrixCommand或者HystrixObservableCommand对象
-HystrixCommand:用在依赖的服务返回单个操作结果的时候。
-HystrixObservableCommand:用在依赖的服务返回多个操作结果的时候。
2、命令执行
Hystrix共有四种命令的执行方式,HystrixCommand实现了两种执行方式,分别为:
-execute():同步执行,从依赖的服务返回一个单一的结果对象,或是在发生错误的时候抛出异常
-queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果的对象

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

HystrixObservableCommand实现了另外两种执行方式
-observe():返回Observable对象,它代表了操作的多个结果,它是一个HotObservable。
-toObservable():同样会返回Observable对象,也代表了操作的多个结果,但它返回的是一个Cold Observable。

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

3、结果是否被缓存
4、断路器是否打开
5、线程池/请求队列/信号量是否占满

依赖隔离

Hystrix使用“舱壁隔离”模式实现线程池的隔离,它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会陀满其他的依赖服务。

使用详解

Hystrix的核心注解是@HystrixCommand,通过它创建了HystrixCommand的实现,同时利用fallback属性指定了服务降级的实现方法。

创建请求命令

Hystraix命令就是HystrixCommand,用于封装具体的依赖服务调用逻辑。通过继承的方式来实现

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

-同步执行:User u = new UserCommand(restTemplate, 1L).execute();
-异步执行:Future<User> futureUser = new UserFuture(restTemplate,1L).queue();异步执行的时候,可以通过对返回的futureUser调用get方法来获得结果。
另外,也可以通过@HystrixCommand注解来实现Hystrix命令

public class UserService {
  @Autowired
  private RestTemplate restTemplate;

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

虽然@HystrixCommand注解可以定义Hystrix命令的实现,但是如上定义的getUserById方式只是同步执行的实现,若要实现异步执行则还需要另外定义

@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注解实现响应式命令时,可以通过observableExecutionMode参数来控制使用observe()还是toObservable()的执行方式。该参数有两种设置方式。
-@HystrixCommand(observableExecutionMode=ObservableExecutionMode.EAGER);EAGER是该参数的模式值,表示toObserve()执行方式
-@HystrixCommand(observableExecutionMode=ObservableExecutionMode.LAZY);表示使用toObservable()执行方式。

定义服务降级

fallback是Hystrix命令执行失败时使用的后备方法,用来实现服务的降级处理逻辑。在HystrixCommand中可以通过重载getFallback()方法来实现服务降级逻辑,Hystrix会在run()执行过程中出现错误、超时、线程池拒绝、断路器等情况时,执行getFallback()方法内的逻辑

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

使用注解实现降级服务只需要使用@HystrixCommand中的fallbackMethod参数来指定具体的服务降级实现方法

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

在使用注解定义服务降级逻辑时,我们需要将具体的Hystrix命令与fallback的实现函数定义在同一个类中,并且fallbackMethod的值必须与实现fallback方法的名字相同。由于必须定义在一个类中,所以对于fallback的访问修饰符没有特定的要求,定义为private、protected、public均可。

异常处理

异常传播

当HystrixCommand实现的run()方法抛出异常时,这些异常会被Hystrix认为是命令执行失败并触发服务降级的处理逻辑。
使用注解配置实现Hystrix命令时,它还支持忽略指定异常类型功能

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

异常处理

命令名称、分组以及线程池划分

以继承方式实现的Hystrix命令使用类名作为作为默认名称,我们也可以在构造函数中通过Setter静态类来设置

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

使用注解的时候设置命令名称、分组以及线程池

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

请求缓存

开启请求缓存的功能:在实现HystrixCommand或HystrixObservableCommand时,通过重载getCacheKey()方法来开启请求缓存。
清理失效缓存功能:在Hystrix中,通过HystrixRequestCache.clear()方法进行缓存清理。

工作原理

尝试获取请求:Hystrix命令在执行前会根据之前提到的isRequestCachingEnabled方法来判断当前命令是否启用了请求缓存。如果开启了请求缓存且重写了getCacheKey方法,并返回了一个非null的缓存Key值,那么就使用getCacheKey返回的Key值调用HystrixRequestCache的get(String cacheKey)获取缓存的HystrixCachedObservable对象。
将请求结果加入缓存
-设置请求缓存:添加@CacheResult注解,Hystrix会将结果加入请求缓存中,而它的缓存Key值会使用所有的参数

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

-定义缓存key:可以使用@CacheResult和@CacheRemove注解的cacheKeymethod方法来指定具体的生成函数;也可以通过使用@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注解实现方式更简单,但是它的优先级比cacheKeyMethod的优先级低,如果已经使用了cacheKeyMethod指定了缓存key的生成函数,那么@CacheKey注解将不会生效

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

@CacheKey注解除了可以指定方法参数作为缓存Key之外,它还允许访问参数对象的内部属性作为缓存Key

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

-缓存清理,通过@CacheRemove注解来实现失效缓存的清理

@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()的执行
-fallback配置,主要用于控制HystrixCommand.getFallback()的执行,这些属性同时适用于线程池的信号量的隔离策略。
-circuitBreaker配置,主要用于HystrixCircuitBreaker断路器的行为
-metrics配置,与HystrixCommand和HystrixObservableCommand执行中捕获的指标信息有关。
-requestContext配置,与HystrixCommand使用的HystrixRequestContext的设置。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容