Hystrix (容错保护)
一.分析
二.雪崩效应
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。
三.Hystrix简介
四.原理说明
当对特定服务的呼叫达到一定阈值时(Hystrix中的默认值为5秒内的20次故障),电路打开,不进行通讯。并且是一个隔离的线程中进行的。
快速入门
在 microservice-order系统中增加Hystrix容错(本教程中order微服务工程主要扮演消费者的角色)。
五.导入pom坐标
<!--整合hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
5.1.修改ItemService的queryItemById方法
在要做容错处理的方法上加@HystrixCommand注解,并指定一个容错方法,即fallbackMethod 。
package com.mr.order.service.impl;
import com.mr.entity.Item;
import com.mr.properties.OrderProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class ItemService {
// Spring框架对RESTful方式的http请求做了封装,来简化操作
@Autowired
private RestTemplate restTemplate;
@Autowired
OrderProperties orderProperties;
/**
* 进行容错处理
* fallbackMethod的方法参数个数类型要和原方法一致
*
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
public Item queryItemById3(Long id) {
// 该方法走eureka注册中心调用(去注册中心根据app-item查找服务,这种方式必须先开启负载均衡@LoadBalanced)
String itemUrl = "http://app-item/item/{id}";
Item result = restTemplate.getForObject(itemUrl, Item.class, id);
/*System.out.println("订单系统调用商品服务,result:" + result);*/
System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
return result;
}
public Item queryItemByIdFallbackMethod(Long id) {
return new Item(id, "查询商品信息出错!", null, null, null);
}
}
5.2在启动类OrderApplication添加@EnableHystrix注解
package com.mr;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* @author Evan
*/
@SpringBootApplication//申明这是一个Spring Boot项目
@EnableEurekaClient
@EnableHystrix
@ComponentScan(basePackages = {"com.mr.order.controller", "com.mr.order.service.impl","com.mr.properties"})//手动指定bean扫描范围
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
/**
* 向Spring容器中定义RestTemplate对象
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
5.3在Controller增加一个入口
package com.mr.order.controller;
import com.mr.entity.Order;
import com.mr.order.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private IOrderService orderService;
@GetMapping(value = "order/{orderId}")
public Order queryOrderById(@PathVariable("orderId") String orderId) {
return this.orderService.queryOrderById(orderId);
}
@GetMapping(value = "order2/{orderId}")
public Order queryOrderById2(@PathVariable("orderId") String orderId) {
return this.orderService.queryOrderByIdx(orderId);
}
}
OrderService中增加:
package com.mr.order.service.impl;
import com.mr.entity.Item;
import com.mr.entity.Order;
import com.mr.entity.OrderDetail;
import com.mr.order.service.IOrderService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Service("orderService")
public class OrderService implements IOrderService {
private static final Map<String, Order> ORDER_DATA = new HashMap<String, Order>();
static {
// 模拟数据库,构造测试数据
Order order = new Order();
order.setOrderId("201910300001");
order.setCreateDate(new Date());
order.setUpdateDate(order.getCreateDate());
order.setUserId(1L);
List<OrderDetail> orderDetails = new ArrayList<OrderDetail>();
Item item = new Item();// 此处并没有商品的数据,只是保存了商品ID,需要调用商品微服务获取
item.setId(1L);
orderDetails.add(new OrderDetail(order.getOrderId(), item));
item = new Item(); // 构造第二个商品数据
item.setId(2L);
orderDetails.add(new OrderDetail(order.getOrderId(), item));
order.setOrderDetails(orderDetails);
ORDER_DATA.put(order.getOrderId(), order);
}
@Autowired
private ItemService itemService;
/**
* 根据订单id查询订单数据
*
* @param orderId
* @return
*/
public Order queryOrderById(String orderId) {
Order order = ORDER_DATA.get(orderId);
if (null == order) {
return null;
}
List<OrderDetail> orderDetails = order.getOrderDetails();
for (OrderDetail orderDetail : orderDetails) {
// 通过商品微服务查询商品详细数据
Item item = this.itemService.queryItemById3(orderDetail.getItem()
.getId());
if (null == item) {
continue;
}
orderDetail.setItem(item);
}
return order;
}
public Order queryOrderByIdx(String orderId) {
Order order = ORDER_DATA.get(orderId);
if (null == order) {
return null;
}
List<OrderDetail> orderDetails = order.getOrderDetails();
for (OrderDetail orderDetail : orderDetails) {
// 通过商品微服务查询商品详细数据
Item item = this.itemService.queryItemById3(orderDetail.getItem()
.getId());
if (null == item) {
continue;
}
orderDetail.setItem(item);
}
return order;
}
public String errorCode(){
return "系统异常,请联系管理官!";
}
}
5.4重新启动进行测试
http://localhost:8102/order/201910300001
测试一切正常。可以发现加了@HystrixCommand注解的方法和普通方法不是共用的线程池,是隔离的:
http://localhost:8102/order2/201910300001
接下来,我们把商品服务停止进行测试:
可以看到,使用了hystrix容错机制时订单服务(app-order)正常,尽管查询商品服务(app-item)已停止服务,查询到的是错误信息。
由此可见,商品服务的宕机并没有影响订单服务的正常工作,起到的容错效果。
如果调用没有做Hytrix容错的方法,则直接返回异常信息:
定义统一的fallback接口
如果在每个方法里都定义对应的fallback方法,显得太臃肿,实际项目中可以单独使用一个类定义各种fallback,在使用feign客户端时使用统一fallback接口。