1. 程式人生 > >詳解SpringCloud-gateway動態路由兩種方式,以及路由載入過程

詳解SpringCloud-gateway動態路由兩種方式,以及路由載入過程

gateway配置路由主要有兩種方式,一種是用yml配置檔案,一種是寫程式碼裡,這兩種方式都是不支援動態配置的。如:

下面就來看看gateway是如何載入這些配置資訊的。

1 路由初始化

無論是yml還是程式碼,這些配置最終都是被封裝到RouteDefinition物件中。

一個RouteDefinition有個唯一的ID,如果不指定,就預設是UUID,多個RouteDefinition組成了gateway的路由系統。

所有路由資訊在系統啟動時就被載入裝配好了,並存到了記憶體裡。我們從原始碼來看看。

圓圈裡就是裝配yml檔案的,它返回的是PropertiesRouteDefinitionLocator,該類繼承了RouteDefinitionLocator,RouteDefinitionLocator就是路由的裝載器,裡面只有一個方法,就是獲取路由資訊的。該介面有多個實現類,分別對應不同方式配置的路由方式。

通過這幾個實現類,再結合上面的AutoConfiguration裡面的Primary資訊,就知道載入配置資訊的順序。

PropertiesRouteDefinitionLocator-->|配置檔案載入初始化| CompositeRouteDefinitionLocator
RouteDefinitionRepository-->|儲存器中載入初始化| CompositeRouteDefinitionLocator
DiscoveryClientRouteDefinitionLocator-->|註冊中心載入初始化| CompositeRouteDefinitionLocator

這是第一順序,就是從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是自帶介面能增刪改查配置的,這個網上有比較多的教程,隨便找個看看就明白了。譬如:

我發個類作為參考

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));
    }
}

相關推薦

SpringCloud-gateway動態路由方式以及路由載入過程

gateway配置路由主要有兩種方式,一種是用yml配置檔案,一種是寫程式碼裡,這兩種方式都是不支援動態配置的。如: 下面就來看看gateway是如何載入這些配置資訊的。 1 路由初始化 無論是yml還是程式碼,這些配置最終都是被封裝到RouteDefinition

Python拼接字串的七方式

幾乎任何一種程式語言,都把字串列為最基礎和不可或缺的資料型別。而拼接字串是必備的一種技能。今天,我跟大家一起來學習Python拼接字串的七種方式。 1、來自C語言的%方式 print('%s %s' % ('Hello', 'world')) >

07.Spring-配置-Spirng建立物件的三方式

測試類 package vc.helloworld.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springfra

動態代理 方式

代理類Proxy和RealSubject應該實現了相同的功能介面,在面向物件的程式設計之中,如果想要兩個物件實現相同的功能,有以下兩種方式: 1)  定義一個功能介面,然後代理類Proxy和真實類

PHP處理密碼的幾方式

在 PHP中,經常會對使用者身份進行認證。本文意在討論對密碼的處理,也就是對密碼的加密處理。 MD5 相信很多PHP開發者在最先接觸PHP的時候,處理密碼的首選加密函式可能就是MD5了,我當時就是這樣的: $password = md5($_POST["password"]); 上面這段程式碼是不是很

