微服务很多的情况下,调用链也会特别长。其中某个服务出现问题,很可能会导致整个微服务不可用。这种情况下,就需要将出现问题的服务隔离开来,就好比现在发现哪里有新冠疫情,就得赶紧隔离,防止传播给其他人。
上图中,日志服务调用分析服务,当分析服务不可用时,采取熔断。分析服务可以提供一个备用的机制如返回一个空对象,这就是服务的降级。
提供容错机制,避免微服务系统雪崩。
一、手动模拟服务异常
还是采用前面几章节的案例,假设用户服务调用订单服务出现异常,这里我手动模拟一个空指针异常,然后使用hystrix的熔断和降级。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
@RestController
public class OrderController implements OrderControllerApi {
@Value("${server.port}")
private String port;
// 当该方法出现异常不可用时,触发熔断,走降级方法。
@HystrixCommand(fallbackMethod = "findOrderListFallBack")
@Override
public JSONArray findOrderList() throws Exception {
// 1.手动触发异常
int a = 9 / 0;
JSONArray orderList = new JSONArray();
JSONObject o1 = new JSONObject();
o1.put("ordNo", "001");
o1.put("ordName", "鼠标");
JSONObject o2 = new JSONObject();
o2.put("ordNo", "002");
o2.put("ordName", "键盘");
JSONObject o3 = new JSONObject();
o3.put("port", port);
System.out.println("port=" + port);
orderList.add(o1);
orderList.add(o2);
orderList.add(o3);
return orderList;
}
/**
* 订单服务降级方法
*
* @return
* @throws Exception
*/
public JSONArray findOrderListFallBack() throws Exception {
System.out.println("进入降级方法findOrderListFallBack...");
JSONArray orderList = new JSONArray();
JSONObject o3 = new JSONObject();
o3.put("port", port);
System.out.println("port=" + port);
orderList.add(o3);
return orderList;
}
@Autowired
public String hello() {
return "hello order!!!";
}
}
3.订单服务启动类ApplicationOrder.java开启熔断机制。
@SpringBootApplication
@EnableEurekaClient // 开启eureka client 注册到server中
@EnableCircuitBreaker // 开启Hystrix的熔断机制
public class ApplicationOrder {
public static void main(String[] args) {
SpringApplication.run(ApplicationOrder.class, args);
}
}
三、模拟调用超时异常
@RestController
public class OrderController implements OrderControllerApi {
@Value("${server.port}")
private String port;
// 当该方法出现异常不可用时,触发熔断,走降级方法。
@HystrixCommand(fallbackMethod = "findOrderListFallBack")
@Override
public JSONArray findOrderList() throws Exception {
// 1.手动触发异常
// int a = 9 / 0;
// 2.调用超时
Thread.sleep(6000);
JSONArray orderList = new JSONArray();
JSONObject o1 = new JSONObject();
o1.put("ordNo", "001");
o1.put("ordName", "鼠标");
JSONObject o2 = new JSONObject();
o2.put("ordNo", "002");
o2.put("ordName", "键盘");
JSONObject o3 = new JSONObject();
o3.put("port", port);
System.out.println("port=" + port);
orderList.add(o1);
orderList.add(o2);
orderList.add(o3);
return orderList;
}
/**
* 订单服务降级方法
*
* @return
* @throws Exception
*/
public JSONArray findOrderListFallBack() throws Exception {
System.out.println("进入降级方法findOrderListFallBack...");
JSONArray orderList = new JSONArray();
JSONObject o3 = new JSONObject();
o3.put("port", port);
System.out.println("port=" + port);
orderList.add(o3);
return orderList;
}
@Autowired
public String hello() {
return "hello order!!!";
}
}
############################################################
#
# 订单微服务
# web访问端口号 约定:8002
#
############################################################
server:
# 动态设置端口号,方便部署集群
port: ${port:8002}
tomcat:
uri-encoding: UTF-8
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: order
############################################################
#
# eureka配置信息
#
############################################################
eureka:
server:
hostname: localhost
port: 7000
client:
# 所有的微服务都必须注册到eureka中
register-with-eureka: true
# 从注册中心获得检索服务实例,用户服务需要配置为true
# 用户服务要去获得其他服务的实例,然后去调用
fetch-registry: true
# 注册中心的服务地址
service-url:
# defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
# 订单服务注册到eureka集群中
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
instance:
lease-renewal-interval-in-seconds: 3 # 调整微服务(eureka-client)和注册中心(eureka-server)之间的心跳时间
lease-expiration-duration-in-seconds: 5 # eureka距离最近的一次心跳等待剔除的时间(假设是3s发送一次心跳,但是某次3s后eureka没有收到心跳,则距离上次心跳5s后,eureka会剔除该节点)默认90s,当前设置为5s
# 配置hystrix
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 # 设置hystrix超时时间,超过2秒触发降级
其余配置同“手动模拟服务异常”步骤1、3、4、5。
hystrix超时时间设置为2s,但是方法里sleep了6s,所以超过了2s,触发服务降级。
二、模拟服务端直接挂掉了
由于服务端直接挂掉了,所以没法在服务端降级了。客户端访问后永远都是报错500。这时候,就需要采用客户端的降级策略,具体的看下面客户端降级的介绍。
上面的OrderController对方法findOrderList指定了降级方法,假设还有100个方法都需要做同样的降级,那么每个方法都需要配置一遍。可以统一配置全局的降级方法,如果某个方法没有指定特定的降级方法,那么就采用默认的全局降级方法。
@RestController
// 配置全局降级方法
@DefaultProperties(defaultFallback = "defaultFallback")
public class OrderController implements OrderControllerApi {
// 当该方法出现异常不可用时,触发熔断,走降级方法。
@HystrixCommand
@Override
public JSONArray findOrderList() throws Exception {
// 1.手动触发异常
// int a = 9 / 0;
// 2.调用超时
Thread.sleep(5000);
JSONArray orderList = new JSONArray();
JSONObject o1 = new JSONObject();
o1.put("ordNo", "001");
o1.put("ordName", "鼠标");
JSONObject o2 = new JSONObject();
o2.put("ordNo", "002");
o2.put("ordName", "键盘");
JSONObject o3 = new JSONObject();
o3.put("port", port);
System.out.println("port=" + port);
orderList.add(o1);
orderList.add(o2);
orderList.add(o3);
return orderList;
}
/**
* 订单服务全局降级方法
*
* @return
* @throws Exception
*/
public JSONArray defaultFallback() throws Exception {
System.out.println("进入全局降级方法defaultFallback...");
JSONArray orderList = new JSONArray();
JSONObject o3 = new JSONObject();
o3.put("status", 500);
o3.put("msg", "系统繁忙,请稍后再试");
orderList.add(o3);
return orderList;
}
@Autowired
public String hello() {
return "hello order!!!";
}
}
用户服务(客户端)调用订单服务(服务端),如果订单服务发生了异常,则可以直接走服务端降级。但是,如果订单服务直接挂掉了,则服务不可用,这时候就需要走客户端降级。
@RestController
public class UserController implements UserControllerApi {
@Autowired
private OrderControllerApi orderControllerApi;
@HystrixCommand(fallbackMethod = "findOrderListFallback", commandProperties =
{@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
value = "1500")})
//name:属性名 value:属性值(当控制器运行时间超过该值时,将会进入降级方法)
@Override
public JSONArray findOrderList() throws Exception {
// 发起调用订单服务
JSONArray result = orderControllerApi.findOrderList();
return result;
}
/**
* 客户端降级方法
*
* @return
* @throws Exception
*/
public JSONArray findOrderListFallback() throws Exception {
JSONArray orderList = new JSONArray();
JSONObject o3 = new JSONObject();
o3.put("status", 500);
o3.put("msg", "系统繁忙,请稍后再试");
o3.put("des", "客户端降级");
orderList.add(o3);
return orderList;
}
@Override
public String hello() {
return "hello user!!!";
}
}
# 配置feign
feign:
client:
config:
# 配置服务提供方的名称
order:
loggerLevel: FULL
hystrix:
enabled: true # 打开feign客户端的内置hystrix
@SpringBootApplication
@EnableEurekaClient // 开启eureka client 注册到server中
@EnableFeignClients({"com.ft"}) // 开启feign,别标明调用的api在哪个包下
@EnableHystrix // 开启hystrix
public class ApplicationUser {
public static void main(String[] args) {
SpringApplication.run(ApplicationUser.class, args);
}
}
4.postman测试,首先开启用户服务和订单服务,发起调用,由于超时会触发服务端降级。
此时,断掉订单服务,发起调用,由于订单服务500,触发客户端降级。