SpringCloud——Eureka Feign Ribbon Hystrix Zuul等关键组件的学习与记录
前言:本篇是对近日学习狂神SpringCloud教程之后的感想和总结,鉴于对SpringCloud体系的了解尚且处于初期阶段,在措辞、理解上难免会有偏颇,还请批评和指正!
目录一、概述
SpringCloud是一套微服务解决方案,在spring-cloud-dependencies Hoxton.SR8版本与spring-boot-dependencies 2.3.4.RELEASE版本的配合下,这一套解决方案提供了Eureka注册中心、Ribbon+RestTemplete或Feign的负载均衡方案、Hystrix熔断、Hystrix+Feign降级、HystrixDashboard服务监控以及Zuul路由网关。
不过,在spring-cloud-dependencies后续的版本中(如2020.x.x系列版本),上述提供的服务变化较大,尤其是Ribbon停止更新(IRule接口被移除)、Hystrix+Feign的变化使我们使用自定义的负载均衡算法、进行服务降级时造成了一些困难,这些部分我还在学习和摸索中,会在基本了解和掌握之后再进行记录,因而本篇依然基于稍早的spring-cloud-dependencies版本进行摘记。
(P.S. 尽管Hoxton版本现在依然处于支持中,不过按照NetFIix在新版本中的改动来看,也许这一套解决方案被弃用是迟早的事情)
二、项目的组成
1.项目的集成方法
SpringCloud项目由一个总的pom.xml负责管理项目中可能会用到的所有依赖,这样管理的作用是方便版本的统一。子模块在使用相同的依赖时,不必指定依赖的版本,因为这些版本已经在总的pom.xml中指定好了。
在一个项目总的pom.xml中,我们可以使用
例如,下方展示的是总的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springclouddemo</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>springcloud-api</module>
<module>springcloud-provider-dept-8001</module>
<module>springcloud-consumer-dept-80</module>
<module>springcloud-eureka-7001</module>
<module>springcloud-eureka-7002</module>
<module>springcloud-eureka-7003</module>
<module>springcloud-provider-dept-8002</module>
<module>springcloud-provider-dept-8003</module>
<module>springcloud-consumer-dept-feign</module>
<module>springcloud-provider-dept-hystrix-8001</module>
<module>springcloud-consumer-hystrix-dashboard</module>
<module>springcloud-zuul</module>
<module>springcloud-config-server-3344</module>
<module>spingcloud-config-client-3355</module>
<module>springcloud-config-eureka-7001</module>
<module>springcloud-config-dept-8001</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<junit.version>4.12</junit.version>
<druid.version>1.1.10</druid.version>
<lombok.version>1.18.22</lombok.version>
</properties>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
<!--日志测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.7</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
下方展示的是某一子模块对父pom.xml的继承和使用
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springclouddemo</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider-dept-hystrix-8001</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
<!--EUREKA提供-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>3.0.5</version>
</dependency>
<!-- actuator完善监控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--用于拿到实体类-->
<dependency>
<groupId>org.example</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
当然,如果我们没有在总的pom.xml中声明一些依赖管理,则这些依赖在子模块中导入时还是要写明版本编号的。
2.项目的各个部分
一个SpringCloud项目由四个部分组成,分别是服务的注册中心、服务的API、服务提供者和服务消费者。
- 注册中心负责服务提供者的注册和发现工作,方便服务消费者对服务进行使用,同时对提供的服务能够进行有效管理。简单来说,注册中心便是多个集群,当一个集群故障,依然可以保证其他集群运行,防止服务崩溃。举个例子,注册中心好比电商服务平台,服务提供者商家在平台上展示商品,服务消费者买家在平台上浏览商品。
- 服务API中提供服务所需要的实体类(及通常与数据库表相对应的部分),同时也提供一些通用的配置,比如在Feign的负载均衡使用时,消费者通过调用API的接口实现对服务的获取,这种配置方法避免了在消费者模块中重复配置接口,达到统一使用的目的。
- 服务提供者是传统项目中均存在的部分,例如在SpringBoot中,我们的项目一般包含Dao层、Service层、Controller层,从而进行数据库交互、服务逻辑处理和服务接口外显等工作,在SpringCloud中,服务提供者便继续承担这些工作,不同的是,服务提供者可能有多个提供相同服务的子模块,这是为了方便实现负载均衡所造成的。
- 服务消费者的目的是对服务提供者url进行隐蔽,外显的是消费者的url和端口,当前端调用服务时,只需要调用消费者便可以得到后台数据。在消费者端,可以采用不同的方法实现负载均衡,这将会在后续进行展开。
下面的部分将对SpringCloud提供的几大核心功能进行介绍,并结合项目源码进行展示。
三、注册与发现——Eureka
Eureka作为注册中心,提供平台以帮助服务提供者将服务进行外显。
首先,我们需要建立一个子项目springcloud-eureka-7001,并在pom.xml中导入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
注:spring-boot-devtools主要提供热重载服务。
接着,我们需要对这个子模块进行基本配置,在application中指定端口、eureka服务端实例名称、是否要被注册中心发现、自己是否为注册中心、以及要注册组件访问的地址
server:
port: 7001
#Euraka配置
eureka:
instance:
# hostname: localhost #Euraka服务端实例名称
hostname: eureka7001.com #Euraka服务端实例名称 集群情况下
client:
register-with-eureka: false #表示是否向eureka注册中心注册自己
fetch-registry: false #如果为false,则表示自己为注册中心
service-url: #表示与注册中心交互的地址,即监控页面,要注册的组件访问这个地址
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 如果单独访问eureka,则在浏览器写 http://localhost:7001/ 即可
defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
最后,我们只需要在启动类中标明这个子模块是一个eureka server就可以了,使用注解@EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer //EnableEurekaServer服务端启动类,接受别人注册进来
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class, args);
}
}
当然,上文提及过,为了保证注册中心不因为意外崩溃,我们可以搭建多个集群,使多个注册中心互相调用,防止注册中心崩溃导致整个服务不可用。
因此我们再创建子项目springcloud-eureka-7002、springcloud-eureka-7003,并配置application.yml
7002:
server:
port: 7002
#Euraka配置
eureka:
instance:
hostname: eureka7002.com #Euraka服务端实例名称
client:
register-with-eureka: false #表示是否向eureka注册中心注册自己
fetch-registry: false #如果为false,则表示自己为注册中心
service-url: #表示与注册中心交互的地址,即监控页面,要注册的组件访问这个地址
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 如果单独访问eureka,则在浏览器写 http://localhost:7001/ 即可
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7003.com:7003/eureka/
7003:
server:
port: 7003
#Euraka配置
eureka:
instance:
hostname: eureka7003.com #Euraka服务端实例名称
client:
register-with-eureka: false #表示是否向eureka注册中心注册自己
fetch-registry: false #如果为false,则表示自己为注册中心
service-url: #表示与注册中心交互的地址,即监控页面,要注册的组件访问这个地址
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 如果单独访问eureka,则在浏览器写 http://localhost:7001/ 即可
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/
四、负载均衡
在SpringCloud中,负载均衡的意义是从客户端的角度出发的,通过负载均衡的策略选择一个服务端的Service,从而避免对一个服务的访问造成较大拥挤,当然,前提是服务提供方提供了几个有同样功能的服务,就像我们在前面所说的那样。
下面将从Ribbon+RestTemplete和Feign两种不同的方法对负载均衡进行介绍。
五、负载均衡——Ribbon+RestTemplete
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
首先,我们创建一个名为springcloud-consumer-dept-80的子模块,这一模块将默认使用80端口,在pom.xml中添加依赖,目录组成和代码如下所示
<dependencies>
<!--ribbon-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>-->
<!-- <version>2.2.9.RELEASE</version>-->
<!-- </dependency>-->
<!--EUREKA提供 其已经包含ribbon,所以不用单独添加了-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
接着我们创建一个Config类,用于配置RestTemplate和负载均衡的实现,@LoadBalanced注解实现了负载均衡
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced //配置负载均衡实现
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
然后我们实现Controller层,这里我们使用一个常量来指定所需要的服务,服务的链接不需要写明url地址,只需要将服务名作为链接的组成部分即可,例如这样:"http://springcloud-provider-dept"
对于每个服务,我们需要使用RestTemplate中的getForObject或postForObject方法调用服务提供者给出的服务。例如
@Autowired
private RestTemplate restTemplate;
//ribbon使用时,这里的地址应该是变量,通过服务名来访问
private static final String REST_URL_PREFIX = "http://springcloud-provider-dept";
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
}
REST_URL_PREFIX+"/dept/add" 是服务接口名
dept 为传入参数
Boolean.class 为返回值类型
这与public boolean add(Dept dept) 也是相对应的。
给出完整的代码:
@RestController
public class DeptConsumerController {
@Autowired
private RestTemplate restTemplate;
// private static final String REST_URL_PREFIX = "http://localhost:8001";
//ribbon使用时,这里的地址应该是变量,通过服务名来访问
private static final String REST_URL_PREFIX = "http://springcloud-provider-dept";
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id, Dept.class);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list", List.class);
}
}
最后实现主启动类:
// ribbon和Eureka整合之后,客户端可以直接调用,不用关心IP地址和端口号
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
六、负载均衡——Feign
使用Feign实现负载均衡其实与使用Ribbon+RestTemple实现本质是相同的,因为Feign本身也继承了Ribbon,不同的是,Feign更符合面向接口编程的思想,通过封装,使我们在调用时以接口的方式获取服务,而不是通过一个RestTemplate中的那种常量。
首先我们创建项目并导入pom.xml中的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<!--ribbon 这里其实可以不用加了-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<!--EUREKA提供 其已经包含ribbon,所以不用单独添加了-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
前面讲到过,负载均衡是以客户端根据某种策略选择服务端的服务接口,因此均衡负载的接口一定会在客户端实现。因此我们要在springcloud-api子模块中实现一个接口,这个接口实现一个统一的负载均衡方案,并帮助客户端调用服务提供者的服务,我们在springcloud-api子模块中创建service层,并新建一个ClientService接口:
@FeignClient(value = "springcloud-provider-dept")
public interface DeptClientService {
@GetMapping("/dept/get/{id}")
Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
List<Dept> queryAll();
@PostMapping("/dept/add")
boolean addDept(Dept dept);
}
在接口中,@FeignClient(value = "springcloud-provider-dept")注解阐述了我们要访问的服务名称springcloud-provider-dept,并且默认的提供了负载均衡算法,至此,我们的负载均衡方案就实现了。
可能某些朋友会疑惑,为什么在Ribbon+RestTemplate的方案中,没有在springcloud-api子模块中创建接口,就我个人的见解来说,其一个原因是Ribbon+RestTemplate方案并非面向接口的,而是使用一个常量作为服务名称直接调用RestTemplate中的方法来搜寻服务,并不存在使用一个统一的自定义的公共接口的问题;换一个角度来说,其第二个原因是RestTemplate本身是一个实现类,就如同我们在springcloud-api子模块中实现的一个公共接口DeptClientService一样,我们剩下的工作仅仅是调用一下,因此不需要自己单独实现。
然后,我们回到springcloud-consumer-dept-feign子模块中,创建一个Controller用于调用我们刚刚写好的接口
@RestController
public class DeptConsumerController {
@Autowired
private DeptClientService service = null;
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return this.service.addDept(dept);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return this.service.queryById(id);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return this.service.queryAll();
}
}
最后,在主启动类中引入注解@EnableFeignClients(basePackages = {"com.demo.springcloud"}),使其能够使用Feign并扫描包中的接口
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.demo.springcloud"})
public class DeptConsumer_feign {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_feign.class, args);
}
}
七、熔断——Hystrix
熔断的主要作用是:由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施。
比如我们请求一个数据库中不存在的数据,如果不加以处理,服务器将在这个地方出现错误,而随着访问者的不断增多,所有的访问者都会因为请求这个出错的服务而阻塞在这里,造成服务的崩溃。而当这一服务崩溃,与之关联的前序服务也会因为拥塞而崩溃,进而造成整个服务全面崩溃,这一现象称之为雪崩。
为了杜绝这一现象,我们引入熔断的机制,当出现服务错误时,我们尝试使用一个拦截的手段,将错误的请求返回一个能够使其正常结束的响应,从而防止错误的发生,避免拥塞。
就我个人的理解来说,这像是一种官方提供的从服务端角度实现的对某一接口的错误处理,我们仅需实现一个错误处理方法,并使用@HystrixCommand注解,当出现错误时,让系统进行回调即可。
下面是熔断的实现方案:
首先,创建一个项目,并导入pom.xml依赖,由于这是在服务提供者的基础上进行的改进,因此依赖与服务提供者是相似的。仅仅多了Hystrix的依赖。
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
<!--EUREKA提供-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>3.0.5</version>
</dependency>
<!-- actuator完善监控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--用于拿到实体类-->
<dependency>
<groupId>org.example</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
接着,改造Controller层,加入错误处理函数和注解:
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/dept/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGet")
public Dept get(@PathVariable("id") Long id) {
Dept dept = deptService.queryById(id);
return dept;
}
//备选方案
public Dept hystrixGet(@PathVariable("id") Long id) {
return new Dept().setDeptno(id)
.setDname("id=>"+id+",不存在该用户,信息无法找到")
.setDb_source("no database");
}
}
最后,在主启动类中加入@EnableCircuitBreaker注解从而支持熔断
@SpringBootApplication
@EnableEurekaClient //服务启动后,将提供者的客户端内容注册到注册中心eureka中
@EnableDiscoveryClient //服务发现
@EnableCircuitBreaker //添加对熔断的支持
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class, args);
}
//增加一个Servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
八、降级——Hystrix+Feign
降级的应用场景是,当一个服务访问过高,而另一个服务几乎很少有人访问时,如果能够关闭另一个服务,则可以为服务器腾出更多的空间来承载那个访问很高的服务。不过此时,贸然关闭另一个服务会导致使用这个服务的用户突然出现错误,为了防止这个情况发生,应该给这个服务增加一个整体性的错误处理方案。当服务突然关闭时,返回给用户一个错误信息,而不是让服务器崩溃。
下面展示实现步骤:
首先,我们需要一个实现了Hystrix依赖的服务提供方,因此继续使用熔断中的项目springcloud-provider-dept-hystrix-8001
接着,由于降级是从客户端角度出发的,因此还需要一个消费者子模块来访问服务提供方,这里我们使用实现了Feign依赖的客户端springcloud-consumer-dept-feign
最后,我们在消费者和提供方中间的桥梁——api子模块中实现一个错误处理方案fallbackfactory,它可以对整个类(即服务)进行错误处理,不过要注意的是,服务提供方的接口上必须要有@HystrixCommand注解,才能起到作用。
@Component
public class DeptClientServiceFallBackFactory implements FallbackFactory {
@Override
public DeptClientService create(Throwable cause) {
return new DeptClientService() {
@Override
public Dept queryById(Long id) {
return new Dept()
.setDeptno(id)
.setDname("id=>"+id+"没有对应的信息,客户端提供了降级信息,当前服务已关闭")
.setDb_source("没有数据库对应信息");
}
@Override
public List<Dept> queryAll() {
return null;
}
@Override
public boolean addDept(Dept dept) {
return false;
}
};
}
}
这一实现类要实现接口FallbackFactory,同时,使用Feign的那个自定义接口中要修改一下FeignClient注解
@FeignClient(value = "springcloud-provider-dept", fallbackFactory = DeptClientServiceFallBackFactory.class)
//@FeignClient(value = "springcloud-provider-dept")
public interface DeptClientService {
@GetMapping("/dept/get/{id}")
Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
List<Dept> queryAll();
@PostMapping("/dept/add")
boolean addDept(Dept dept);
}
此时,当我们启动服务时,突然关闭服务提供方的进程,再次从客户端请求便会显示没有对应的信息,客户端提供了降级信息,当前服务已关闭的错误信息了。
熔断与降级的不同:
1.熔断是对某一个接口进行错误处理,降级可以对某一个类进行错误处理
2.熔断从服务提供方的角度出发,降级从消费者角度出发
九、服务监控——HystrixDashBoard
Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求,多少成功,多少失败等等。
在客户端我们需要单独创建一个监控子模块,并在主启动类中加入@EnableHystrixDashboard注解。
在服务提供方的pom.xml中加入如下依赖即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在服务提供方的启动类中加入如下代码:
//增加一个Servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
十、路由网关——Zuul
路由网关主要提供一个统一的系统入口,访问服务均从这一入口进行,可以有效屏蔽各个微服务本身的端口信息,提升安全性。
首先,创建一个zuul的子模块,并在application.yml中加入对zuul的配置信息
zuul:
routes:
mydept.serviceId: springcloud-provider-dept # 原本的项目名称
mydept.path: /mydept/**
# 上述操作用于将微服务名称springcloud-provider-dept隐藏起来,即url上的springcloud-provider-dept将被替换为mydept
ignored-services: springcloud-provider-dept #不再使用这个路径访问 隐藏全部路径为:“*”
prefix: /demodemo #设置统一的前缀
接着在主启动类中加入@EnableZuulProxy注解即可
@SpringBootApplication
@EnableZuulProxy //一般选择代理
public class ZuulApplication_9527 {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication_9527.class, args);
}
}
十一、远程yml配置——Config
在项目的开发中,每一个子模块都包含自己的application.yml配置信息,当一个产品从dev阶段变为test阶段时,我们必须要修改项目中的application.yml。远程的yml配置方案允许不修改项目中的yml,而是修改远程仓库中的配置信息,项目中的配置文件只需要简单调用远程仓库的配置信息即可,从而达到项目与配置解耦的目的,也方便运营、测试人员对项目上线前进行环境调试。
首先,我们需要一个用于获取远程仓库配置文件的子模块,创建springcloud-config-server-3344,并在pom.xml中加入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
在application.yml中配置端口和远程仓库连接信息
server:
port: 3344
spring:
application:
name: springcloud-config-server #应用的名称
#连接远程仓库
cloud:
config:
server:
git:
uri: https://gitee.com/xxx/springcloud-demo.git #https
在主启动类中加入注解@EnableConfigServer
@SpringBootApplication
@EnableConfigServer
public class Config_Server_3344 {
public static void main(String[] args) {
SpringApplication.run(Config_Server_3344.class, args);
}
}
接着,我们在需要远程配置的子模块中引入config依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
新建一个bootstrap.yml配置文件,这个文件用于调用获取远程仓库配置文件的子模块从而间接调用远程仓库的配置文件
spring:
cloud:
config:
name: config-dept # 需要从git上读取的资源名称,不需要后缀
label: master
profile: dev
uri: http://localhost:3344
# 上述相当于:http://localhost:3344/master/config-dept-dev.yml
修改application.yml,删除所有的配置,仅保留项目名称即可
spring:
application:
name: spring-config-client-3355
由于bootstrap.yml是比application.yml优先级更高的配置文件,因此项目一定会按照远程仓库的在线配置文件来对项目进行配置,因此即使在application.yml写上了不同的端口信息,也是不起作用的。
在远程仓库中的yml文件如下所示:
spring:
profiles:
active: dev
---
server:
port: 8001
management:
endpoints:
web:
exposure:
include: "*"
mybatis:
type-aliases-package: com.demo.springcloud.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
spring:
profiles: dev
application:
name: springcloud-config-dept #应用的名称
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/eis?useUnicode=true&characterEncoding-utf-8
username: root
password: 123456
#eureka的配置,服务注册到哪里,提供者要找到注册中心
eureka:
client:
service-url:
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/ #集群情况下的提供者发布配置网址
instance:
instance-id: springcloud-provider-dept8001 #修改eureka上的默认描述信息
#info配置 随便写的
info:
app.name: demo-yjn
company.name: yangejining.springcloud.com
---
server:
port: 8001
management:
endpoints:
web:
exposure:
include: "*"
mybatis:
type-aliases-package: com.demo.springcloud.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
spring:
profiles: test
application:
name: springcloud-config-dept #应用的名称
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/eis1?useUnicode=true&characterEncoding-utf-8
username: root
password: 123456
#eureka的配置,服务注册到哪里,提供者要找到注册中心
eureka:
client:
service-url:
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/ #集群情况下的提供者发布配置网址
instance:
instance-id: springcloud-provider-dept8001 #修改eureka上的默认描述信息
#info配置 随便写的
info:
app.name: demo-yjn
company.name: yangejining.springcloud.com
附录
参考资料:
https://www.bilibili.com/video/BV1jJ411S7xr
restTemplate的介绍和使用_weixin_41261521的博客-CSDN博客_resttemplate
转载请注明出处#