此案例是两个微服务工程的相互调用一个provider一个consumer 因此需要引入相关配置 依赖、yml
provider yml和pom
server:
port: 8001 #服务端 端口号
spring:
application:
name: cloud-provider-hystrix-payment #服务应用名
eureka:
client:
register-with-eureka: true #把当前服务注册到注册中心 值为true
fetch-registry: true #表示自己就是注册中心 需要去维护服务的实例 并且检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka
# 用于服务降级 在注解@FeignClient中添加fallbackFactory属性值
feign:
hystrix:
enabled: true #在Feign中开启Hystrix
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--执行器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包 up这里的工程只是实体类 -->
<groupId>com.zjl</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--springboot的热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok实体注解-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
consumer yml和pom
server:
port: 80 #端口号
eureka:
client:
register-with-eureka: false
service-url: #向其它注册中心关联本机微服务
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
# 用于服务降级 在注解@FeignClient中添加fallbackFactory属性值
feign:
hystrix:
enabled: true #在Feign中开启Hystrix
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,up这里引入的是自己的实体包工程 -->
<dependency>
<groupId>com.zjl</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--执行器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
在provider方主启动类上加入注解
@EnableCircuitBreaker //开启断路器功能
业务层代码
paymentInfo_Ok()这个方法是可以正常访问的方法
paymentInfo_TimeOut()这个方法是会出现异常的方法 并对其进行降级 可以看到在这个方法中设置最大的连接时间为5s 但是在这个方法中我们通过TimeUtil.Second.Sleep这个方法设置了睡眠状态为6秒 这样的话 就导致连接不成功 页面报错 但是我们在@HystrixCommad这个注解中 看到了一个参数fallbakMethod = "paymentInfo_TimeOutHandler"这个值就是我们备份的方法 什么是备份的方法? 就是当我们的这个paymentInfo_TimeOut()这个方法发生异常时 比如 高并发 网络卡顿等现象 不能让这个请求连接一直在等待状态 给它一个提示的信息 paymentInfo_TimeOutHandler这个方法就是给它的提示信息 我们可以看到 这个方法 返回的是一个字符串 以此来达到服务的降级目的
@Override //正常访问的方法
public String paymentInfo_OK(Integer id) {
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_Ok,id:"+id+"\t"+"O(∩_∩)O哈哈~";
}
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
})
@Override /*
@HystrixCommand:
controller中调用此方法 并且 这个方法出现了高并发的情况导致其他微服务访问不进来 就会对这个服务进行降级(@HystrixCommand)
这个注解中的fallbackMethod属性中指定的这个字符串 就是如果这个方法出现问题导致其他的微服务访问不到这个端口就会走fallbackMethod
这个属性中的方法 以免 另一个微服务一直进行等待或者报错的状态
并且在@HystrixProperty注解中设置了他最大的响应时间为5s 超过了这个时间就会执行
paymentInfo_TimeOutHandler这个方法
*/
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 6;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id:"+id+"\t"+"O(∩_∩)O哈哈~"+"耗时:"+timeNumber+"s";
}
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+"系统繁忙请稍后再试:"+id+"\t"+"┭┮﹏┭┮";
}
}
Controller层中的代码
@RestController
@Slf4j
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_Ok(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("***result:***"+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("***result:***"+result);
return result;
}
}
代码效果演示 此时我们可以看到这个是ok的这个方法 是可以正常访问的
再来看一下timeout这个方法 此时我们可以看到 它走的是paymentInfo_TimeOutHandler这个方法中的内容 因为我们业务层实现类中设置的最大连接时间为5s 但是方法体中 线程睡眠时间设置的是6s所以没有继续进行等待 而去走了备份好的方法 这样就达到了服务的降级效果
那么 服务的消费方(consumer)如何实现?
在主启动类中加入注解
@EnableHystrix //启动熔断降级服务
看一下业务层接口 没有什么不同 只是Feign对方法的负载均衡操作 再看一下controller
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_Ok(@PathVariable("id")Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
我们可以看到consumer中依旧是对timeout进行了一个降级的操作 但是这次不同 出现的并不是连接超时的异常 而是除零异常 此时provider中业务层接口的实现类中睡眠时间已经修改为3所以不会出现连接问题 主要是来测试出现异常的情况下是否还会继续走备份的方法
@RestController
@Slf4j
public class OrderHystrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_Ok(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_Ok(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
int age = 10/0;
System.out.println(age);
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费端80,系统繁忙请稍后再试或者自己运行出错请检查自己,┭┮﹏┭┮";
}
代码效果演示 可以看到 这样依然可以走备份好的方法
从上述的代码中我们可以发现两个问题第一个就是代码膨胀 因为我们每写好一个方法就要写一个备份的方法 那么在真实的项目中 假如有100个方法 那么就要进行100次备份 这样导致代码量增加写起来也非常的麻烦 那么我们可以对这样的操作 在controller中写一个全局的备份方法 具体实现
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")//设置全局备份方法
public class OrderHystrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_Ok(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_Ok(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
//因为使用了全局通用的备份方法所以注释掉这段代码 如果部分方法可能出现异常需要走备份的方法
//要加上@HystrixCommad注解
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
int age = 10/0;
System.out.println(age);
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费端80,系统繁忙请10秒钟后再试或者自己运行出错请检查自己,┭┮﹏┭┮";
}
//下面是全局fallback
public String payment_Global_FallbackMethod(){
return "Gloabl 异常处理信息,请稍后再试,/(ToT)/~~";
}
}
与上部分代码的不同之处就是在类上多了这样的一个注解@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") 这个注解就是全局备份的方法 让所有出现问题的方法都去执行这个备份的方法 但是 如果有部分方法因为一些条件因素不能走这个通用的备份方法 那么继续在需要走特定备份的方法上加入@HystrixCommand(fallbackMethod = "指定备份的方法")这个注解就可以了
第二个问题 在以上部分代码中我们可以看到 业务逻辑与服务降级的代码都混合在一起了 这样代码的耦合度就会变的非常高 如何解决 看以下代码
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentHystrixServiceImpl.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_Ok(@PathVariable("id")Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
相对于之前的service接口中可以发现多了一个fallback的属性 这个fallback赋值的就是对应的实现类
@Service
public class PaymentHystrixServiceImpl implements PaymentHystrixService {
@Override
public String paymentInfo_Ok(Integer id) {
return "---PaymentHystrixServiceImpl fallbak paymentInfo_Ok /(ㄒoㄒ)/~~---";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "---PaymentHystrixServiceImpl fallbak paymentInfo_TimeOut /(ㄒoㄒ)/~~---";
}
}
如果提供方和消费方还有注册中心都在运行的状态 通过浏览器来访问消费方中的方法地址可以看到这样的结果 是正常运行的
但是如果提供方突然宕机了 那么访问地址走的是哪里的方法 可以看到 现在我们开启的微服务只有一个注册中心和一个80端口(消费方)
那么再次访问刚刚访问过的地址 看一下结果
可以看到此时它走的是业务层中实现类中return返回的信息 因为提供方已经宕机了 目前存在的只有消费方和一个注册中心service接口中@FeignClient(value中定义的服务名已经是访问不到的了)但是通过实现类 打印了对应方法返回的信息 同时也解决了代码耦合度的问题
如有不足或问题 欢迎补充~~~