使用Hystrix实现降级和熔断,以及监控

两个概念:

降级:如果调用的远端服务出现问题(超时或异常),则返回一个结果,用于提示。(不管远端服务如何每次都会调用)

熔断:如果调用的远端服务出现问题,则在一段时间之内直接返回提示信息(不再调远端的服务),一段时间后陆续调用远端服务,如果不再出现问题,则恢复正常调用远端服务。(远端服务出问题,暂时不再调用,过段时间再试)

本文项目基于:https://blog.csdn.net/nece001/article/details/106807322

 

实现降级和熔断的步骤:先实现降级,再实现熔断

因为要调服务的操作是发生在消费者这一方,所以在消费者的项目里增加降级和熔断的处理。(服务提供者也可以)

准备工作:

1.首先要引入Hystrix的jar包:

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

2.启动类增加注解:@EnableCircuitBreaker

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableCircuitBreaker
public class Consumer8203Application {

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

}

实现一个简单的降级的体验一下:

给控制器增加方法,并添加好注解:

package com.example.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class IndexController {

    @Resource
    RestTemplate restTemplate;

    @Autowired
    ProductApi productApi;

    @Value("${server.port}")
    String port;

    @GetMapping("/a")
    public String index() {

        // 直接使用注册中心,服务提供项目的项目名称去请求就可以了
        String url = "http://PRODUCT-SERVICE/index";
        String result = restTemplate.getForObject(url, String.class);
        return result + port;
    }

    @GetMapping("/feign")
    public String feign() {
        return productApi.index() + port;
    }

    /**
     * 模拟远程调用返回超时的情况
     * @return String
     * @throws InterruptedException 
     */
    @GetMapping("/delay")
    @HystrixCommand(defaultFallback = "delayFallback", commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") // 设置超时时间为3秒,超过3秒的由降级方法返回提示
    })
    public String delay() throws InterruptedException {
        float s = 5.1F;  // 这个时间肯定会被降级(即:返回的内容是delayFallback的返回值),可以改小点看结果。
        Thread.sleep((long)(s * 1000));
        return "延迟" + s + "秒, 访问正常";
    }

    /**
     * 降级方法
     * @return String
     */
    public String delayFallback() {
        return "执行超时了,被降级...";
    }
}

启动后访问,可以看到降级的效果。

延迟的时间改短些则正常: 

这就是所谓的“降级”的效果。

增加有服务提供方参与的场景:

这里有三个项目:分别是注册中心(8201),服务提供者(8202),服务消费者(8203)

服务提供者中增加一个时间较长的方法,模拟耗时很长的方法:

package com.example.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {

    // 注入本项目的端口号
    @Value("${server.port}")
    private String port;

    // 提供一个接口用于提供服务
    @GetMapping("/index")
    public String index() {
        return "index : " + port;
    }

    @GetMapping("/sleep")
    public String sleep() throws InterruptedException {
        int s = 5;

        Thread.sleep(s * 1000);
        return "sleep: " + s + " (s)";
    }
}

先启动注册中心和服务提供者,消费者的控制器内增加一个方法,测试下效果:

    /**
     * 测试下用远端服务
     * @return String
     */
    @GetMapping("/sleep-delay")
    @HystrixCommand(defaultFallback = "delayFallback")
    public String sleepDelay(){
        return productApi.sleep() + port;
    }

这是用了一个通用的方法达到了降级的目的,注解@HystrixCommand除了defaultFallback这个参数可以调默认降级方法外,还有一个fallbackMethod可以调用一个与原方法对应的参数相同的方法,用于提供更明确的提示。

与原方法对应的参数相同的降级方法

控制器增加三个方法:

    // 感觉这个说法还算靠谱点
    // fallbackMethod: 设置HystrixCommand服务降级所使用的方法名称,注意该方法需要与原方法定义在同一个类中,并且方法签名也要一致
    // defaultFallback: 设置HystrixCommand默认的服务降级处理方法,如果同时设定了fallbackMethod,会优先使用fallbackMethod所指定的方法,需要注意的是defaultFallback该属性所指定的方法没有参数,需要注意返回值与原方法返回值的兼容性
    @GetMapping("/sleep/{id}")
    @HystrixCommand(fallbackMethod = "sleepFallbackMethod", defaultFallback = "sleepFallback") // 使用指定的降级方法
    public String sleep(@PathVariable("id") int id) {
        //int a = 10 / 0; // 触发抛出异常
        return productApi.sleep() + port + " id: " + id;
    }
    
    // 参数签名必须与原方法相同
    public String sleepFallbackMethod(int id) {
        return "sleep 方法被 控制器中指定的方法sleepFallbackMethod 降级,参数是:" + id;
    }

    // 无参数
    public String sleepFallback() {
        return "sleepFallback 控制器中方法降级..." + port;
    }

可以根据不同参数给出不同的结果。

给多个方法指定默认的降级方法

有可能多个方法降级的提示只需要返回相同的内容,这时可以给控制器类增加注解:@DefaultProperties(defaultFallback = "通用的降级方法名")

