Spring Cloud 微服务实践 (2) - Gateway 重试机制

用户头像
xiaoboey
关注
发布于: 2020 年 09 月 16 日
Spring Cloud 微服务实践(2) - Gateway重试机制

本文对上一篇的代码进行重构,并使用Spring Cloud Gateway的重试机制,解决部分服务实例宕机导致服务不可用的问题。



在上一篇里我们遗留了一个问题没有解决,就是部署了多个实例的服务在部分实例罢工后,会出错并直接把错误丢到终端用户面前,体验一点都不友好。理想的状态应该是终端用户对内部实例的“动荡”毫无知觉,也就是运维时做迁移或者升级,不需要通知(打扰)用户,7X24小时提供服务。



本文的源代码: https://github.com/xiaoboey/from-zero-to-n/tree/master/one ,Spring Boot的版本是2.3.3,Spring Cloud的版本是Hoxton.SR8。

1、重构pom.xml

先重构一下pom.xml,前面都是用Spring Initializr单独初始化的项目,在pom.xml上没有体现协作精神。



Maven可以通过modules把项目的多个模块聚合到一起,形成一个整体,所以我们增加一个pom.xml文件,把eureka-server、gateway和service-one聚合在一起。并且把一部分相同的配置都提炼上来,子项目通过parent共享这些配置。

<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/>
</parent>
<modules>
<module>eureka-server</module>
<module>gateway</module>
<module>service-one</module>
</modules>
<groupId>com.example.one</groupId>
<artifactId>from-zero-to-one</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>from-zero-to-one</name>
<description>Demo project for Spring Cloud Hoxton</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>



修改子项目的Maven配置(pom.xml),这里以eureka-server为例,其他以此类推。用parent指向父级项目,就是前面配置的聚合pom.xml。另外artifactId我也做了调整,主要是为了跟文章一一对应,代码也是复制过来做的修改,不是在原来的代码上重构。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.one</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Service Discovery by Eureka Server</description>
<parent>
<groupId>com.example.one</groupId>
<artifactId>from-zero-to-one</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>



Maven配置文件(pom.xml)的重构到此结束,现在有了父级聚合pom.xml,编译打包的话就可以直接在这里统一进行,不用分别进到各个子项目下去编译打包。

mvn clean install -DskipTests



用父级pom.xml编译打包



Maven项目如果做了模块聚合,并且子项目有依赖父级项目或者其他平级项目,建议都用这种方式进行总体的编译打包,特别是依赖项目代码有修改,或者jar包依赖调整等情况都建议总体编译一次,不然直接进入子项目打包可能会报一些莫名其妙的错误。用IDE也是一样,也建议经常用cmd或者shell方式整体编译,这不是什么大招,但可能会少让你少掉些头发。



2、网关增加重试机制

重试,在生活中有很多例子,比如打电话,对方占线的话就过一会再打,对方关机也是隔断时间再试试。拜访某位大佬,去的时候对方没在,那就下一次。



Spring Cloud Gateway的重试机制也类似,就是某个实例没响应就多试试,并且Gateway的重试机制比较完善,可以设定次数,还可以转移,调用别的实例进行重试。说到这里,我们会发现Gateway的重试机制正好可以解决我们的实例宕机问题。



当某个实例宕机(失联)后,Gateway因为还没有收到Eureka Server的通知,不知道这个实例已经罢工了,还是继续转发请求过去,出错或者超时?那就再试一次,还不行就换一个实例。



修改application.yml,增加重试配置:

spring:
...
cloud:
gateway:
# discovery:
# locator:
# enabled: true
# lower-case-service-id: true
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service-one/**
filters:
- StripPrefix=1
- name: Retry
args:
retries: 1
series:
- SERVER_ERROR
methods:
- GET
- POST
exceptions:
- java.io.IOException



  • StripPrefix:去掉前缀,这里设置为1表示去掉一级前缀,也就是把/service-one去掉,比如/service-one/hello这个请求,去掉一级前缀转发到service-one的实例时,请求的路径就是“/hello”;

  • retries:重试次数,默认重试3次;

  • series:哪些状态码才会进行重试,默认值是SERVER_ERROR,可以配置多个,其他的取值是INFORMATIONAL, SUCCESSFUL, REDIRECTION, CLIENT_ERROR;

  • methods:哪些方法需要进行重试,默认值是GET,就是Http的那些Method,还有HEAD, POST, PUT, PATCH, DELETE, OPTIONS等

  • exceptions:指定哪些异常需要进行重试逻辑,默认值是java.io.IOException。(上面配置里的exceptions配置是多余的,因为设置的值跟默认值一样,没有意义,这里只是为了列出来讲一下)



这里有个小遗憾,重试只能针对具体的service配置,所以网关的自动配置(上面配置中加#注释的那些内容)不能并存,我查了一些资料没找到解决办法。



上面说的小遗憾,Spring Cloud为什么不支持呢?可能是因为有些Method并不适合重试,能重试的Method要满足幂等性,任意多次调用产生的影响均与一次调用的影响相同。从重试的配置也可以看出来,默认是对IO异常进行重试,已经避开了很多坑。我们可以设想一下,如果对一个不幂等又耗时的方法进行超时重试,会产生什么结果呢?数据错误和拖垮系统都不在话下!

3、编译测试

试试看能不能解决服务宕机的问题



把网关Gateway的重试机制配置好之后,编译并启动起来。然后确保eureka-server也已启动,再启动多个service-one实例,还是用开启新的cmd或者shell,执行“mvn spring-boot:run”命令的方式启动新实例。

mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8082 --management.server.port=7082"
#如果是PowerShell,带=号的参数要用单引号引起来才能正确传递
mvn spring-boot:run -D'spring-boot.run.arguments="--server.port=8082 --management.server.port=7082"'

访问http://localhost:8080/service-one/getPort并反复刷新,直到返回的值出现新启动实例的端口,表示新实例已经注册到Eureka Server,并且Gateway已经拿到新实例的信息并转发请求。终止一个service-one实例,再反复刷新getPort,看看是什么效果?是不是getPort不再报错?



上一篇: 《Spring Cloud 微服务实践(1) - 用Initializr初始化

下一篇: 《Spring Cloud 微服务实践 (3) - 服务间的调用

发布于: 2020 年 09 月 16 日 阅读数: 130
用户头像

xiaoboey

关注

IT老兵 2020.07.20 加入

资深Coder,爱好钓鱼逮鸟。

评论

发布
暂无评论
Spring Cloud 微服务实践(2) - Gateway重试机制