文章目录
- 前言
- 一、服务雪崩
- 1.服务雪崩概述
- 2. 造成服务雪崩的原因
- 3. 如何防止雪崩
- 二、Spring Cloud Hystrix
- 1.什么是Spring Cloud Hystrix(豪猪哥)
- 2.搭建测试环境
- (1)创建cloud-provider-hystrix-payment8003支付服务
- (2)创建cloud-consumer-feign-hystrix-order订单服务
- (3)jmeter压力测试
- 3.服务降级实例
- (1)支付服务做服务降级
- (2)@EnableHystrix 注解
- (3)全局服务降级DefaultProperties
- (4)通配服务降级FeignFallback
- 4.服务熔断实例
- (1)服务熔断原理剖析
- (2)服务熔断实例
- 5.Hystrix工作流程
- 6.服务监控
- (1)Hystrix图形化Dashboard搭建
- (2)图形化Dashboard监控实战
前言
在微服务架构中,一个系统往往是由多个服务组成,这些服务之间相互依赖。
假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
提示:以下是本篇文章正文内容,下面案例可供参考
一、服务雪崩
1.服务雪崩概述
在微服务之间进行服务调用是由于某一个服务故障,导致级联服务故障的现象,称为雪崩效应。
雪崩效应描述的是提供方不可用,导致消费方不可用并将不可用逐渐放大的过程。
2. 造成服务雪崩的原因
- 服务提供者不可用
a)硬件故障:硬件损坏造成的服务器主机宕机, 网络硬件故障造成的服务提供者的不可访问
b)程序Bug:
c) 缓存击穿:缓存击穿一般发生在缓存应用重启, 所有缓存被清空时,以及短时间内大量缓存失效时. 大量的缓存不命中, 使请求直击后端,造成服务提供者超负荷运行,引起服务不可用
d)用户大量请求:在秒杀和大促开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用
- 重试加大流量
a)用户重试:在服务提供者不可用后, 用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单
b)代码逻辑重试: 服务调用端的会存在大量服务异常后的重试逻辑
- 服务调用者不可用
a)同步等待造成的资源耗尽:当服务调用者使用同步调用 时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了。
3. 如何防止雪崩
使用Spring Cloud Hystrix进行服务熔断、降级。
然而Hystrix已经停更,进入了维护模式,Hystrix官方推荐的替代产品:Resilience4J
但我们需要知道Hystrix是干嘛,怎么用的,它的思想
二、Spring Cloud Hystrix
1.什么是Spring Cloud Hystrix(豪猪哥)
Spring Cloud Hystrix 是基于 Netflix 公司的开源组件 Hystrix 实现的,
它提供了熔断器功能,能够有效地阻止分布式微服务系统中出现联动故障,以提高微服务系统的弹性。
Spring Cloud Hystrix 具有服务降级、服务熔断、线程隔离、请求缓存、请求合并以及实时故障监控等强大功能。
Hystrix 的使用文档:https://github.com/Netflix/Hystrix/wiki/How-To-Use
- 服务熔断
熔断机制是应对雪崩效应的⼀种微服务链路保护机制。当扇出链路的某个微服务不可⽤或者响应时间太⻓时,熔断该节点微服务的调⽤,进⾏服务的降级,快速返回错误的响应信息。当检测到该节点微服务调⽤响应正常后,恢复调⽤链路。【通常与服务降级一起使用】
- 服务降级
服务降级是从系统整体考虑,当某个服务熔断之后,服务器不再被调⽤时,客户端可以为发送的请求准备⼀个本地的fallback回调,返回⼀个与方法返回值类型相同的缺省值,这样做,虽然服务水平下降,但整体仍然可用,比直接熔断要好。
- 服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
2.搭建测试环境
(1)创建cloud-provider-hystrix-payment8003支付服务
改pom
在原有支付服务的依赖下,添加hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
添加yml
server:
port: 8003
#服务名称
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
启动类
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8003 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8003.class,args);
}
}
业务类
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id){
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}
public String paymentInfo_TimeOut(Integer id)
{
try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): 3";
}
}
controller类
@RestController
@Slf4j
public class PaymentController {
@Resource
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;
}
}
(2)创建cloud-consumer-feign-hystrix-order订单服务
改pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
添加yml
server:
port: 80
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: false
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka
启动类
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
业务类
@Component
@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);
}
controller类
@RestController
@Slf4j
public class OrderHystirxController {
@Resource
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}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
(3)jmeter压力测试
压测40000并发,访问支付模块
此时80调用8003,发现订单模块访问变慢。
原因:8003同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕
解决方案:使用服务降级
- 超时导致服务器变慢(转圈) - 超时不再等待
- 出错(宕机或程序运行出错) - 出错要有兜底
3.服务降级实例
为了测试比较清晰,因此故意制造两种异常
- 制造超时异常
- int age = 10/0,计算异常
(1)支付服务做服务降级
更改支付服务业务类
主启动类添加@EnableCircuitBreaker注解来激活Hystrix的功能
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //此处添加
public class PaymentHystrixMain8003 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8003.class,args);
}
}
修改代码造成计算异常
测试:两种异常都是跳转到了兜底的方法上
(2)@EnableHystrix 注解
@EnableHystrix注解它继承了@EnableCircuitBreaker,并对它进行了在封装。
这两个注解都是激活hystrix的功能,
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix {
}
(3)全局服务降级DefaultProperties
每个方法上配置一个服务降级方法。太麻烦,只需要对核心业务有专属
其他普通的可以进行全局降级
@Service
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class PaymentService {
@HystrixCommand
public String paymentInfo_TimeOut(Integer id)
{ int age=10/0;
return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): 3";
}
public String payment_Global_FallbackMethod(){
return "全局异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
}
(4)通配服务降级FeignFallback
降级方法与业务方法写在了一块,耦合性太高
修改order模块,这里开始,pay模块就不服务降级了,服务降级写在order模块即可
创建PaymentHystrixService接口实现类
@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出现异常";
}
}
让PayService的实现类生效:
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",
fallback = PaymentFallbackService.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);
}
配置文件中开启hystrix
feign:
hystrix:
enabled: true
测试:支付服务报错,首先会运行支付服务中该方法的兜底方法
当我们关掉支付服务后,重新发起请求
可以看到,并没有报500错误,而是降级访问实现类的同名方法
这样,即使服务器挂了,用户要不要一直等待,或者报错
4.服务熔断实例
(1)服务熔断原理剖析
服务熔断:就类似于保险丝
熔断这个概念由martin fowler 提出的,在他的博客中对熔断的原理进行了概述
简单来说,断路器有三种状态熔断打开、熔断关闭、熔断半开
正常调用时,断路器是关闭的。
当出现高并发等情况,导致某个服务瘫痪,此时断路器打开,服务发生熔断,会进行服务的降级,快速返回错误的信息。
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果能够正常运行,则恢复正常链路,此时断路器关闭
(2)服务熔断实例
修改cloud-hystrix-pay8003模块
- 修改业务类
@HystrixCommand(fallbackMethod = "payment_Hander",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失败率达到多少后跳闸
})
public String paymentInfo_TimeOut(Integer id)
{ if(id<0){
throw new RuntimeException("id不能为负数");
}
//try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): 3";
}
public String payment_Hander(Integer id){
return "线程池: "+Thread.currentThread().getName()+" 8003系统繁忙或者运行报错,请稍后再试,id: "+id;
}
- 测试
正确 - http://localhost:8001/payment/circuit/1
错误 - http://localhost:8001/payment/circuit/-1
多次错误,再来次正确,但错误得显示
HystrixCommandProperties 这个类规定了HystrixCommand注解下的HystrixProperty属性里的值。
5.Hystrix工作流程
官网的工作流程:
步骤说明:
- 创建HystrixCommand (用在依赖的服务返回单个操作结果的时候)或HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候)对象。
- 若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。
- 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(第8步);如果断路器是关闭的,检查是否有可用资源来执行命令(第5步)。
- 线程池/请求队列信号量是否占满。如果命令依赖服务的专有线程地和请求队列,或者信号量(不使用线程的时候)已经被占满,那么Hystrix也不会执行命令,而是转接到fallback处理理辑(第8步) 。
- Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。
HystrixCommand.run():返回一个单一的结果,或者抛出异常。
HystrixObservableCommand.construct():返回一个Observable对象来发射多个结果,或通过onError发送错误通知。 - Hystix会将“成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行"熔断/短路"。
- 当命令执行失败的时候,Hystix会进入fallback尝试回退处理,我们通常也称波操作为“服务降级”。
- 当Hystrix命令执行成功之后,它会将处理结果直接返回或是以Observable的形式返回。
6.服务监控
(1)Hystrix图形化Dashboard搭建
创建模块:cloud-consumer-hystrix-dashboard-9001
改pom:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</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: 9001
启动类 @EnableHystrixDashboard注解
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}
测试:浏览器输入:http://localhost:9001/hystrix
出现这个则配置成功
(2)图形化Dashboard监控实战
对监控的服务主启动类添加以下内容
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix //此处添加
public class PaymentHystrixMain8003 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8003.class, args);
}
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
* 只要在自己的项目里配置上下面的servlet就可以了
* 否则,Unable to connect to Command Metric Stream 404
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
监控测试:
启动注册中心、9003、9001服务
填写监控地址 http://localhost:8003/hystrix.stream 到 http://localhost:9001/hystrix页面的输入框
监控说明: