两个概念:
降级:如果调用的远端服务出现问题(超时或异常),则返回一个结果,用于提示。(不管远端服务如何每次都会调用)
熔断:如果调用的远端服务出现问题,则在一段时间之内直接返回提示信息(不再调远端的服务),一段时间后陆续调用远端服务,如果不再出现问题,则恢复正常调用远端服务。(远端服务出问题,暂时不再调用,过段时间再试)
本文项目基于: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的接口实现一个类
步骤:
- 修改application.yml 启用 Feign 对 Hystrix的支持。
- 实现接口 ProductApi 的一个实现类。
- 为接口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的监控
有两个坑:
新建一个监控端的项目: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”按钮开始监控
可以访问下服务,看监控的图形了。
完毕。