gateway动态路由_动态网关之Spring Cloud Gateway实现

    在公司微服务化过程中,不同的服务一般会有不同的网络地址,而客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,显然不合理。

介绍

    目前国内微服务框架一般就两套,一个是SpringCloud,另外一个SpringAlibaba(Dubbo)。微服务是功能粒度上的拆分,必然导致拆分之后的模块变多。模块与模块之间的通信与维护就变得越来越复杂,微服务网关就应运而生。

    网关的作用不言而喻,提供统一API入口,解耦隔离服务,鉴权校验,动态路由。目前主流的网关有三个:Zuul、Kong、Gateway。下面分别简单介绍一下。

Zuul是Neflix开源的微服务网关,核心是一系列的过滤器。经历了1.x到2.x版本的架构升级,从原来1.x的线程阻塞io到2.x基于netty的非阻塞io,性能提升2成左右。也是SpringCloud全家桶里面的成员。使用比较方便,用户量也比较广。不过由于Netfix闭源问题,部分用户也在转向其他两个使用。

Kong基于OpenResty开源的一个分布式Api网关,高性能、易扩展等特点也使得它现在也很流行。由于Kong是基于Nginx与Lua的高性能平台。所以插件功能是一种特色。同时自己也可以使用Lua脚本定制。所以效率不言而喻。

Gateway是在由于Netfix闭源情况下,Apache Spring官方自己推出的网关工具,入手也还是方便,同时对动态路由配置也是支持。今天我们就讲关于SpringCloudGateway的动态路由使用。

案例

    SpringCloud微服务的搭建就不在这里展开了,不懂的可以百度/谷歌。本项目用到的微服务有注册中心、网关、测试服务。下面给出springcloud的版本及所需依赖。

父项目的pom主要内容

   <properties>        <java.version>1.8java.version>        <spring-cloud.version>Hoxton.SR5spring-cloud.version>    properties>    <modules>        <module>registermodule>        <module>tablemodule>    modules>      <dependencies>        <dependency>            <groupId>org.springframework.cloudgroupId>            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>        dependency>        <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-testartifactId>            <scope>testscope>            <exclusions>                <exclusion>                    <groupId>org.junit.vintagegroupId>                    <artifactId>junit-vintage-engineartifactId>                exclusion>            exclusions>        dependency>        <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-actuatorartifactId>        dependency>        <dependency>            <groupId>io.micrometergroupId>            <artifactId>micrometer-registry-prometheusartifactId>        dependency>    dependencies>    <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.cloudgroupId>                <artifactId>spring-cloud-dependenciesartifactId>                <version>${spring-cloud.version}version>                <type>pomtype>                <scope>importscope>            dependency>        dependencies>    dependencyManagement>

注册中心我们用的是eureka方便入手搭建,当然后面也会用到Nacos作为配置中心,也可以一并使用Nacos做注册中心,二合一。eureka依赖及配置文件内容如下:

  测试环境可以不要下面的安全认证依赖。  <dependency>      <groupId>org.springframework.bootgroupId>      <artifactId>spring-boot-starter-securityartifactId>  dependency>  <dependency>      <groupId>org.springframework.cloudgroupId>      <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>  dependency>
spring.application.name=registerserver.port=8000#下面两项是验证注册用的配置spring.security.user.name=adminspring.security.user.password=rootmanagement.endpoints.web.exposure.include=*management.endpoints.web.exposure.exclude=envmanagement.metrics.tags.application=${spring.application.name}eureka.client.registerWithEureka=false #不注册eureka.client.fetchRegistry=false #不取注册信息eureka.client.service-url.defaultZone=http://admin:root@192.168.2.63:8000/eureka/

下面看Gateway的依赖及配置文件内容:

 <dependency>      <groupId>org.springframework.cloudgroupId>      <artifactId>spring-cloud-starter-netflix-ribbonartifactId>        dependency>    <dependency>      <groupId>com.alibaba.cloudgroupId>      <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>      <version>2.2.1.RELEASEversion>        dependency>  <dependency>      <groupId>org.springframework.cloudgroupId>      <artifactId>spring-cloud-starter-gatewayartifactId>  dependency>

