zoukankan      html  css  js  c++  java
  • 详解SpringCloudgateway动态路由两种方式,以及路由加载过程

    gateway配置路由主要有两种方式,一种是用yml配置文件,一种是写代码里,这两种方式都是不支持动态配置的。如:

    下面就来看看gateway是如何加载这些配置信息的。

    1 路由初始化

    无论是yml还是代码,这些配置最终都是被封装到RouteDefinition对象中。

    一个RouteDefinition有个唯一的ID,如果不指定,就默认是UUID,多个RouteDefinition组成了gateway的路由系统。

    所有路由信息在系统启动时就被加载装配好了,并存到了内存里。我们从源码来看看。

    圆圈里就是装配yml文件的,它返回的是PropertiesRouteDefinitionLocator,该类继承了RouteDefinitionLocator,RouteDefinitionLocator就是路由的装载器,里面只有一个方法,就是获取路由信息的。该接口有多个实现类,分别对应不同方式配置的路由方式。

    通过这几个实现类,再结合上面的AutoConfiguration里面的Primary信息,就知道加载配置信息的顺序。

    PropertiesRouteDefinitionLocator-->|配置文件加载初始化| CompositeRouteDefinitionLocator RouteDefinitionRepository-->|存储器中加载初始化| CompositeRouteDefinitionLocator DiscoveryClientRouteDefinitionLocator-->|注册中心加载初始化| CompositeRouteDefinitionLocator

    参考:https://www.jianshu.com/p/b02c7495eb5e

    https://blog.csdn.net/X5fnncxzq4/article/details/80221488

    这是第一顺序,就是从CachingRouteLocator中获取路由信息,我们可以打开该类进行验证。

    不管发起什么请求,必然会走上面的断点处。请求一次,走一次。这是将路由信息缓存到了Map中。配置信息一旦请求过一次,就会被缓存到上图的CachingRouteLocator类中,再次发起请求后,会直接从map中读取。

    如果想动态刷新配置信息,就需要发起一个RefreshRoutesEvent的事件,上图的cache会监听该事件,并重新拉取路由配置信息。

    通过下图,可以看到如果没有RouteDefinitionRepository的实例,则默认用InMemoryRouteDefinitionRepository。而做动态路由的关键就在这里。即通过自定义的RouteDefinitionRepository类,来提供路由配置信息。

    例如:

    在getRouteDefinitions方法返回你自定义的路由配置信息即可。这里可以用数据库、nosql等等任意你喜欢的方式来提供。而且配置信息修改后,发起一次RefreshRoutesEvent事件即可让配置生效。这就是动态配置路由的核心所在,下面来看具体代码实现。

    2 基于数据库、缓存的动态路由

    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>com.maimeng</groupId>
        <artifactId>apigateway</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>apigateway</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <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>Finchley.SR1</spring-cloud.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-webflux</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-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.51</version>
            </dependency>
            <!--<dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <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>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    
    </project>

    注意这里是SR1,经测试SR2有bug,会出问题。

    @Configuration
    public class RedisConfig {
    
        @Bean(name = {"redisTemplate", "stringRedisTemplate"})
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
            StringRedisTemplate redisTemplate = new StringRedisTemplate();
            redisTemplate.setConnectionFactory(factory);
            return redisTemplate;
        }
    
    }

    核心类:

    @Component
    public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
    
        public static final String GATEWAY_ROUTES = "geteway_routes";
    
        @Resource
        private StringRedisTemplate redisTemplate;
    
        @Override
        public Flux<RouteDefinition> getRouteDefinitions() {
            List<RouteDefinition> routeDefinitions = new ArrayList<>();
            redisTemplate.opsForHash().values(GATEWAY_ROUTES).stream()
                    .forEach(routeDefinition -> routeDefinitions.add(JSON.parseObject(routeDefinition.toString(), RouteDefinition.class)));
            return Flux.fromIterable(routeDefinitions);
        }
    
        @Override
        public Mono<Void> save(Mono<RouteDefinition> route) {
            return null;
        }
    
        @Override
        public Mono<Void> delete(Mono<String> routeId) {
            return null;
        }
    
    }

    主要是在get方法里,此处从redis里获取配置好的Definition。

    然后我们的工作就是将配置信息,放到redis里即可。

    下面就是我模拟的一个配置,等同于在yml里

    spring:
      cloud:
        gateway:
          routes:
          - id: header
            uri: http://localhost:8888/header
            filters:
            - AddRequestHeader=header, addHeader
            - AddRequestParameter=param, addParam
            predicates:
            - Path=/jd
    @Resource
        private StringRedisTemplate redisTemplate;
        @PostConstruct
        public void main() {
            RouteDefinition definition = new RouteDefinition();
            definition.setId("id");
            URI uri = UriComponentsBuilder.fromHttpUrl("http://127.0.0.1:8888/header").build().toUri();
           // URI uri = UriComponentsBuilder.fromHttpUrl("http://baidu.com").build().toUri();
            definition.setUri(uri);
    
            //定义第一个断言
            PredicateDefinition predicate = new PredicateDefinition();
            predicate.setName("Path");
    
            Map<String, String> predicateParams = new HashMap<>(8);
            predicateParams.put("pattern", "/jd");
            predicate.setArgs(predicateParams);
    
            //定义Filter
            FilterDefinition filter = new FilterDefinition();
            filter.setName("AddRequestHeader");
            Map<String, String> filterParams = new HashMap<>(8);
            //该_genkey_前缀是固定的,见org.springframework.cloud.gateway.support.NameUtils类
            filterParams.put("_genkey_0", "header");
            filterParams.put("_genkey_1", "addHeader");
            filter.setArgs(filterParams);
    
            FilterDefinition filter1 = new FilterDefinition();
            filter1.setName("AddRequestParameter");
            Map<String, String> filter1Params = new HashMap<>(8);
            filter1Params.put("_genkey_0", "param");
            filter1Params.put("_genkey_1", "addParam");
            filter1.setArgs(filter1Params);
    
            definition.setFilters(Arrays.asList(filter, filter1));
            definition.setPredicates(Arrays.asList(predicate));
    
            System.out.println("definition:" + JSON.toJSONString(definition));
            redisTemplate.opsForHash().put(GATEWAY_ROUTES, "key", JSON.toJSONString(definition));
        }

    定义好后,将其放到redis里,之后启动项目访问/jd,再启动后台的localhost:8888项目。即可进行验证。

    之后如果要动态修改配置,就可以通过类似于上面的方式,来获取json字符串,然后将字符串放到redis里进行替换。替换后,需要通知gateway主动刷新一下。

    刷新时,可以定义一个controller,然后调用一下notifyChanged()方法,就能完成新配置的替换了。

    3 通过REST接口

    gateway是自带接口能增删改查配置的,这个网上有比较多的教程,随便找个看看就明白了。譬如:

    http://springcloud.cn/view/368

    我发个类作为参考

    package com.maimeng.apigateway.route;
    
    import com.alibaba.fastjson.JSON;
    import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
    import org.springframework.cloud.gateway.filter.FilterDefinition;
    import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
    import org.springframework.cloud.gateway.route.RouteDefinition;
    import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
    import org.springframework.context.ApplicationEventPublisher;
    import org.springframework.context.ApplicationEventPublisherAware;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.web.util.UriComponentsBuilder;
    import reactor.core.publisher.Mono;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.Resource;
    import java.net.URI;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    
    import static com.maimeng.apigateway.repository.RedisRouteDefinitionRepository.GATEWAY_ROUTES;
    
    /**
     * @author wuweifeng wrote on 2018/10/25.
     */
    @Service
    public class DynamicRouteService implements ApplicationEventPublisherAware {
    
        @Resource
        private RouteDefinitionWriter routeDefinitionWriter;
    
        private ApplicationEventPublisher publisher;
    
        private void notifyChanged() {
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        }
    
    
        /**
         * 增加路由
         *
         */
        public String add(RouteDefinition definition) {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            notifyChanged();
            return "success";
        }
    
    
        /**
         * 更新路由
         */
        public String update(RouteDefinition definition) {
            try {
                this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
            } catch (Exception e) {
                return "update fail,not find route  routeId: " + definition.getId();
            }
            try {
                routeDefinitionWriter.save(Mono.just(definition)).subscribe();
                notifyChanged();
                return "success";
            } catch (Exception e) {
                return "update route  fail";
            }
    
    
        }
    
        /**
         * 删除路由
         *
         */
        public String delete(String id) {
            try {
                this.routeDefinitionWriter.delete(Mono.just(id));
    
                notifyChanged();
                return "delete success";
            } catch (Exception e) {
                e.printStackTrace();
                return "delete fail";
            }
    
        }
    
        @Override
        public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
            this.publisher = applicationEventPublisher;
        }
    
        @Resource
        private StringRedisTemplate redisTemplate;
        @PostConstruct
        public void main() {
            RouteDefinition definition = new RouteDefinition();
            definition.setId("id");
            URI uri = UriComponentsBuilder.fromHttpUrl("http://127.0.0.1:8888/header").build().toUri();
           // URI uri = UriComponentsBuilder.fromHttpUrl("http://baidu.com").build().toUri();
            definition.setUri(uri);
    
            //定义第一个断言
            PredicateDefinition predicate = new PredicateDefinition();
            predicate.setName("Path");
    
            Map<String, String> predicateParams = new HashMap<>(8);
            predicateParams.put("pattern", "/jd");
            predicate.setArgs(predicateParams);
    
            //定义Filter
            FilterDefinition filter = new FilterDefinition();
            filter.setName("AddRequestHeader");
            Map<String, String> filterParams = new HashMap<>(8);
            //该_genkey_前缀是固定的,见org.springframework.cloud.gateway.support.NameUtils类
            filterParams.put("_genkey_0", "header");
            filterParams.put("_genkey_1", "addHeader");
            filter.setArgs(filterParams);
    
            FilterDefinition filter1 = new FilterDefinition();
            filter1.setName("AddRequestParameter");
            Map<String, String> filter1Params = new HashMap<>(8);
            filter1Params.put("_genkey_0", "param");
            filter1Params.put("_genkey_1", "addParam");
            filter1.setArgs(filter1Params);
    
            definition.setFilters(Arrays.asList(filter, filter1));
            definition.setPredicates(Arrays.asList(predicate));
    
            System.out.println("definition:" + JSON.toJSONString(definition));
            redisTemplate.opsForHash().put(GATEWAY_ROUTES, "key", JSON.toJSONString(definition));
        }
    }

    转载 https://cloud.tencent.com/developer/article/1384112
  • 相关阅读:
    『深度应用』NLP机器翻译深度学习实战课程·零(基础概念)
    Sorl初始
    Hadoop简介
    lucene的分词器宝典
    Lucene 更新、删除、分页操作以及IndexWriter优化
    Lucene 初步 之 HelloWorld
    lucene介绍和存储介绍
    Spring 集成rabbiatmq
    RabbitMQ 之消息确认机制(事务+Confirm)
    RabbitMQ 的路由模式 Topic模式
  • 原文地址:https://www.cnblogs.com/javalinux/p/14265305.html
Copyright © 2011-2022 走看看