zoukankan      html  css  js  c++  java
  • spring cloud 2.x版本 Gateway动态路由教程

    摘要

    本文采用的Spring cloud为2.1.8RELEASE,version=Greenwich.SR3
    

    本文基于前面的几篇Spring cloud Gateway文章的实现。
    参考

    前言

    写了几篇关于Spring Cloud Gateway的文章后发现,Gateway涉及的知识范围太广了,真是深刻体会了“一入Spring cloud深似海”。

    现实生产环境中,使用Spring Cloud Gateway都是作为所有流量的入口,为了保证系统的高可用,尽量避免系统的重启,所以需要Spring Cloud Gateway的动态路由来处理。之前的文章《Gateway路由网关教程》提供的路由配置,在系统启动时候,会将路由配置和规则加载到内存当中,无法做到不重启服务就可以动态的新增、修改、删除内存中的路由配置和规则。

    简单的动态路由实现

    Spring Cloud Gateway源码中提供了GatewayControllerEndpoint类来修改路由配置,但是官方文档好像并没有做详细的使用说明,只是简单介绍了几个简单的api接口。感兴趣的小伙伴可以先查看官方文档(第11章节 Actuator API)。

    引致官方文档:

    The /gateway actuator endpoint allows to monitor and interact with a Spring Cloud Gateway application. To be remotely accessible, the endpoint has to be enabled and exposed via HTTP or JMX in the application properties.

    1.1 添加相关pom依赖

    在原来spring-gateway的基础上增加spring-boot-starter-actuator依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    

    1.2 在application.yml中增加配置

    management:
      endpoints:
        web:
          exposure:
            include: "*"
    

    配置说明:management.endpoints.web.exposure.include 暴露所有的gateway端点

    1.3 启动服务

    启动服务,访问http://localhost:8100/actuator/gateway/routes,这是我们可以看到所有的routes信息。


    我们也可以访问单个路由信息:http://localhost:8100/actuator/gateway/routes/CompositeDiscoveryClient_EUREKA-CLIENT

    显示如下:

    1.4 增加、修改路由信息

    Gateway默认使用的是GatewayControllerEndpoint这个类,GatewayControllerEndpoint又继承了AbstractGatewayControllerEndpoint类。

    提供的方法:(只列具了几个相关方法,其他方法小伙们可以自行查看源码)

    1. /gateway/routes 查询所有路由信息
    2. /gateway/routes/{id} 根据路由id查询单个信息
    3. /gateway/routes/{id} @PostMapping 新增一个路由信息
    4. /gateway/routes/{id} @DeleteMapping 删除一个路由信息

    1.4.1 新增路由

    我们可根据/gateway/routes返回的路由信息,来模仿一个@PostMapping请求参数

    {
        "uri": "http://httpbin.org:80",
        "predicates": [
            {
                "args": {
                    "pattern": "/ribbon/**"
                },
                "name": "Path"
            }
        ],
        "filters": []
    }
    

    这是我们可以通过postman来发送一个post请求,如图所示:

    response返回1证明已经插入成功,我们可以通过http://localhost:8100/actuator/gateway/routes/查看路由信息,显示结果如下:

    截图红框中的信息就是新增加的

    1.4.2 删除路由信息

    我们可以直接用postman模拟DeleteMapping请求,http://localhost:8100/actuator/gateway/routes/addroutes

    显示如下:

    这时候我们在访问http://localhost:8100/actuator/gateway/routes,可以看到新增加的路由已经被删除成功了。

    1.5 小结

    基于Spring Cloud Gateway默认方法实现的动态路由我就完成了,在前言中我已经提到了,这种方式是基于jvm内存实现,一旦服务重启,新增的路由配置信息就是完全消失了。所有这个时候我们可以考虑是否可以参考GatewayControllerEndpoint这类,自己实现一套动态路由方法,然后将路由信息持久化。

    自定义动态路由

    1.1定义相关类

    1.1.1 定义实体类

    可以自定义实体类,我这里偷了一个懒,直接用了Gateway的RouteDefinition类,感兴趣的小伙伴可以参考RouteDefinition类自己扩展,然后写一个Convert类将自定义的类转换成RouteDefinition就可以了。

    1.1.2 自定义RedisRouteDefinitionRepository类

    我这边采用redis做为路由配置的信息的持久层,所以写了一个RedisRouteDefinitionRepository。

    package spring.cloud.demo.spring.gateway.component;
    
    import com.google.common.collect.Lists;
    import org.springframework.cloud.gateway.route.RouteDefinition;
    import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
    import org.springframework.cloud.gateway.support.NotFoundException;
    import org.springframework.stereotype.Component;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    import spring.cloud.demo.spring.gateway.redis.RedisUtils;
    import spring.cloud.demo.spring.gateway.util.JsonUtils;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    /**
     * @auther: maomao
     * @DateT: 2019-11-03
     */
    @Component
    public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
    
        //存储的的key
        private final static String KEY = "gateway_dynamic_route";
    
        @Resource
        private RedisUtils redisUtils;
    
        /**
         * 获取路由信息
         * @return
         */
        @Override
        public Flux<RouteDefinition> getRouteDefinitions() {
            List<RouteDefinition> gatewayRouteEntityList = Lists.newArrayList();
            redisUtils.hgets(KEY).stream().forEach(route -> {
                RouteDefinition result = JsonUtils.parseJson(route.toString(), RouteDefinition.class);
                gatewayRouteEntityList.add(result);
            });
            return Flux.fromIterable(gatewayRouteEntityList);
        }
    
        /**
         * 新增
         * @param route
         * @return
         */
        @Override
        public Mono<Void> save(Mono<RouteDefinition> route) {
            return route.flatMap(routeDefinition -> {
                redisUtils.hset(KEY, routeDefinition.getId(), JsonUtils.toString(routeDefinition));
                return Mono.empty();
            });
        }
    
        /**
         * 删除
         * @param routeId
         * @return
         */
        @Override
        public Mono<Void> delete(Mono<String> routeId) {
            return routeId.flatMap(id -> {
                if (redisUtils.hHashKey(KEY, id)) {
                    redisUtils.hdel(KEY, id);
                    return Mono.empty();
                }
                return Mono.defer(() -> Mono.error(new NotFoundException("route definition is not found, routeId:" + routeId)));
            });
        }
    }
    
    

    1.1.3 自定义Controller和Service

    package spring.cloud.demo.spring.gateway.controller;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cloud.gateway.route.RouteDefinition;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import reactor.core.publisher.Mono;
    import spring.cloud.demo.spring.gateway.service.GatewayDynamicRouteService;
    
    import javax.annotation.Resource;
    
    /**
     * 自定义动态路由
     * @auther: maomao
     * @DateT: 2019-11-03
     */
    @RestController
    @RequestMapping("/gateway")
    @Slf4j
    public class GatewayDynamicRouteController {
    
        @Resource
        private GatewayDynamicRouteService gatewayDynamicRouteService;
    
        @PostMapping("/add")
        public String create(@RequestBody RouteDefinition entity) {
            int result = gatewayDynamicRouteService.add(entity);
            return String.valueOf(result);
        }
    
        @PostMapping("/update")
        public String update(@RequestBody RouteDefinition entity) {
            int result = gatewayDynamicRouteService.update(entity);
            return String.valueOf(result);
        }
    
        @DeleteMapping("/delete/{id}")
        public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
            return gatewayDynamicRouteService.delete(id);
        }
    
    }
    
    
    package spring.cloud.demo.spring.gateway.service;
    
    import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
    import org.springframework.cloud.gateway.route.RouteDefinition;
    import org.springframework.cloud.gateway.support.NotFoundException;
    import org.springframework.context.ApplicationEventPublisher;
    import org.springframework.context.ApplicationEventPublisherAware;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Service;
    import reactor.core.publisher.Mono;
    import spring.cloud.demo.spring.gateway.component.RedisRouteDefinitionRepository;
    
    import javax.annotation.Resource;
    
    /**
     * @auther: maomao
     * @DateT: 2019-11-03
     */
    @Service
    public class GatewayDynamicRouteService implements ApplicationEventPublisherAware {
    
        @Resource
        private RedisRouteDefinitionRepository redisRouteDefinitionRepository;
    
        private ApplicationEventPublisher applicationEventPublisher;
    
        /**
         * 增加路由
         * @param routeDefinition
         * @return
         */
        public int add(RouteDefinition routeDefinition) {
            redisRouteDefinitionRepository.save(Mono.just(routeDefinition)).subscribe();
            applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
            return 1;
        }
    
        /**
         * 更新
         * @param routeDefinition
         * @return
         */
        public int update(RouteDefinition routeDefinition) {
            redisRouteDefinitionRepository.delete(Mono.just(routeDefinition.getId()));
            redisRouteDefinitionRepository.save(Mono.just(routeDefinition)).subscribe();
            applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
            return 1;
        }
    
        /**
         * 删除
         * @param id
         * @return
         */
        public Mono<ResponseEntity<Object>> delete(String id) {
            return redisRouteDefinitionRepository.delete(Mono.just(id)).then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
                    .onErrorResume(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build()));
        }
    
    
        @Override
        public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
            this.applicationEventPublisher = applicationEventPublisher;
        }
    }
    
    

    1.2 application.yml

    application.yml配置要暴漏Gateway的所有端点,可以看考之前的配置信息。

    1.3 启动服务

    启动Spring cloud Gateway服务,先访问http://localhost:8100/actuator/gateway/routes,查看已有的路由配置信息。然后我们用postman请求add方法,http://localhost:8100/gateway/add,如果所示:

    注意截图中红框的内容。证明已经新增成功。

    这时我们在访问http://localhost:8100/actuator/gateway/routes查看结果。如果所示:

    同理我们可以访问update和delete方法,我这里就不过多描述了。

    总结

    自定义动态路由核心原理其实就要重写网关模块,也就是之前提到的RedisRouteDefinitionRepository类。我这里偷懒没有重新定义对应的实体类,这里需要注意的是,传入参数一定要按照application.yml中配置的格式,然后转成json,如果格式不正确会报错。

    代码地址

    gitHub地址


    《Srping Cloud 2.X小白教程》目录

    • 写作不易,转载请注明出处,喜欢的小伙伴可以关注公众号查看更多喜欢的文章。
    • 联系方式:4272231@163.com
  • 相关阅读:
    JQuery Ajax调用asp.net后台方法
    擦亮自己的眼睛去看SQLServer之简单Insert
    擦亮自己的眼睛去看SQLServer之简单Select
    SQL Server CONVERT() 函数
    给reporting services加个条件型的格式 (轉)
    优化SQL语句:in 和not in的替代方案
    技术不如你,但老板就是赏识他,为什么?
    LINQ to SQL活学活用(1):这要打破旧观念(轉)
    [续] MATLAB 混合编程——下篇:调用其它编程语言
    [精] MATLAB 混合编程——上篇:被其它编程语言调用
  • 原文地址:https://www.cnblogs.com/fengfujie/p/11820590.html
Copyright © 2011-2022 走看看