@DefaultProperties(defaultFallback = "sleepFallbackDefault")

 控制器内添加4个方法:需要有降级的方法只需添加注解@HystrixCommand,不需要指定降级方法名,会使用类上指定的默认降级方法。不添加注解的不会做降级处理。

    @GetMapping("/sleep2")
    @HystrixCommand // 使用默认的降级方法
    public String sleep2() {
        return productApi.sleep() + port;
    }

    @GetMapping("/sleep3")
    @HystrixCommand // 使用默认的降级方法
    public String sleep3() {
        return productApi.sleep() + port;
    }

    // 不做降级处理,会出异常:java.net.SocketTimeoutException: Read timed out
    @GetMapping("/sleep4")
    public String sleep4() {
        return productApi.sleep() + port;
    }
    
    // 无参数
    public String sleepFallbackDefault() {
        return "sleepFallbackDefault 控制器中方法降级,默认的降级方法..." + port;
    }

可以看到sleep4方法没有指定注解,方法没有降级。为了防止出现这种情况出现,可以为Feign定义的接口(ProductApi)提供一个实现类,来定义接口中各方法的降级方法。

为Feign的接口实现一个类

步骤:

  1. 修改application.yml 启用 Feign 对 Hystrix的支持。
  2. 实现接口 ProductApi 的一个实现类。
  3. 为接口ProductApi的注解@FeignClient增加参数fallback

feign:
  hystrix:
      
    # 开启 Feign对Hystrix的支持
    enabled: true

添加 ProductApi 接口的实现类 ProductApiHystrix.java,并注入容器

package com.example.api.hystrix;

import com.example.api.ProductApi;
import org.springframework.stereotype.Component;

@Component // 注入容器
public class ProductApiHystrix implements ProductApi{

    @Override
    public String index() {
        return "ProductApiHystrix.index 降级方法...";
    }

    @Override
    public String sleep() {
        return "ProductApiHystrix.sleep 降级方法...";
    }
    
}

 为接口ProductApi的注解@FeignClient增加参数fallback

@FeignClient(value = "PRODUCT-SERVICE", fallback = ProductApiHystrix.class)

 重启并访问,可以看到控制器的方法没有做改变的情况下,达到了降级的目的:

到此,降级已经完成。

实现熔断:

控制器中增加两个方法,模拟下接口出问题:

    // 可以造成熔断的方法
    @GetMapping("/sleep5/{id}")
    @HystrixCommand(defaultFallback = "sleepBreaker", commandProperties = {
        @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),  // 开启熔断器
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),  // 当请求达到这个数量之后,才进行错误占比的计算。
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),  // 半打开休眠时间,熔断之后过了这段休眠时间,就会半打开,尝试接口是否恢复,如果恢复就完全打开熔断器。
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")  // 错误占比,当错误次数超过这个百分比,就会熔断。
    }) // commandProperties 的整个意思就是10秒中10次请求中有60%的请求出问题就熔断
    public String sleep5(@PathVariable("id") int id) throws Exception {
        if (id < 0) {
            throw new Exception("id 是负数,抛出异常!!");
        }
        return "访问正常 id:" + id + "  port:" + port;
    }

    // 熔断时调用的方法
    public String sleepBreaker() {
        return "接口有问题,目前被熔断了...";
    }

正常情况:

问题情况:

多次使用-1为参数访问,再用1做为参数再访问,也不会得到正常结果了,就是被熔断了:

完毕。

 

Hystrix的监控

有两个坑:

  1. 被监控项目,需要在配置文件中设置监控端口的访问地址。具体的参考文章
  2. Spring Boot 2.1.15以上版本是用的 Hoxton.SR5,JS中有错误。具体的参考文章

新建一个监控端的项目:hystrix-dashboard-8204

监控端只是为了显示监控结果,是独立的项目,所以不需要与项目的版本相同,能监控就行。

需要用到的包:

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

修改配置文件,添加端口号:8204

启动类添加注解:@EnableHystrixDashboard

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboard8204Application {

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

}

启动并访问:http://localhost:8204/hystrix

到这一步,监控端就准备好了。

修改被监控端的配置:

修改项目consume-8203中的application.yml

spring:
  application:
    name: consume # 消费者的实例名称
    
server:
  port: 8203 # 消费者实例的端口号

eureka:
  instance:
    instance-id: consume-${server.port}
    prefer-ip-address: true
  client:
    serviceUrl:
      defaultZone: http://localhost:8201/eureka/ # 注册中心的服务地址,用于将消费者注册进去
      #defaultZone: http://localhost:8201/eureka/,http://localhost:8221/eureka/  # 注册中心的服务地址,用于将消费者注册进去

feign:
  hystrix:
      
    # 开启 Feign对Hystrix的支持
    enabled: true

# 需要改变ribbon轮询调用方式的服务名
PRODUCT-SERVICE:
  ribbon:
    # 改为随机的方式
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

management: 
  endpoints: 
    web: 
      exposure: 
        include: health,info,hystrix.stream #根据需求增删路径

 然后启动,访问:http://localhost:8203/actuator/hystrix.stream

可以看到有内容输出,表示准备就绪

没有异常的时候的:

访问下消费端,触发下降级,有降级数据的时候的:

使用hystrix-dashboard显示图表:

把被监控的服务地址填写好:http://localhost:8203/actuator/hystrix.stream,点击“Monitor Stream”按钮开始监控

可以访问下服务,看监控的图形了。

完毕。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值