服务降级,既可以配置在客户端,也可以配置在服务端,需要根据具体的业务需求,进行灵活配置。
本文模拟的情况,是在服务端80进行配置。
服务降级,是通过注解 @HystrixCommand 来实现的。
/**
* 模拟超时操作
* fallbackMethod:服务降级后的回调方法(超时异常or运行异常都会触发)
* commandProperties:
* isolation.thread.timeoutInMilliseconds:配置HystrixCommand执行的超时时间,单位为毫秒。
* 本类的配置:该方法如果执行超过三秒钟没有返回,将不再继续等待,转而执行paymentTimeOutHandler方法)
* @param id
* @return String
*/
@Override
@HystrixCommand(fallbackMethod = "paymentTimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentTimeOut(Integer id) {
int timeNumber = 5000;
//timeNumber = 10/0;
try {
TimeUnit.MILLISECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程名称: " + Thread.currentThread().getName() +
" ,方法名:paymentTimeOut, id: " + id + " ,超时了~ 耗时:" + timeNumber + "毫秒";
}
/**
* =========================== 服务降级 ===========================
* 服务降级的处理方法
* paymentTimeOutHandler与paymentTimeOut的参数列表要对应上,否则会报错
*/
public String paymentTimeOutHandler(Integer id) {
return "线程名称: " + Thread.currentThread().getName() +
" ,系统繁忙系统报错 ,paymentInfo_TimeOutHandler ,请稍后再试 ,id: " + id + "\t " + "o(╥﹏╥)o";
}
可以看到,凡是存在隐患的业务方法,都需要配置服务降级。
如果每个方法,都要配置服务降级,如果由100个方法,1000个方法呢。
因此,引申出一个问题——能不能针对业务,配置一个全局的fallback方法,由他统一管理服务降级?
全局服务降级配置,是通过在类上添加注解 @DefaultProperties 指定
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OpenFeignOrderController {
/**
* 模拟异常
* HystrixCommand注解没有配置属性,走全局fallback方法
*/
@GetMapping("/global/fallback")
@HystrixCommand
public String feignGlobalFallback(){
String result = null;
return result.toString();
}
/**
* 全局fallback 降级方法
*/
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试,o(╥﹏╥)o";
}
}
到了这一步,我们又发现了一个让人难受的地方,虽然每个业务都配置了全局的降级方法。
但是业务逻辑和服务降级,耦合在了一起。
代码非常的不优雅,如何解决?
package com.banana.springcloud.service;
import com.banana.springcloud.service.fallback.OpenFeignOrderFallbackService;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* FeignClient注解中的value属性指定了要调用的{服务}名
* @author layman
* @date 2021/1/18
* fallback为value指定的服务名,提供统一的服务降级处理类OpenFeignOrderFallbackService。
* 如果访问正常,不会走fallback指定的OpenFeignOrderFallbackService类,而是直接访问。
* 如果访问异常,需要服务降级,就会走fallback指定的OpenFeignOrderFallbackService。
* A:如果是feignSuccess方法出现异常,那么就会去OpenFeignOrderFallbackService.class找对应的feignSuccess方法,处理fallback(方法名需一一对应)
*/
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = OpenFeignOrderFallbackService.class)
public interface OpenFeignOrderService {
/**
* 模拟成功访问
*/
@GetMapping(value = "/payment/hystrix/success/{id}")
String feignSuccess(@PathVariable("id") Long id);
/**
* 模拟openFeign访问超时
*/
@GetMapping("/payment/hystrix/timeout/{id}")
String feignTimeout(@PathVariable("id") Long id);
}
服务熔断的定义
服务熔断的架构图:
代码演示(服务熔断的代码,放在服务端8001)
PaymentServiceImpl
/**
* =========================== 服务熔断 ===========================
* 服务的降级->进而熔断->恢复调用链路(会回复调用链路,而不是永久熔断)
* 本类的配置详解:如果在10秒钟的时间窗口期内,8次请求失败率达到60%,触发熔断,5秒钟(默认)后,进入半开状态,尝试放行请求
*/
@Override
@HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallback", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "8"), //请求次数,默认为20
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //时间窗口期,默认为10秒
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),//失败率达到多少后触发熔断,默认为50
})
public String paymentCircuitBreaker(Integer id) {
if (id < 0) {
throw new RuntimeException("******id 不能为负数");
}
//不带-的UUID
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber;
}
@HystrixCommand注解中的参数配置
都在com.netflix.hystrix.HystrixCommandProperties
这个类里
有兴趣的同学,可以去围观一下。
服务端8001进行自测。
PaymentController
package com.banana.springcloud.controller;
import com.banana.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author layman
* @date 2021/1/18
*/
@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
/**
* 测试服务熔断
* @param id
* @return
*/
@GetMapping("/hystrix/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
String result = paymentService.paymentCircuitBreaker(id);
log.info("------------result: " + result);
return result;
}
}
模拟的逻辑是,如果id是负数,抛出异常,如果是正数,则放行。
详情请参见:Alibaba的sentinel
cs