spring cloud gateway已经使用了较长一段时间,一直有想法,整理整理一下,形成一个有效的记录,近期终于抽空写成笔记。

1、基本环境

版本:consul-1.7.2

服务:192.168.11.45(windows)、192.168.110.35(centos72)、192.168.110.36(centos72)

2、安装目录

centos7.2:/opt/consul-1.7.2

windows:D:/test/consul

3、集群架构

1.三个consul服务节点,组成集群提供注服务

2.两个producer生产者服务,提供业务服务响应

3.一个gateway网关服务,提供路由转发服务

4.消费者模拟请求发送到网关,再由网关路由到生产者服务的业务中;

4、Consul

Consul是一个在分布式环境中的提供服务注册和发现流程的服务管理软件,分布式高可用,提供服务发现和配置共享,主要特点是:服务发现、健康检查、键值存储、安全服务通信、多数据中心。

在集群架构中,Consul保证了c(数据一致性)、a(高可用)特点,不保证p(分区容错)特点。在SpringCloud中已对 Consul进行了自动配置与封装,并且官方建议替代 Eureka。

Consul 采用 raft 算法来保证数据的强一致性:

      a.服务注册到Consul时,raft协议要求必须过半数的节点都写入成功才认为注册成功;

      b.集群中的Leader挂掉时,重新选举期间整个consul不可用,保证强一致性但会牺牲可用性。

安装过程比较简单,自行百度,省略...

4.1、集群配置

服务一:config/server1.json(IP:192.168.110.35)

{
    "datacenter":"dc1",
    "node_name":"server1",
    "server":true,
    "data_dir":"/opt/consul-1.7.2/data",
    "addresses":{
        "http":"0.0.0.0"
    },
    "ports":{
        "http":8500,
        "serf_lan": 8301,
        "serf_wan": 8302
    }
}

服务二:config/server2.json(IP:192.168.11.45)

{
    "datacenter":"dc1",
    "node_name":"server2",
    "server":true,
    "data_dir":"D:/test/consul/data",
    "addresses":{
        "http":"0.0.0.0"
    },
    "ports":{
        "http":8500,
        "serf_lan": 8301,
        "serf_wan": 8302
    }
}

服务三:config/server3.json(IP:192.168.110.36)

{
    "datacenter":"dc1",
    "node_name":"server3",
    "server":true,
    "data_dir":"/opt/consul-1.7.2/data",
    "addresses":{
        "http":"0.0.0.0"
    },
    "ports":{
        "http":8500,
        "serf_lan": 8301,
        "serf_wan": 8302
    }
}

4.2、启动服务

# linux(IP:192.168.110.35)

./consul agent -server -ui -bootstrap-expect=3 -config-file=./config/server1.json -advertise=192.168.110.35 -bind=0.0.0.0 -client=0.0.0.0

# windwos(IP:192.168.11.45)

consul agent -server -ui -bootstrap-expect=3 -config-file=./config/server2.json -advertise=192.168.11.45 -bind=0.0.0.0 -client=0.0.0.0 -join 192.168.110.35

# linux(IP:192.168.110.36)

./consul agent -server -ui -bootstrap-expect=3 -config-file=./config/server3.json -advertise=192.168.110.36 -bind=0.0.0.0 -client=0.0.0.0 -join 192.168.110.35

4.3、UI管理界面

URL: http://192.168.110.35:8500/ui

Nodes集群节点

3台服务启动正常,集群通讯成功;

4.4、查看集群状态

在任意一台consul节点所在的服务器上,输入:./consul operator raft list-peers

192.168.110.35为leader

192.168.11.45、192.168.110.35为follower

5、Spring Cloud Consul + Gateway

5.1、(微服务+网关)工程结构搭建

略.....

 注意SpringCloud和SpringBoot版本组合,否则会有兼容性或jar版本冲突:
 Spring-Cloud               Spring-Boot
 =================          ==============
 Hoxton.SR4                 2.2.6.RELEASE
 Hoxton.SR1                 2.2.1.RELEASE
 Greenwich.SR3              2.1.1.RELEASE
 Finchley.RELEASE           2.0.3.RELEASE
 
 本工程:
JVM:1.8
Spring-Cloud:Hoxton.SR1
Spring-Boot:2.2.1.RELEASE

5.2、建立生产者服务

consul-producer模块

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

ProducerController.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RestController;