由于我们用到了nacos配置中心,所以在bootstrap.properties中配置如下:

spring.cloud.nacos.config.server-addr=192.168.23.202:8848spring.cloud.nacos.config.file-extension=properties

在application.yaml或者application.properties中配置如下:

eureka:  client:    service-url:      defaultZone: http://admin:root@192.168.2.63:8000/eureka/management:  endpoint:    prometheus:      enabled: true  endpoints:    web:      exposure:        exclude: env        include: '*'  metrics:    export:      prometheus:        enabled: true    tags:      application: ${spring.application.name}server:  port: 8003spring:  application:    name: gateway  cloud:    gateway:      discovery:        locator:          enabled: true          lower-case-service-id: true      httpclient:        connect-timeout: 8000        response-timeout: 5000    nacos:      config:        server-addr: 192.168.23.202:8848
#注册中心地址eureka.client.service-url.defaultZone=http://admin:root@192.168.2.63:8000/eureka/server.port=8003spring.application.name=gatewaymanagement.endpoints.web.exposure.include=*management.endpoints.web.exposure.exclude=envmanagement.endpoint.prometheus.enabled=truemanagement.metrics.export.prometheus.enabled=truemanagement.metrics.tags.application=${spring.application.name}#为监听nacos配置变化的配置参数spring.cloud.nacos.config.server-addr=192.168.23.202:8848#自动发现服务spring.cloud.gateway.discovery.locator.enabled=true#服务名小写spring.cloud.gateway.discovery.locator.lower-case-service-id=true#全局连接超时时间spring.cloud.gateway.httpclient.connect-timeout=8000#全局相应超时时间spring.cloud.gateway.httpclient.response-timeout=5000

通过上面注册中心和网关服务的配置,我们不用写任何代码就可以让其跑起来。然后下面我们创建一个测试服务注册到Eureka中,用于测试网关的路由。添加pom依赖及配置文件如下:

 <dependency>    <groupId>org.springframework.bootgroupId>    <artifactId>spring-boot-starter-webartifactId>dependency>
eureka.client.service-url.defaultZone=http://admin:root@192.168.2.63:8000/eureka/spring.application.name=tableserver.port=8002

添加请求接口如下:

@RestController@RequestMapping("/table")public class TableController {    @Value("${server.port}")    private String port;    @GetMapping("/test")    public Object test(HttpServletRequest request) {        System.out.println("table:port=" + port);        try {            TimeUnit.SECONDS.sleep(5);        } catch (InterruptedException e) {            e.printStackTrace();        }        return "table:port=" + port;    }}