虛擬機器下 solr7.1 cloud 叢集搭建 (手動壓和官方指令碼方式

準備工作:   vmware workstation 12,OS使用的是ubuntu16.04,三臺虛擬機器搭建一個solr叢集,zookeeper共用這三臺虛擬機器組成zookeeper叢集。   zookeeper的版本為3.4.10,solr版本為7.1,不使用

二進位制:世界上有10懂二進位制不懂。

  目錄 一、十進位制整數轉二進位制、八進位制、十六進位制 二、二進位制轉十進位制、八進位制、十六進位制 三、十進位制、八進位制、十六進位制轉二進位制 四、二進位制小數與十進位制小數 一、十進位制整數轉二進位制、八進位制、十六進位制 首先是一張 十進位制

javascript消除字符串兩邊空格的方式面向對象和函數式編程

xxx 字符 name tco 由於 pre 字符串 對象實例 () 主要是javascript中消除字符串空格,比較兩種方式的不同 //面向對象,消除字符串兩邊空格 String.prototype.trim = function() { return this.rep

對Java代碼加密的方式防止反編譯

java加密使用Virbox Protector對Java項目加密有兩種方式,一種是對War包加密,一種是對Jar包加密。Virbox Protector支持這兩種文件格式加密,可以加密用於解析class文件的java.exe,並且可以實現項目源碼綁定制定設備,防止部署到客戶服務器的項目被整體拷貝。兩種加密方

SpringBoot配置Bean的方式--註解以及配置文件

cep tms ast doc ice print str PE 寫實 一、註解方式編寫實體類:package com.example.bean;import org.springframework.boot.context.properti

操作系統編程語言分類執行python方式變量內存管理定義變量的三個特征

什麽 height 取代 沒有 一個 Coding 開發 軟件 簡單 操作系統 1、什麽是操作系統 操作系統位於計算機硬件與應用軟件之間 是一個協調、管理、控制計算機硬件資源與軟件資源的控制程序2、為何要有操作系統? 1、控制硬件 2、把對硬件的復雜

python制作電腦定時關機辦公神器另含其它方式無需編程!

自動 靜態 自己的 基礎 win src 成功 他會 bsp 小編本人目前就是在電腦面前工作,常常會工作到淩晨兩三點還在為自己的夢想奮鬥著。有時在辦公椅上就稀裏糊塗睡著了,我相信有很多朋友和我一樣,這樣是很不好的。第一對身體不好,第二對電腦不好。 對身體方面,小編也

實現多線程的方式賣票場景親測可用

fig his nts conf desc end scrip thread tar 寫在開始 賣票場景:   多線程共同賣票,總票數在多個賣票窗口共享 實現方式:   1繼承Thread類;   2實現Runnable接口 正文開始 方式1 Thread繼承 packa

Maven執行TestNG的testcase 方式testng.xmltestngCase.java

詳情參照: http://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html 1.maven通過maven-surefire-plugin來執行maven專案中src/test/j

Axios傳參的方式表單資料和json字串(Form Data和Request Payload)

第一種方式:Form Data Axios引數配置: 1、引入 import Qs from 'qs' 2、 return request({ headers: { 'Content-Type': 'application/x-www-form-

java之執行緒建立的方式狀態和匿名內部類建立子類或實現類物件

一.匿名內部類建立子類或實現類物件 new Test(){} 相當於建立了Test類的子類物件 並且沒有類名 建立介面實現類 new 介面名() {};介面實現類的物件 注意 : new 後邊是類或者介面名 大括號內是類或者介面中的方法 public

PHP實現無限極分類的方式遞迴和引用

https://blog.csdn.net/falcom_fans/article/details/75579663 說到無限極分類,比較常見的做法是在建表的時候,增加一個PID欄位用來區別自己所屬的分類   由於展示資料的時候,需要表達出這種所屬關係,所以必然要在讀取資料

android開啟另外的app方式內建到自己本身的app重新開啟app

android開啟另外的app兩種方式,內建到自己本身的app空間,重新開啟app空間, 目錄 1、內建到自己本身的app空間, 被開啟的app的activity的主配置檔案的程式碼編寫、 <activity android:n

KEIL / MDK生成BIN檔案的方式直接複製就能用

在After Build/Rebuild選項卡中,勾選 "Run # 1",在後面輸入框寫入bin檔案生成方式,如下 圖1 第一種方式:設定絕對路徑(個人實踐過,沒有成功,不知道為什麼) "D:\Program Files\MDK516\ARM\ARMCC\bin\

Java ZXing 生成QRCode二維碼的方式可設定圖片大小和外白色邊框大小

QRGen在ZXing基礎上開發,這個庫使得利用Java生成QR碼變為小菜一碟。它需要依賴ZXing,所以生成圖案時你同時需要ZXing和QRGen的jar包。 QR 碼最常見的應用便是為網站中一個特定的網頁或下載頁帶來流量。因此,QR碼常常會編碼URL或網站地址,使用者可以通過手機攝像頭掃描,並在其瀏覽器中