//生产者控制器
@RestController
@RequestMapping("/producer")
public class ProducerController {
    @Value("${server.port}")
    private String port;
    @RequestMapping(value = "/test")
    public String test() {
        return "test api server port: " + port + ",ok:" + System.currentTimeMillis();
    }
    @RequestMapping(value = "/test0")
    public String test0() {
        return "test0 api server port: " + port + ",ok:" + System.currentTimeMillis();
    }
    @RequestMapping(value = "/test1")
    public String test1() {
        return "test1 api server port: " + port + ",ok:" + System.currentTimeMillis();
    }
}

配置生产者服务一

application.yml

server:
  port: 8086
  address: 0.0.0.0
  servlet:
    context-path: /

spring:
  application:
    name: consul-producer
  cloud:
    consul:
      host: 192.168.11.45
      port: 8500
      discovery:
        service-name: consul-producer
        hostname: 192.168.11.45
        tags: version=1.0,author=JL

management:
  health:
    refresh:
      enabled: true

服务端口:8086,注册到Consul服务节点IP为192.168.11.45

配置生产者服务二

application.yml

server:
  port: 8087
  address: 0.0.0.0
  servlet:
    context-path: /

spring:
  application:
    name: consul-producer
  cloud:
    consul:
      host: 192.168.110.36
      port: 8500
      discovery:
        service-name: consul-producer
        hostname: 192.168.11.45
        tags: version=1.0,author=JL

management:
  health:
    refresh:
      enabled: true

服务端口:8086,注册到Consul服务节点IP为192.168.11.45

在idea中分二次启动consul-producer,每次按上述配置修改注册的consul节点host和port,表示启动两个相同服务名,但注册到不同consul节点的上的分布式生产者微服务;

5.3、建立网关服务

gateway-server模块

服务端口:8089,注册到Consul服务节点IP为192.168.110.35

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
</dependencies>

application.yml

server:
  port: 8089
  address: 0.0.0.0
  servlet:
    context-path: \

