📢📢📢📣📣📣
哈喽!大家好,我是【一心同学】,一位上进心十足的【Java领域博主】!😜😜😜
✨【一心同学】的写作风格:喜欢用【通俗易懂】的文笔去讲解每一个知识点,而不喜欢用【高大上】的官方陈述。
✨【一心同学】博客的领域是【面向后端技术】的学习,未来会持续更新更多的【后端技术】以及【学习心得】。
✨如果有对【后端技术】感兴趣的【小可爱】,欢迎关注【一心同学】💞💞💞
❤️❤️❤️感谢各位大可爱小可爱!❤️❤️❤️
目录
4.2.3 配置springcloud-eureka-7001
4.2.4 配置springcloud-provider-hystrix-8001
4.2.6 配置springcloud-consumer-blog-feign-80
前言
微服务架构应用的特点就是多服务,而服务层之间通过网络进行通信,从而支撑起整个应用系统,所以,各个微服务之间不可避免的存在耦合依赖关系。但任何的服务应用实例都不可能永远的健康或网络不可能永远的都相安无事,所以一旦某个服务或局部业务发生了故障,会导致系统的不可用,我们知道当故障累积到一定程度就会造成系统层面的灾害,也就是级联故障,也叫雪崩效应,所以微服务需要在故障累计到上限之前阻止或疏通这些故障以保证系统的稳固安全,而Hystrix的出现就可以解决这个问题!
一、雪崩效应
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。
二、什么是Hystrix?
2.1 介绍
Hystrix是一个可以解决雪崩效应,可用于处理分布式系统的延迟和容错开源库,能够保证在一个依赖出问题的情况下,不会导致整个体系服务失败,避免级联故障,从而提高分布式系统的弹性。
2.2 理解
我们可以把Hystrix当成是一个“断路器”,当我们某个服务单元发生故障之后,通过断路器的故障监控 (类似熔断保险丝) ,向调用方返回一个服务预期的,可处理的备选响应 (FallBack) ,而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
例如,我们客户A执行了一个错误的操作B,而这个错误的操作B会导致客户A进入到一个漫长甚至无法响应的境地,而我们Hystrix的作用就是,但客户A执行了错误的操作B时,我们直接返回一个友好提示,而不是让客户一直在那里等待。
三、Hystrix能干吗?
- 服务降级
- 服务熔断
- 服务限流
- 接近实时的监控
四、服务熔断
4.1 介绍
熔断机制是赌赢雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阀值缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是:@HystrixCommand。
4.2 搭建服务熔断
4.2.1 建立一个父工程Maven
编写pom.xml:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springCloud的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringBoot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.12.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
4.2.2 建立子工程
注:同样是Maven。
职责:
springcloud-api:存放实体类和需要调用的接口(service)
springcloud-eureka-7001:服务注册中心
springcloud-consumer-blog-feign-80:服务消费方
springcloud-provider-hystrix-8001:服务生产方
4.2.3 配置springcloud-eureka-7001
(1)建立以下目录
(2)导入依赖
<!--导包~-->
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<!--导入Eureka Server依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--热部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
(3)编写配置文件
application.yml:
server:
port: 7001
# Eureka配置
eureka:
instance:
# Eureka服务端的实例名字
hostname: localhost
client:
# 表示是否向 Eureka 注册中心注册自己(这个模块本身是服务器,所以不需要)
register-with-eureka: false
# fetch-registry如果为false,则表示自己为注册中心,客户端的化为 ture
fetch-registry: false
# Eureka监控页面~
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
(4)编写启动类EurekaServer_7001
package com.yixin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class, args);
}
}
4.2.4 配置springcloud-provider-hystrix-8001
注意:我们就是在该项目即服务的生产方使用熔断服务。
(1)建立以下目录
(2)导入依赖 导入Hystrix依赖。
<!--导包~-->
<dependencies>
<!--导入Hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<!--导入Eureka Server依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--Spring Boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
<!--热部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
(3)编写配置文件
application.yml:
server:
port: 8001
spring:
application:
name: springcloud-provider-blog
# Eureka配置:配置服务注册中心地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: springcloud-provider-dept-8001 #修改Eureka上的默认描述信息
(4)编写BlogController
package com.yixin.springcloud.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
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.RestController;
@RestController
public class BlogController {
//表示db这个数据库
@Value("dbsource")
private String dbsource;
//如果根据id查询出现异常,则走hystrixGet这段备选代码
@HystrixCommand(fallbackMethod = "hystrixGet")
@GetMapping("/blog/info/{id}")
public String getInfo(@PathVariable("id") Integer id) {
if (id == 0) {//id为0就会抛出异常
throw new RuntimeException("这个id=>" + id + ",不可以为0");
}
return dbsource + id;
}
public String hystrixGet(@PathVariable("id") Integer id) {
return dbsource + "的id不可以为" + id;
}
}
(5)编写启动类BlogProvider_8001
注意:为主启动类添加对熔断的支持注解@EnableCircuitBreaker。
package com.yixin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker // 添加对熔断的支持注解
public class BlogProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(BlogProvider_8001.class, args);
}
}
4.2.5 配置springcloud-api
(1)建立以下目录
(2)导入依赖
<dependencies>
<!--Feign的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
(3)编写BlogClientService
package com.yixin.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
//value就是我们服务生产方在注册中心的服务名
@FeignClient(value = "SPRINGCLOUD-PROVIDER-BLOG")
public interface BlogClientService {
@GetMapping("/blog/info/{id}")
public String getInfo(@PathVariable("id") Integer id);
}
4.2.6 配置springcloud-consumer-blog-feign-80
(1)建立以下目录
(2)导入依赖
<!--导包~-->
<dependencies>
<!--Feign的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--导入springcloud-api,这样我们才能使用到刚刚在该子项目中编写的类-->
<dependency>
<groupId>com.yixin</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<!--导入Eureka依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--Spring Boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
<!--热部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
(3)编写配置文件
server:
port: 8000
spring:
application:
name: springcloud-consumer-blog
# Eureka配置:配置服务注册中心地址
eureka:
client:
register-with-eureka: false # 不向 Eureka注册自己
service-url:
defaultZone: http://localhost:7001/eureka/
(4)编写BlogConsumerController
package com.yixin.springcloud.controller;
import com.yixin.service.BlogClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BlogConsumerController {
//这个就是我们springcloud-api下的接口
@Autowired
private BlogClientService blogClientService;
@GetMapping("/consumer/blog/{id}")
public String get(@PathVariable("id") Integer id){
return "消费端:"+blogClientService.getInfo(id);
}
}
(5)编写启动类BlogConsumer_feign_80
package com.yixin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
// feign客户端注解,并指定要扫描的包以及配置接口BlogClientService
@EnableFeignClients(basePackages = {"com.yixin.service"})
public class BlogConsumer_feign_80 {
public static void main(String[] args) {
SpringApplication.run(BlogConsumer_feign_80.class,args);
}
}
至此,我们的熔断服务就搭建好了。
4.2.7 测试
依次启动:
a、springcloud-eureka-7001:服务注册中心
b、springcloud-provider-hystrix-8001:服务生产方
c、springcloud-consumer-blog-feign-80:服务消费方
访问:http://localhost:7001/
(1)如果出现以下界面,则说明成功绑定我们的服务生产方了。
(2)接下来我们来进行服务消费方的测试。
规则:不可输入id=0的请求。
我们先正常访问:http://localhost:8000/consumer/blog/1
(3)如果我们进行不正常访问,看下效果。
非正常访问:http://localhost:8000/consumer/blog/0
可以发现,它给予了我们友好的提示,而这个提示就是我们用熔断机制写的! 如果不写熔断机制,那么其输出会变成如下图,非常不友好!
因此,为了避免因某个微服务后台出现异常或错误而导致整个应用或网页报错,使用熔断是必要的!
五、服务降级
5.1 介绍
服务降级就是当服务器压力剧增的情况下,整体资源快不够了,忍痛将某些服务单元先关掉,关闭后还要返回一些可处理的备选方法,待渡过难关,再开启回来,就是尽可能的把系统资源让给优先级高的服务。
5.2 为什么要服务降级?
资源有限,而请求是无限的。如果在并发高峰期,不做服务降级处理,一方面肯定会影响整体服务的性能,严重的话可能会导致宕机某些重要的服务不可用。所以,一般在高峰期,为了保证核心功能服务的可用性,都要对某些服务降级处理。比如当双11活动时,把交易无关的服务统统降级,如查看蚂蚁深林,查看历史订单等等。
如下图可知,当某一时间内服务A的访问量暴增,而B和C的访问量较少,为了缓解A服务的压力,这时候需要B和C暂时关闭一些服务功能,去承担A的部分服务,从而为A分担压力,叫做服务降级。
5.3 服务降级需要考虑的问题
- 那些服务是核心服务,哪些服务是非核心服务
- 那些服务可以支持降级,那些服务不能支持降级,降级策略是什么
- 除服务降级之外是否存在更复杂的业务放通场景,策略是什么?
5.4 自动降级分类
(1)超时降级:主要配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况。
(2)失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况。
(3)故障降级:比如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)。
(4)限流降级:秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)。
5.5 搭建服务降级
我们只需修改我们的以上的项目即可实现。
步骤一:增加一个没有熔断机制的springcloud-provider-blog-8001
(1)导入依赖
<!--导包~-->
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<!--导入Eureka Server依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--Spring Boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
<!--热部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
(2)编写配置类
server:
port: 8001
spring:
application:
name: springcloud-provider-blog
# Eureka配置:配置服务注册中心地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: springcloud-provider-dept-8001 #修改Eureka上的默认描述信息
(3)编写BlogController
package com.yixin.springcloud.controller;
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.RestController;
@RestController
public class BlogController {
//表示dbsource这个数据库
@Value("dbsource")
private String dbsource;
//注册进来的微服务,获取一些消息
@GetMapping("/blog/info/{id}")
public String getInfo(@PathVariable("id") Integer id){
//id不可以为0
if(id==0){
throw new RuntimeException("这个id=>"+id+",不可以为0");
}
return dbsource+id;
}
}
(4)编写启动类
package com.yixin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class BlogProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(BlogProvider_8001.class,args);
}
}
步骤二:修改springcloud-api
(1)在springcloud-api模块下的service包中新建降级配置类BlogClientServiceFallBackFactory.java
package com.yixin.service;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
//服务降级
@Component
public class BlogClientServiceFallBackFactory implements FallbackFactory {
@Override
public BlogClientService create(Throwable throwable) {
return new BlogClientService() {
public String getInfo(Integer id) {
return "dbsource的id不可以为:" + id + "或该服务已经关闭!";
}
};
}
}
b、在BlogClientService指定降级配置类BlogClientServiceFallBackFactory
package com.yixin.service;
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;
@Component //注册到spring容器中
//value就是我们服务生产方在注册中心的服务名
@FeignClient(value = "SPRINGCLOUD-PROVIDER-BLOG",fallbackFactory = BlogClientServiceFallBackFactory.class)
public interface BlogClientService {
@GetMapping("/blog/info/{id}")
public String getInfo(@PathVariable("id") Integer id);
}
步骤三:修改BlogConsumer_feign_80
(1)启动类增加@ComponentScan("com.yixin")注解,为了扫描到我们本项目中的controller层
package com.yixin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableEurekaClient
// feign客户端注解,并指定要扫描的包以及配置接口BlogClientService
@EnableFeignClients(basePackages = {"com.yixin.service"})
@ComponentScan("com.yixin")
public class BlogConsumer_feign_80 {
public static void main(String[] args) {
SpringApplication.run(BlogConsumer_feign_80.class, args);
}
}
(2)配置文件中开启服务降级。
server:
port: 8000
spring:
application:
name: springcloud-consumer-blog
# Eureka配置:配置服务注册中心地址
eureka:
client:
register-with-eureka: false # 不向 Eureka注册自己
service-url:
defaultZone: http://localhost:7001/eureka/
# 开启降级feign.hystrix
feign:
hystrix:
enabled: true
至此,服务降级就搭建完成了!
步骤四:测试
依次启动:
a、springcloud-eureka-7001:服务注册中心
b、springcloud-provider-blog-8001:服务生产方
c、springcloud-consumer-blog-feign-80:服务消费方
(1)访问:http://localhost:7001/
出现以下界面,说明我们的服务生产方成功绑定了Eureka。
(2)正常访问:http://localhost:8000/consumer/blog/1
(3)非正常访问:http://localhost:8000/consumer/blog/0
(4)关闭服务生产方,springcloud-provider-blog-8001
再次正常访问:http://localhost:8000/consumer/blog/1
至此!测试成功!!!
六、 服务熔断和降级的区别
- 服务熔断—>服务端:某个服务超时或异常,引起熔断~,类似于保险丝(自我熔断)
- 服务降级—>客户端:从整体网站请求负载考虑,当某个服务熔断或者关闭之后,服务将不再被调用,此时在客户端,我们可以准备一个 FallBackFactory ,返回一个默认的值(缺省值)。会导致整体的服务下降,但是好歹能用,比直接挂掉强。
- 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
- 实现方式不太一样,服务降级具有代码侵入性(由控制器完成/或自动降级),熔断一般称为自我熔断。
七、 熔断,降级,限流
限流:限制并发的请求访问量,超过阈值则拒绝;
降级:服务分优先级,牺牲非核心服务(不可用),保证核心服务稳定;从整体负荷考虑;
熔断:依赖的下游服务故障触发熔断,避免引发本系统崩溃;系统自动执行和恢复
小结
以上就是【一心同学】对Hystrix组件中的【服务熔断】和【服务降级】的讲解,说到底,这两个功能就是为了防止因错误的操作或服务器的关闭从而给用户带来不友好的使用,而且能够保证在一个依赖出问题的情况下,不会导致整个体系服务失败,大家看到这里,可以再重新返回上面看【一心同学】对Hystrix的概念讲解,相信会收获更多!
如果这篇【文章】有帮助到你,希望可以给【一心同学】点个赞👍,创作不易,相比官方的陈述,我更喜欢用【通俗易懂】的文笔去讲解每一个知识点,如果有对【后端技术】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【一心同学】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💕💕!