Nacos的部署(https://nacos.io/):

下载最新稳定版:https://github.com/alibaba/nacos/releases

 unzip nacos-server-$version.zip 或者 tar -xvf nacos-server-$version.tar.gz  cd nacos/bin  sh startup.sh -m standalone #单机启动  sh startup.sh -p embedded -m standalone #单机内置数据源启动

通过以上的搭建及部署,就完成了我们想要的微服务架构。但是现在我们访问网关无法路由到指定的服务。下面开始配置网关。

spring:  cloud:    gateway:      routes:#路由list      - id: after_route #路由ID        uri: https://demo.org #路由去的uri        predicates:#规则        - After=2020-07-20T17:42:47.789-07:00[America/Denver]

上面的作用是对2020-07-20T17:42:47.789-07:00[America/Denver]这个时间段以后的请求路由到https://demo.org。当然还有Before/Between。想了解更多请看官网介绍https://cloud.spring.io/spring-cloud-gateway/reference/html。在开发中我们希望通过请求前缀自动匹配路由到对应的服务。比如:当请求http://gatewayhost/table/test,希望其自动转向table服务。请求/table/test接口获取数据。可以如下配置:

spring:  cloud:    gateway:      routes:      - id: path_route        uri: https://tableurl        predicates:        - Path=/table/**

如果在微服务中不想写http地址,可以改成服务名称“lb://server-name”,lb代表loadbalance-负载均衡,server-name则是服务名称。如下:

spring:  cloud:    gateway:      routes:      - id: path_route        uri: lb://table        predicates:        - Path=/table/**

可以设置路由转发时在url上加前缀,如请求网关的地址是http://host/abc/efg,在服务端请求的地址其实是path/abc/efg。那么久可以使用PrefixPath配置:

spring:  cloud:    gateway:      routes:      - id: table_route        uri: lb://table        filters:        - PrefixPath=/path

在有些情况下,网络抖动或失败时需要重试,可以如下配置:

spring:  cloud:    gateway:      routes:      - id: retry_test        uri: http://localhost:8088/table        predicates:        - Host=*.retry.com        filters:        - name: Retry          args:            retries: 3#重试次数            statuses: BAD_GATEWAY#请求返回状态码,org.springframework.http.HttpStatus            methods: GET,POST#针对那种格式的请求,需要啊重试            backoff:#重试间隔              firstBackoff: 10ms #首次间隔时间              maxBackoff: 50ms #最大间隔时间              factor: 2 #重试因子              basedOnPreviousValue: false

截取部分请求url,转发给相应服务StripPrefix,如请求网关地址为http://host/table/table/test,而服务提供的接口为/table/test,就可以使用StripPrefix=1去掉一个part在,注意不是一个字符:

spring:  cloud:    gateway:      routes:      - id: nameRoot        uri: https://nameservice        predicates:        - Path=/name/**        filters:        - StripPrefix=1#截取一个部分

设置网关请求超时有两种,一种是全局的超时时间,可以设置大一点,另外一种是针对单个服务或单个接口的超时时间,这个根据实际做调整。

spring:  cloud:    gateway:      httpclient:        connect-timeout: 1000  #全局连接超时时间1s        response-timeout: 5s  #全局请求相应超时时间5s

单个超时设置

      - id: per_route_timeouts        uri: https://example.org #转发路由uri        predicates:          - name: Path            args:              pattern: /delay/{timeout}        metadata:          response-timeout: 2000  #/delay/{timeout}接口的相应超时时间          connect-timeout: 2000  #/delay/{timeout}接口的连接超时时间

当然以上的配置都可以通过java代码实现。例如单个超时可以这样设置:

import static org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT_ATTR;import static org.springframework.cloud.gateway.support.RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR;      @Bean      public RouteLocator customRouteLocator(RouteLocatorBuilder routeBuilder){         return routeBuilder.routes()               .route("test1", r -> {                  return r.host("*.somehost.org").and().path("/somepath")                        .filters(f -> f.addRequestHeader("header1", "header-value-1"))                        .uri("http://someuri")                        .metadata(RESPONSE_TIMEOUT_ATTR, 2000)                        .metadata(CONNECT_TIMEOUT_ATTR, 2000);               })               .build();      } @Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder, ThrottleGatewayFilterFactory throttle) {    return builder.routes()            .route(r -> r.host("**.abc.org").and().path("/image/png")                .filters(f ->                        f.addResponseHeader("X-TestHeader", "foobar"))                .uri("http://httpbin.org:80")            )            .route(r -> r.path("/image/webp")                .filters(f ->                        f.addResponseHeader("X-AnotherHeader", "baz"))                .uri("http://httpbin.org:80")                .metadata("key", "value")            )            .route(r -> r.order(-1)                .host("**.throttle.org").and().path("/get")                .filters(f -> f.filter(throttle.apply(1,                        1,                        10,                        TimeUnit.SECONDS)))                .uri("http://httpbin.org:80")                .metadata("key", "value")            )            .build();}

对应习惯使用properties格式的配置文件的朋友来说,yaml有点不适应,我们也可以转换。例如下面这样:

spring.cloud.gateway.routes[2].id=10spring.cloud.gateway.routes[2].uri=lb:ws://websocket-service-v1spring.cloud.gateway.routes[2].predicates[0]=Path=/api/websocket-service-v1/**spring.cloud.gateway.routes[2].filters[0]=RewritePath=/api/websocket-service-v1/(?.*), /$\{remaining}

熔断降级配置:

spring:  cloud:    gateway:      routes:      - id: ingredients        uri: lb://ingredients        predicates:        - Path=//ingredients/**        filters:        - name: Hystrix          args:            name: fetchIngredients            fallbackUri: forward:/fallback/test #降级的请求地址