spring:
  application:
    # 服务名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 启用本地化网关
          enabled: true
          # 将服务名转换为小写
          lower-case-service-id: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters),filters 不是必需参数)
      routes:
      # id 必需唯一
      - id: consul-producer
        # lb:service-name 表示注册中心的服务名称
        uri: lb://consul-producer
        # 匹配条件
        predicates:
        # 路径断言匹配
        - Path=/producer/**
        # 必需是POST请求
        - Method=POST
        filters:
        # 在转发url后加参数name=jk
        - AddRequestParameter=name,jk
#          - StripPrefix=1
#           - Cookie=mycookie,mycookievalue
      - id: test0-id
        # 转发到指定url
        uri: http://192.168.11.45:8086
        predicates:
        # 路径断言匹配
        - Path=/route/producer/test0
        filters:
        # StripPrefix=1表示过滤Path第一段(截取/test)
        - StripPrefix=1
        # 在转发url后加参数version=test0
        - AddRequestParameter=version,test0
        # 添加熔断机制,当服务不可用时或超过了指定超时长,则转发到fallback
        - name: Hystrix
          args:
            name: fallbackcmd
            fallbackUri: forward:/fallback
      - id: test1-id
        # 转发到指定url
        uri: http://192.168.11.45:8087
        predicates:
        - Method=GET
        # 路径断言匹配
        - Path=/route/producer/test1
        filters:
        # StripPrefix=1表示过滤Path第一段(截取/test)
        - StripPrefix=1
        # 在转发url后加参数version=test1
        - AddRequestParameter=version,test1
        # 添加熔断机制,当服务不可用时或超过了指定超时长,则转发到fallback
        - name: Hystrix
          args:
            name: test1Hystrix
            fallbackUri: forward:/fallback/test1
    # consul注册中心
    consul:
      # 注册中心host
      host: 192.168.110.35
      # 注册中心端口
      port: 8500
      # 服务配置
      discovery:
        # 注册到consul的服务名称
        service-name: ${spring.application.name}
        # 注册到consul的hostname(主机名称,consul是通过hostname提供给客户端连接的)
        hostname: 192.168.11.45
        # 注册到consul的标识
        tags: version=1.0,author=JL
        ip-address: 192.168.11.45

# 启用服务健康检测,consul将通过http://host:port/actuator/health 检测服务的存活,默认10s一次
management:
  health:
    refresh:
      enabled: true

# 配置日志
logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

# 熔断器配置
hystrix:
  command:
    default:
      execution:
        timeout:
          # enabled表示是否启用超时检测,默认为true
          enabled: true
        isolation:
          thread:
            # 全局熔断器超时
            timeoutInMilliseconds: 5000
    # test1Hystrix为熔断器名称,对应:filters.args.name
    test1Hystrix:
      execution:
        timeout:
          # enabled表示是否启用超时检测,默认为true
          enabled: false
        isolation:
          thread:
            # 特定服务熔断器超时
            timeoutInMilliseconds: 2000

GatewayController.java(熔断测试控制器)

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

//熔断机制测试控制器
@RestController
@RequestMapping("/fallback")
public class GatewayController {
    @RequestMapping("")
    public String fallback(){
        return "error fallback method , " + System.currentTimeMillis();
    }
    @RequestMapping("/test1")
    public String fallbackTest1(){
        return "error fallbackTest1 method , " + System.currentTimeMillis();
    }
}

以上三个服务均在开发机器:192.168.11.45上

6、服务检测

consul集群成功、微服务注册成功

consul-producer 成功注册了两个微服务

gateway-service 成功注册了一个微服务

7、发起网关请求

192.168.11.45:8089为网关服务IP+端口

7.1、测试网关test0接口

http://192.168.11.45:8089/route/producer/test0

按照网关服务application.yml配置,route/producer/test0转发接口指向微服务:192.168.11.45:8086

7.2、测试网关test1接口

http://192.168.11.45:8089/route/producer/test01

按照网关服务application.yml配置,route/producer/test1转发接口指向微服务:192.168.11.45:8087

8、熔断测试

把gateway-service模块的 application.yml中,是否启用超时检测enabled 设置为true,并且超时时间设为2000毫秒(2秒钟)

test1Hystrix 指向的网关配置中的- id: test1-id下的args.name: test1Hystrix

# test1Hystrix为熔断器名称,对应:filters.args.name
test1Hystrix:
  execution:
    timeout:
      # enabled表示是否启用超时检测,默认为true
      enabled: false
    isolation:
      thread:
        # 特定服务熔断器超时
        timeoutInMilliseconds: 2000

修改完毕,重启网关服务

在生产者模块的ProducerController.java中对test1()方法进行修改,加入一行代码:Thread.sleep(7 * 1000);对网关转发进入该test1()方法时暂停7秒。

重新发起test1网关测试

http://192.168.11.45:8089/route/producer/test1

因网关中的超时设置为timeoutInMilliseconds: 2000,而生产者服务中的暂停时长为1000*7毫秒,在请求转发到达test1后,因超时网关会触发熔断机制,会转发到“fallbackUri: forward:/fallback/test1”网关内的回调fallbackTest1()方法中,返回上述内容;

9、Consul故障测试

测试consul在集群的情况下,出现宕机或故障,导致节点或服务不可用的情况下的表现;

9.1、网关转发与consul注册规则

http://192.168.11.45:8089/route/producer/test0

按照网关服务application.yml配置,route/producer/test0转发接口指向微服务:192.168.11.45:8086

192.168.11.45:8086服务注册在consul集群的server2

http://192.168.11.45:8089/route/producer/test1

按照网关服务application.yml配置,route/producer/test1转发接口指向微服务:192.168.11.45:8087

192.168.11.45:8087服务注册在consul集群的server3

9.2、停止集群服务server3

模拟服务宕机,手动在服务器上kill掉server3,模拟服务进程异常,服务宕机;

关掉server3,查找IP192.168.110.36所在服务上的进程,kill掉

ps -ef|grep consul

kill -9 pid

没有server3节点的显示

项目模块consul-producer生产者2

生产者2服务信息 192.168.11.45:8087,注册到consul集群中的server3(IP:192.168.110.36)

日志中抛异常“org.apache.http.conn.HttpHostConnectException: Connect to 192.168.110.36:8500 [/192.168.110.36] failed: Connection refused: connect”

重新发起test1网关测试

http://192.168.11.45:8089/route/producer/test1

按照网关服务application.yml配置,route/producer/test1转发接口指向微服务:192.168.11.45:8087

9.3、通过服务名称转发

变更配置,将gateway模块下的application.yml中test0-id和test1-id的uri路径更改为lb://consul注册服务名

test0-id

- id: test0-id
  # 转发到指定url
  # uri: http://192.168.11.45:8086
  uri: lb://consul-producer

test1-id

- id: test1-id
  # 转发到指定url
  # uri: http://192.168.11.45:8087
  uri: lb://consul-producer

重新发起test1网关测试

http://192.168.11.45:8089/route/producer/test1

按照网关服务application.yml配置,route/producer/test1转发接口指向微服务:lb://consul-producer

由于consul集群中的server3已下线,192.168.11.45:8087注册微服务无效,则直接转发到了同名服务,192.168.11.45:8086下

9.4、停止集群服务server2

模拟服务宕机,我们继续停掉server2,只保留集群server1主服务节点(leader)

手动在服务器上kill掉server2,模拟服务进程异常,服务宕机;

因为server2是部署在本地开发机器上,是windows环境,直接在cmd命令窗口ctrl + c即可;

在刷新consul的UI管理界面,则出现500错误

集群中存活的server1节点(原集群中的leader),后端有抛“No cluster leader”,提示没有集群领导者。原因是和consul节点过半宕机,服务无法选举leader节点;

再一次刷新test1网关接口,转发正常

http://192.168.11.45:8089/route/producer/test1

此时除server1节点外,其它server2、server3节点服务发现与注册中心因空宕机均不可用,并且server1不在是leader节点;

9.5、停止集群服务server1

模拟服务宕机,我们继续停掉server1,整个集群服务中的所有consul节点全部停止不可用;

手动在服务器上kill掉server1,模拟服务进程异常,服务宕机;

刷新UI界面,此时看到server2还在,这是consul的数据缓存,实际server2服务已停止

关掉server1,查找IP192.168.110.35所在服务上的进程,kill掉

ps -ef|grep consul

kill -9 pid

再次刷新 http://192.168.110.35:8500/ui/,已变成无法访问网站,因为server1所对应的节点为192.168.110.35已停止服务;

项目模块gateway-server网关服务

网关服务信息 192.168.11.45:8089,注册到consul集群中的server1(IP:192.168.110.35)

日志中抛异常“org.apache.http.conn.HttpHostConnectException: Connect to 192.168.110.35:8500 [/192.168.110.35] failed: Connection refused: connect”

虽然网关注册的consul服务节点因宕机不可用,但再一次刷新test1网关接口,转发是正常的

http://192.168.11.45:8089/route/producer/test1

经过上述测试,从关闭server3节点、再关闭server2节点,到最后关闭server1节点的所有节点,网关转发始终是正常的;

由于SpringCloudGateway网关会从consul中拉取一份已注册的微服务清单,同时会本地也会维护一套路由配置规则,当consul服务副节点下线后,网关还是从注册的consul节点后获取同名服务尝试连接,如果连接成功则断续转发;如果网关注册的consul节点停止服务不可用,则SpringCloudGateway会从本地维护的SpringCloudConsul微服务清单和网关规则中匹配合适的微服务,继续提供网关转发服务;

10、恢复consul集群

参考前面的,启动服务章节,启动server1、server2、server3,启动过程不需要按顺序启动,但要保证-join 192.168.110.35,先一步启动,以免其它节点加入集群时,找不到节点;

服务全部启动完毕后,3个节点选举server1为leader主节点,其它server2 、server3均为follower为追随节点;

10.1、项目模块恢复

server1、server2、server3三个节点恢复正常集群后,两个conusl-producer生产者服务和gateway-server网关服务均无继续抛错,日志恢复正常,整个consul集群+springcloud分布式微服务架构继续提供业务高可用性服务;

10.2、网关变更注册节点

更改gateway-server模块的appclication.yml配置,把consul.host从192.168.110.35(leader)修改为192.168.110.36(follwer),将网关从注册leader节点变更到注册follwer节点;

# consul注册中心
consul:
  # 注册中心host
  # host: 192.168.110.35
  host: 192.168.110.36

重启gateway-server网关服务,成功注册到server3节点

再一次刷新test1网关接口,转发正常

http://192.168.11.45:8089/route/producer/test1

通过上述变更网关注册的consul节点,测试验证微服务可以通过当前consul注册节点,查询集群内其它consul节点微服务注册信息;

11、总结:

1.consul集群中,数据中心的leader节点是负责所有follower的数据同步和通讯;微服务可以通过当前注册consul节点(leader和follwer),查询consul集群中其它节点注册的微服务信息;

2.gateway网关应用模块,注册到leader节点,从leader节点中,拉取了consul集群中其它follower节点内注册的微服务清单;

3.consul集群中任意节点的退出或故障,会导制注册的到该consul节点的微服务抛异常,极端下甚至服务不可用;同时新的微服务是无法注册到宕机consul节点上;

4.当整个consul集群不可用时,gateway网关服务,会从自身从consul拉取的微服务清单和自身维护的一套路由规则中去匹配转发接口到指定微服务中;

5.当整个consul集群服务恢复正常时,基于SpringCloudConsul的微服务会自动连接到consul集群中各自注册的节点上,恢复正常业务架构;

参考:

https://cloud.spring.io/spring-cloud-static/spring-cloud-consul/2.2.2.RELEASE/reference/html/#spring-cloud-consul-agent

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