使用Hystrix实现服务容错

Hystrix

Hystrix是运行在客户端的,通过一定的手段,规避在不同但互相关联的服务调用中出现的故障和过分的延迟响应,从而防止发生服务雪崩。

Hystrix实现容错的手段

  • 服务降级
  • 服务熔断
  • 请求合并
  • 线程池隔离
  • 信号量隔离

SpringCloud集成Hystrix

  1. 导入相关依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.10.RELEASE</version>
</dependency>

注意,因为Fegin中包含了Hystrix,如果使用了Fegin客户端请求框架,只需要在配置文件中设置feign.hystrix.enabled=true开启Hystrix即可。

  1. 在启动类上添加@EnableHystrix注解

服务降级

所谓服务降级就是说当服务迟迟没有响应,不会继续等待,而是会执行一个兜底的方法避免请求的阻塞。一定程度上降低了故障的发生。

实现步骤

在客户端的Service层创建一个方法并为其添加@HystrixCommand注解

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

其中fallbackMethod属性为这个rest请求指定了一个失败的回调方法error()

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

紧接着在Controller层创建一个接口,并调用刚才的业务方法helloService()

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

为了测试需要,将myservicehello接口做一些小小的修改:在方法中让他沉睡1000ms(这是因为Hystrix的默认超时时间为1000ms,达到了这个时间就会进行降级)

@GetMapping("/hello")
public String index() throws InterruptedException {
    client.getServices().forEach(System.out::println);
    Thread.sleep(1000);
    return "Hello world";
}

现在,启动注册中心、客户端、服务端,开始测试

服务降级测试-1.png

测试达到预期结果!

服务熔断

服务熔断跟服务降级很相似,不同的地方在于熔断表示会断开与服务的连接,不过这种断开一般是暂时的,后续还会尝试与服务进行连接,如果还是不行则继续保持熔断状态,反复如此。

实现步骤

在客户端的Service层创建一个方法并为其添加@HystrixCommand注解,并为其设置一些参数

@HystrixCommand(fallbackMethod = "error",commandProperties = {
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500"),
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "30")
})
public String helloService(){
    return restTemplate.getForEntity("http://myservice/hello",String.class).getBody();
}

其中circuitBreaker.requestVolumeThreshold表示请求数量、execution.isolation.thread.timeoutInMilliseconds表示请求超时时间、circuitBreaker.errorThresholdPercentage表示请求失败数所占请求数量的百分比。综合来看,大意就是:请求数量达到了2,且失败的请求数占其30%,则会熔断当前请求。

同样为了便于测试,将myservicehello接口做一些小小的修改:设置其睡眠时间为2000ms

@GetMapping("/hello")
public String index() throws InterruptedException {
    System.out.println(client.getServices().get(0)+"已收到请求");
    Thread.sleep(2000);
    return "Hello world";
}

测试,请求两次后的结果如下
熔断请求1-1.png

发现服务端只接受到了一次请求,这是因为在第二次请求时由于失败的请求比例已经大于等于阈值,Hystrix已经对服务做了熔断。但正如之前所述,熔断不是永久的,默认5000ms后会暂时解除熔断如果请求仍失败,则再次熔断。

5000ms后再次发起请求
熔断请求2-1.png

再次接收到请求

请求合并

将多个请求合并为一个请求进行服务的访问,能一定程度上减少网络开销

实现步骤

在客户端的Service层创建一个方法并为其添加@HystrixCollapser注解,并对其属性进行设置。需要注意的是,这个方法中的代码不会被执行

@HystrixCollapser(batchMethod = "mybatch",scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,collapserProperties = {
        @HystrixProperty(name = "maxRequestsInBatch",value = "200"),
        @HystrixProperty(name = "timerDelayInMilliseconds",value = "10"),
})
public Future<String> helloService2(String name){
    System.out.println("是否被执行?");
    return null;
}

其中,batchMethod属性表示执行合并后请求的方法;maxRequestsInBatch表示批处理最大请求数,就是说最大能合并多少条请求;timerDelayInMilliseconds指执行合并操作的毫秒数。需要注意的是方法的返回类型必须Future<T>类型。

