在分布式系统里,一个服务依赖多个服务,可能存在某个服务调用失败,比如超时、异常等。如何保证一个依赖出问题的情况下,不会导致整体服务失败,通过 Hystrix 就可以有效的解决。
1.pom.xml 添加Hystrix依赖
<!-- Hystrix 依赖 -->
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2.在 order-service 服务的主程序入口添加 @EnableCircuitBreaker 注解,开启Hystrix断路由,当然也可以使用 @SpringCloudApplication 代替 @SpringBootApplication 和 @EnableCircuitBreaker
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
3.改造 order-service 订单服务的 controller 控制层接口
@RestController
@RequestMapping(value = "/api/v2/order")
public class OrderController {
@Autowired
private ProductOrderService productOrderService;
@RequestMapping(value = "save")
//指定出错后执行的方法
@HystrixCommand(fallbackMethod = "saveOrderFail")
public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId){
return productOrderService.save(userId, productId);
}
/**
* 出错执行该方法
* 注意这里的参数要与api方法save()参数一致
* @author: 药岩
* @Date: 2020/3/24 21:37
* @Description:
*/
public Object saveOrderFail(int userId, int productId){
Map<String, Object> map = new HashMap<>();
map.put("code", 500);
map.put("message", "当前下单人数过多,请稍后再试!");
map.put("data", new int[0]);
return map;
}
}
4.启动 eureka_server 项目和 order-service 项目,product-service 不启动。
浏览器访问:
http://localhost:8781/api/v2/order/save?user_id=1&product_id=3
feign:
hystrix:
enabled: true #开启Hystrix,旧版本默认开启,新版本默认关闭
// feign 客户端,指定商品服务名
@FeignClient(name = "product-service")
public interface ProductClient {
/**
* 这里指定调用的product-service 商品服务的URL接口
* @author
* @param id
* @return
*/
@GetMapping(value = "/api/v1/product/find")
String findById(@RequestParam(value = "id") int id);
}
可以看到有一个fallback,注释说,自定义一个class,然后这个class必须实现被@FeignClient注释标记的接口,这里也就是ProductClient ,并且被spring当成bean扫描。
创建一个包名为 fallback,编写一个ProductClientFallback并且实现ProductClient接口
/**
* 针对商品服务降级处理
* @author: 药岩
* @Date: 2020/3/24 22:58
* @Description:
*/
@Component
public class ProductClientFallback implements ProductClient {
@Override
public String findById(int id) {
System.out.println("feign结合Hystrix调用...");
return null;
}
}
在 feign 的客户端指定降级处理的fallback类
// feign 客户端,指定商品服务名
@FeignClient(name = "product-service", fallback = ProductClientFallback.class)
public interface ProductClient {
/**
* 这里指定调用的product-service 商品服务的URL接口
* @author
* @param id
* @return
*/
@GetMapping(value = "/api/v1/product/find")
String findById(@RequestParam(value = "id") int id);
}
启动 eureka-serve 注册中心和order-service服务,暂时不用启动 product-service。
访问浏览器:
http://localhost:8781/api/v2/order/save?user_id=1&product_id=3
控制台:
2020-03-25 20:50:41.997 INFO 10824 --- [oduct-service-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client product-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=product-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@55b80263
feign结合Hystrix调用...
2020-03-25 20:52:53.629 INFO 10824 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2020-03-25 20:57:53.633 INFO 10824 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
这里可以看到方法已经走了 Hystrix 的降级处理。
思路:
首先客户端访问了 order-service 的 /api/v2/order/save 接口,但该接口已经标记了@HystrixCommand(fallbackMethod = “saveOrderFail”) 注解,出错会交给saveOrderFail()方法处理,到了service层的 save() 方法,这里通过 feign 接口去调用了 product-service 服务的接口取数据,这个feign接口标记了@FeignClient(name = “product-service”, fallback = ProductClientFallback.class),如果 product-service 商品服务调不通无响应就会交给ProductClientFallback处理,但是 product-service 商品服务没有启动,所以 ProductClientFallback 处理了并在控制台打印了,最后将异常抛给了最外层的saveOrderFail()方法处理。
在熔断降级的时候是需要及时处理的,这样子才能保证线上的平稳运行,所以我们这边设计在项目出现问题时,设定一个短信通知来进行进行报警通知。
server:
port: 8781
spring:
application:
name: order-service
redis:
database: 0
host: 115.29.149.49
port: 6379
timeout: 1000
#指定注册中心
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
#自定义负载均衡策略
product-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
feign:
hystrix:
enabled: true #开启Hystrix,旧版本默认开启,新版本默认关闭
/**
* 出错执行该方法
* 注意这里的参数要与api方法save()参数一致
* @author: devin
* @Date: 2020/3/24 21:37
* @Description:
*/
public Object saveOrderFail(int userId, int productId){
String orderValue = redisTemplate.opsForValue().get("order-save");
new Thread(() -> {
if (StringUtils.isNotBlank(orderValue)){
System.out.println("已经发送过了,不再重复发送");
}else {
redisTemplate.opsForValue().set("order-save", UUID.randomUUID().toString(), 2, TimeUnit.SECONDS);
System.out.println("系统异常,下单失败,开始发送信息,2分钟后过期,请处理");
}
}).start();
Map<String, Object> map = new HashMap<>();
map.put("code", 500);
map.put("message", "当前下单人数过多,请稍后再试!");
map.put("data", new int[0]);
return map;
}
2020-03-30 22:06:26.415 INFO 5252 --- [ HystrixTimer-1] io.lettuce.core.KqueueProvider : Starting without optional kqueue library
系统异常,下单失败,开始发送信息,2分钟后过期,请处理
feign结合Hystrix调用...
已经发送过了,不再重复发送
feign结合Hystrix调用...
已经发送过了,不再重复发送
feign结合Hystrix调用...
系统异常,下单失败,开始发送信息,2分钟后过期,请处理
2020-03-30 22:10:29.881 INFO 5252 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
1.自定义 Hystrix 的超时时间
#设置超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
1.添加仪表盘依赖
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
org.springframework.boot
spring-boot-starter-actuator
2.启动类添加仪表盘注解
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
@EnableHystrixDashboard
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}