在Gateway服务里面提供一个"/fallback/test"接口,当网关请求服务是出现超时熔断情况,就会返回该接口的内容,给予友好体验。

进阶

    前面讲了gateway的搭建、配置都是项目启动后不能不停机的改变。下面我们来看如果想动态调整路由规则。上面也介绍了我们使用nacos来做配置中心(Appllo或者spring cloud config也是可以的),这样就可以将我们的动态路由放在配置中心上,同时监听配置中心的变化,再来更新网关服务的路由。达到不停机既可调整路由规则的效果。

在nacos上新建配置:

5715c6f9d9a770f9ccd10af3b2f7d6de.png

填入dataId,group,选择json格式,填入配置内容。

db25bd91e5460ec10cefe75e412bb514.png

内容如下:

[    {        "id":"table",        "predicates":[            {                "name":"Path",                "args":{                    "pattern":"/table/**"                }            }        ],        "uri":"lb://table",        "filters":[            {                "args":{                    "retries":1,                    "statuses":"BAD_GATEWAY",                    "method":"GET"                },                "name":"Retry"            },              {                "args":{                    "name":"fallback",                    "fallbackUri":"forward:/fallback"                },                "name":"Hystrix"  #熔断降级配置            },             {                "args":{                    "prefix":"/table"                },                "name":"PrefixPath"            },            {                "name":"PreserveHostHeader"            }        ],        "metadata":{            "response-timeout":3000,            "connect-timeout":3000        }                       },    {        "id":"user",        "predicates":[            {                "name":"Path",                "args":{                    "pattern":"/user/**"                }            }        ],        "uri":"lb://user",        "filters":[            {                "args":{                    "parts":"0"                },                "name":"StripPrefix"            }        ]    }]

更改我们Gateway服务。添加Nacos监听代码及动态路由替换代码:

@Componentpublic class NacosGatewayConfig implements ApplicationEventPublisherAware{    private String dataId = "gateway";    private String group = "DEFAULT_GROUP";    @Value("${spring.cloud.nacos.config.server-addr}")    private String serverAddr;    @Autowired    private RouteDefinitionWriter routeDefinitionWriter;    private ApplicationEventPublisher applicationEventPublisher;    private static final List ROUTE_LIST = new ArrayList<>();//启动执行方法,也可以继承CommandLineRunner-run()启动执行    @PostConstruct     public void dynamicRouteByNacosListener() {        try {        //如果使用的是其他配置中心,需要替换如下监听设置。            ConfigService configService = NacosFactory.createConfigService(serverAddr);            String initInfo = configService.getConfig(dataId, group, 5000);            addAndPublishBatchRoute(initInfo);            configService.addListener(dataId, group, new Listener() {                @Override                public void receiveConfigInfo(String configInfo) {                    addAndPublishBatchRoute(configInfo);                }                @Override                public Executor getExecutor() {                    return null;                }            });        } catch (NacosException e) {            e.printStackTrace();        }    }        private void addAndPublishBatchRoute(String configInfo) {        try {            clearRoute();            List gatewayRouteDefinitions = JSONObject.parseArray(configInfo, RouteDefinition.class);            for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {                addRoute(routeDefinition);            }            publish();            System.out.println(gatewayRouteDefinitions);        } catch (Exception e) {            e.printStackTrace();        }    }    private void clearRoute() {        for (String id : ROUTE_LIST) {            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();        }        ROUTE_LIST.clear();    }    private void addRoute(RouteDefinition definition) {        try {            routeDefinitionWriter.save(Mono.just(definition)).subscribe();            ROUTE_LIST.add(definition.getId());        } catch (Exception e) {            e.printStackTrace();        }    }    private void publish() {//发布路由改变事件        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));    }    @Override//注入publisher    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {        this.applicationEventPublisher = publisher;    }}

总结

    微服务网关我们不只是用于api的管理转发,比如可以在网关层面做用户校验鉴权,让垃圾请求提前过滤掉,减少服务开销。同时也可以作为灰度发版、服务权重设置、服务管理、流量分发等应用。

好了,Spring Cloud Gateway的动态路由项目就讲到这里。喜欢就转发吧。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值