springboot+Hystrix+eureka+Openfeign实现分布式生产者消费者直接的调用以及解决服务降级、服务熔断、服务限流等问题(1)
Hystrix是什么
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,
Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合
预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会
被长时间、不必要地占用,从而避免了故障在分布式系统
Hystrix能干什么:
服务降级、服务熔断、接近实时的监控、服务限流等
Hystrix重要概念:
1.服务降级:服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback
哪些情况下会出现服务降级:程序出现异常时、超时、服务熔断触发服务降级、线程池/信号量打满也会导致服务降级
2.服务熔断:类似于保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
过程如下:服务的降级->进而熔断->恢复调用链路(当访问量能够接收时再次启动)
3.服务限流:就像是下班时间地铁站安检时一次只能够进入多少个乘客
在现实项目中我们知道如果一个方法的调用时间过长或者方法运行错误可能会导致我们等待过长的时间或者是直接返回一个不友好的页面错误,那么这个时候我们就应该想一个办法,当超时、运行异常、宕机时我们以及做一个服务降级找一个兜底的方法作为返回。比如我们调用一个查询方法,当出现三种情况中的一种时我们应该利用Hystrix的服务降级去运行一个兜底的方法返回一个结果,就好比返回一个系统繁忙让我们不必长时间等待。
下面就是一个简单的Hystrix系统服务降级的案例
有几种实现方式:
1.我们为每一个方法都配置一个兜底方法。缺点就是代码过于冗余
2.我们为除了有特殊需要的方法配置一个全局的兜底方法。因为兜底返回的内容都差不多
3.我们利用Openfeign做一个类似于全局的兜底方法,这个主要是在Openfeign的一个注解上进行的。
接下来我就对上面三种方式都列举一个小的案例,就不访问数据库啦,直接从service开始,数据库访问大家可以试试都是一样的只不过service层直接返回一个数据而已。
这里说明以下Hystrix在生产者端(服务端)和消费者端(客户端)都可以使用,这个只是看大家的业务逻辑来定的,因为在一个项目中消费者也可能是其他消费者的生产者。
1.第一种过去冗余的做法
新建生产者项目的服务降级
pom.xml
<dependencies>
<!--新增hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.at.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency> <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>
yml文件
#端口号
server:
port: 8001
#数据库链接设置
spring:
application:
# 应用名称
name: cloud-provider-hystrix-payment
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册消息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#集群版
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
#单机版
#defaultZone: http://localhost:7001/eureka/
instance:
instance-id: hystrixpayment8001
prefer-ip-address: true #访问路径可以显示ip
#Eureka客户端向服务端发送心跳的实际间隔,单位为秒(默认为30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端收到最后一次心跳后等待时间上线,单位为秒(默认为90秒) 超时将剔除服务
lease-expiration-duration-in-seconds: 2
启动类@EnableCircuitBreaker需要放这个注解来激活Hystrix的服务降级
@SpringBootApplication
//eureka的激活注解
@EnableEurekaClient
//Hystrix服务降级注解
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
service类
public interface PaymentService {
public String paymentInfo_OK(Integer id);
public String paymentInfo_TimeOut(Integer id);
}
serviceImpl类@HystrixCommand(fallbackMethod=“paymentInfo_TimeOutHandler”,commandProperties ={@HystrixProperty(name=“execution.isolation.thread.timeoutInMilliseconds”,value = “2000”)})添加这些就是在做服务的降级处理,fallbackMethod是表示用那个类作为兜底的类后面的就是运行等待的最大时间
@Service
public class PaymentServiceImpl implements PaymentService {
@Override
public String paymentInfo_OK(Integer id) {
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"哈哈哈" ;
}
//2秒钟以内就是正常的业务逻辑
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")})
@Override
public String paymentInfo_TimeOut(Integer id) {
//因为Hystrix上设置的最长为两秒,下面的程序是3秒所以会出错
int timeNumber = 3;
try { TimeUnit.SECONDS.sleep(timeNumber);
}catch (Exception e) {e.printStackTrace();
}
//下面意思是故意运行出错
//int a=10/0;
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut程序运行成功,id:"+id;
}
//下面这个方法就是一个兜底的方法,当配置了 @HystrixCommand的方法出现超时或者运行失败时就会运行下面的兜底方法
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+" 业务超时或者运行出错,id: "+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;
}
}
新建消费者的冗余的服务降级
原理都是一样的只不过消费者启动类上的注解和生产者的不一样
pom.xml文件
<dependencies>
<!--新增hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.at.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<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>
yml文件
server:
port: 80
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册消息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#集群版
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
#单机版
#defaultZone: http://localhost:7001/eureka/
instance:
instance-id: consumer80
prefer-ip-address: true #访问路径可以显示ip
#Eureka客户端向服务端发送心跳的实际间隔,单位为秒(默认为30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端收到最后一次心跳后等待时间上线,单位为秒(默认为90秒) 超时将剔除服务
lease-expiration-duration-in-seconds: 2
spring:
application:
# 应用名称
name: cloud-provider-hystrix-order
ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000
启动类
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
Openfeign调用接口类
@Component
@FeignClient("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);
}
controller类 @HystrixCommand注解中的内容是在配置兜底类和超时时间
如果是运行失败或者宕机则与超时时间无关直接走兜底类,兜底类的参数列表必须和运行方法的参数列表一致才行否则报错
@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);
log.info("*******result:"+result);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties ={
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000") })
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
log.info("*******result:"+result);
return result;
}
//兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}
}
生产者项目也可以作为消费者项目,消费者项目也可以作为生产者项目,只需要将生产者该有的注解添加上,把消费者该有的注解添加上即可。
下面是第二种用在全局的兜底方法,这个只能是一些统一参数格式的兜底方法
这个我就只在消费者端添加啦,生产者端除了启动类上端注解不同其余都相同
pom文件和上面消费者的一样
只是需要修改yml和controller这两个文件
yml文件需要添加一个Hystrix全局的过期时间这样才能让全局服务降级有最大过期时间
server:
port: 80
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册消息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#集群版
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
#单机版
#defaultZone: http://localhost:7001/eureka/
instance:
instance-id: consumer80
prefer-ip-address: true #访问路径可以显示ip
#Eureka客户端向服务端发送心跳的实际间隔,单位为秒(默认为30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端收到最后一次心跳后等待时间上线,单位为秒(默认为90秒) 超时将剔除服务
lease-expiration-duration-in-seconds: 2
spring:
application:
# 应用名称
name: cloud-provider-hystrix-order
ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000
#下面是新添加hystrix的全局过期时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
controller类
主要是添加了一个@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
然后在需要验证的方法上只需要添加注解@HystrixCommand注解内不需要再加任何的内容
然后添加一个全局兜底的方法,这个方法和每个特殊兜底方法不同的是不需要和验证方法的参数列表一致,就是不需要添加任何的参数列表
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //全局的
public class OrderHystrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@Autowired
Payment8001service payment8001service;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
log.info("*******result:"+result);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties ={
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000") })
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
log.info("*******result:"+result);
return result;
}
//兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}
//全局兜底
public String payment_Global_FallbackMethod(){
return "我是消费者80,我是全局兜底对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}
@GetMapping("/consumer/payment/hystrix/all")
public String shiyan(){
return payment8001service.shiyan();
}
}
第三中方法主要是用到Openfeign的注解和接口需要和Openfeign进行联合使用
需要新建一个类用于实现Openfeign调用第三方的那个接口,在实现类中写兜底方法,然后只需要在Openfeign接口的@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
配置上那个类是接口的实现类即可
然后就在yml文件中写feign和Hystrix的全局时间即可
当然前提是我们将第一种和第二种服务降级都注释掉避免冲突
新建一个类并实现Openfeign的接口
这里面的实现类都是生产者的方法,直接就是兜底方法
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "paymentInfo_OK运行异常";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "paymentInfo_TimeOut运行异常";
}
}
yml文件
server:
port: 80
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册消息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
#集群版
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
#单机版
#defaultZone: http://localhost:7001/eureka/
instance:
instance-id: consumer80
prefer-ip-address: true #访问路径可以显示ip
#Eureka客户端向服务端发送心跳的实际间隔,单位为秒(默认为30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端收到最后一次心跳后等待时间上线,单位为秒(默认为90秒) 超时将剔除服务
lease-expiration-duration-in-seconds: 2
spring:
application:
# 应用名称
name: cloud-provider-hystrix-order
ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000
# #下面是新添加hystrix的全局过期时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
feign:
hystrix:
enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
controller类只是将原来的两种降级的方法注释掉了,这里也贴出来方便看
@RestController
@Slf4j
//@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //全局的
public class OrderHystrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@Autowired
Payment8001service payment8001service;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
log.info("*******result:"+result);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties ={
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000") })
// @HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
log.info("*******result:"+result);
return result;
}
//兜底方法
// public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
// return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
// }
//全局兜底
// public String payment_Global_FallbackMethod(){
// return "我是消费者80,我是全局兜底对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
// }
@GetMapping("/consumer/payment/hystrix/all")
public String shiyan(){
return payment8001service.shiyan();
}
}
到此第一部分的服务降级到此告一段落后面将继续介绍其他内容。