创建一个mybatch方法,并挂上@HystrixCommand注解,这个方法中的参数、返回值是一个集合类型。这是因为多个参数被合并为一个集合,多个返回值也被合并为一个集合的原因

@HystrixCommand
public List<String> mybatch(List<String> names){
    List<String> list = restTemplate.postForObject("http://myservice/hello2",names,List.class);
    return list;
}

在Controller中新建一个接口,调用两次helloService2()方法,意为将这两个请求合并为一个请求

@RequestMapping(value = "/consumer2")
public String helloConsumer2() throws ExecutionException, InterruptedException {
    Future<String> result1 = helloService.helloService2("张三");
    Future<String> result2 = helloService.helloService2("李四");
    System.out.println(result1.get());
    System.out.println(result2.get());
    return "OK";
}

而在服务端,仍然要返回合并了多条数据的集合类型

@PostMapping("/hello2")
public List<String> index2(@RequestBody List<String> names) throws InterruptedException {
    System.out.println("已经接收到批处理请求");
    List<String> list = new ArrayList<>();
    list.add(names.get(0));
    list.add(names.get(1));
    return list;
}

打开浏览器访问/consumer2接口

批处理-1.png

服务端只接收到了一次请求,所以测试成功。

线程池隔离

默认情况下,web程序需要依赖tomcat的内置线程池发起服务的调用,这就导致程序中所有的请求都是使用同一个线程池,如果程序中存在一个请求占用的线程比较多,就会间接影响到其他的请求调用。通过为不同请求指定Hystrix的线程池可以实现线程池的隔离,使不同请求互不影响。

实现步骤

在客户端的Service层创建一个方法并为其添加@HystrixCommand注解,并对其属性进行设置。

@HystrixCommand(groupKey = "apple",threadPoolKey = "apple",threadPoolProperties = {
        @HystrixProperty(name = "coreSize",value = "4"),
})
public String thread1(){
    System.out.println(Thread.currentThread().getName());
    return "thread1";
}

public String thread2(){
    System.out.println(Thread.currentThread().getName());
    return "thread2";
}

其中groupKey表示分组名称、threadPoolKey表示线程池名称,HystrixProperty则用来指定线程数量。上面的示例为thread1指定了Hystrix线程池,thread2则使用tomcat线程池,从而实现请求之间互不影响。

在Controller中添加一个接口,分别调用thread1thread2

@RequestMapping("/thread")
public String threadPool(){
    helloService.thread1();
    helloService.thread2();
    return "OK";
}

打开浏览器访问/thread接口
线程池隔离-1.png

结果显示,thread1thread2分别使用了不同的线程池,不会互相影响。

信号量隔离

可以将信号量理解为线程计数器,假设1s内最多允许2条线程访问请求,第3条线程则会调用fallbackMethod指定的兜底方法。所以说信号量隔离实现了服务的限流。

实现步骤

在客户端的Service层创建一个方法并为其添加@HystrixCommand注解,并对其属性进行设置,并指定兜底方法

@HystrixCommand(commandProperties = {
        @HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE"),
        @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "2")
},fallbackMethod = "down")
public String semaphore() throws InterruptedException {
    System.out.println("OK");
    Thread.sleep(900);
    return null;
}

其中,execution.isolation.strategy指定使用信号量的方式实现服务的降级(默认使用线程池),execution.isolation.semaphore.maxConcurrentRequests则表示1s内最多只能有2个线程访问服务。

创建一个降级方法

public String down(){
    System.out.println("执行降级");
    return null;
}

在Controller中添加一个新的接口

@RequestMapping("/semaphore")
public String semaphore() throws InterruptedException {
    helloService.semaphore();
    return "OK";
}

使用apache-jmeter工具进行请求测试。因为设置了信号量最多为2,所以这里指定3个请求线程
jmeter1-1.png

设置请求地址并发起请求
jmeter2-1.png
jmeter3-1.png

控制台打印信息:
semaphore-1.png

由于1s内只允许2个线程请求服务,所以当第3个发起请求会被降级处理。

谢谢大家

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值