SpringCloudAlibaba 五、使用 Gateway 網關實現服務轉發及 網關集羣及 動態修改配置( 本文註冊中心爲 Nacos)

一、Gateway 網關描敘及說明

1、微服務網關描敘

微服務網關是整個微服務API請求的入口,可以實現日誌攔截、權限控制、解決跨域問題、
限流、熔斷、負載均衡、黑名單與白名單攔截、授權等。

2、Gateway與Zuul的區別?

Zuul網關屬於netfix公司開源的產品屬於第一代微服務網關
Gateway屬於SpringCloud自研發的第二代微服務網關
相比來說SpringCloudGateway性能比Zuul性能要好:
注意:Zuul基於Servlet實現的,阻塞式的Api, 不支持長連接。
SpringCloudGateway基於Spring5構建,能夠實現響應式非阻塞式的Api,支持長連接,能夠更好的整合Spring體系的產品。

3、過濾器與網關的區別

過濾器用於攔截單個服務
網關攔截整個的微服務

二、Gateway 網關環境搭建(Nacos+Gateway)

1、創建spring boot項目(2.0.1)

在這裏插入圖片描述

2、pom.xml 依賴

注意使用的是 webflux,不是web

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <artifactId>sprng-cloud-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sprng-cloud-gateway</name>
    <description>Demo project for Spring Boot</description>
    <dependencies>
    
        <!-- web組件 webflux -->
        <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>
        
        <!-- nacos 註冊中心 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>0.2.2.RELEASE</version>
        </dependency>
        
        <!-- gateway 網關-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3、application.yml 配置

### 網關端口號
server:
  port: 80
spring:
  application:
    ### 服務名稱,不能使用下劃線
    name: alibaba-gateway
  cloud:
    ### 註冊中心
    nacos:
      discovery:
        server-addr: 192.168.177.128:8848
    ### 網關
    gateway:
      ### 開啓基於註冊中心的路由表。gateway可以通過開啓以下配置來打開根據服務的serviceId來匹配路由,
      ### 默認是大寫,如果需要小寫serviceId,則配置# spring.cloud.gateway.locator.lowerCaseServiceId:true
      discovery:
        locator:
          enabled: true
      ###路由策略
      routes:
        ### 配置方式一:絕對路徑
          ### 路由id, 如果不寫的話默認是uuid 唯一值
        - id: baidu
          ####轉發http://www.mayikt.com/
          uri: http://www.baidu.com/
          ### 匹配規則
          predicates:
            - Path=/baidu/**
        ### 配置方式二:根據serviceId 動態獲取url路徑
        - id: member
          #### 基於lb負載均衡形式轉發, 而是lb://開頭,加上serviceId
          uri: lb://alibaba-server
          ### 這個是過濾器,對應的是filters 配置,有寫好的過濾器,應該也可以自定義
          filters:
            - StripPrefix=1
          ### 匹配規則,可以配置多個,使用正則匹配,請求地址攜帶***(/***/)跳轉到我們配置的uri,如:uri/***
          predicates:
            - Path=/alibaba-server/**

4、啓動類

@SpringBootApplication
class SprngCloudGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(SprngCloudGatewayApplication.class, args);
    }
}

5、測試效果

配置方式一:訪問 127.0.0.1/baidu 會直接跳轉到百度首頁
配置方式二:
alibaba-server 是我本地的服務,已註冊到nacos,並提供了getUserId 接口
如下:使用網關80端口+ 服務id ,轉發到具體的服務
在這裏插入圖片描述
nacos 服務列表
在這裏插入圖片描述

三、Gateway 網關過濾器

gateway 過濾器分爲倆種。GatewayFilter 與 GlobalFilter。

GlobalFilter :全局過濾器
GatewayFilter :將應用到單個路由或者一個分組的路由上。
還有內置的過濾器斷言機制

1、全局過濾器(token 驗證)

有token 參數放行,無返回錯誤信息

/**
 * TODO  全局過濾器
 *
 * @return
 */
@Component
public class TestFilter implements GlobalFilter, Ordered {

    @Bean
    public GlobalFilter TestFilter() {
        return new TestFilter();
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("============執行全局過濾器==============");
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (token == null || token.isEmpty()) {
            // 驗證不通過,返回錯誤信息
            String msg = "token not is null ";
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.BAD_REQUEST);
            DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
            return response.writeWith(Mono.just(buffer));
        }
        // 驗證通過,放行
        return chain.filter(exchange);
    }


    /**
     * TODO  過濾順序指定,過濾Web處理程序會將的所有實例GlobalFilter和所有特定GatewayFilter於路由的實例添加到過濾器鏈中。
     */
    @Override
    public int getOrder() {
        return -1;
    }
}

網關集羣,那麼具體的服務怎麼獲取到是哪個網關轉發過來的呢

2、網關傳遞自定義參數到轉發的服務


/**
 * TODO  全局過濾器
 *
 * @return
 */
@Component
public class TestFilter implements GlobalFilter{

    @Value("${server.port}")
    private String serverPort;

    @Bean
    public GlobalFilter TestFilter() {
        return new TestFilter();
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      
        // 添加當前網關消息request 在轉發到具體的服務,如:ip,端口
        ServerHttpRequest request = exchange.getRequest().mutate().header("serverPort", serverPort).build();
        exchange.mutate().request(request).build();
        // 驗證通過,放行
        return chain.filter(exchange);
    }

}

3、自定義過濾器(暫不說明)

四、Gateway 網關集羣

如需要添加黑名單,白名單功能,自行配置可獲取用戶真實ip,默認無法獲取

1、使用Nginx/集羣

可使用Nginx 或者 lvs虛擬vip 訪問增加系統的高可用
在這裏插入圖片描述

2、nginx 相關配置nginx.conf

hosts 文件添加配置

127.0.0.1 a80.www.baidu.com

hosts 文件路徑 : C:\windows\system32\drivers\etc

nginx.conf 配置參考

upstream mysvr { 
      server 127.0.0.1:8080;               # 這裏定義可以加 http://  
      server 127.0.0.1:8081;
    }
server {
      listen       80;
      server_name  a80.www.baidu.com;
      location  ~*^.+$ {                   # 請求的url過濾,正則匹配,~爲區分大小寫,~*爲不區,區分大小寫。 默認 /
           proxy_pass  http://mysvr;       # 請求轉向mysvr 定義的服務器列表 
    }

配置說明:攔截服務地址爲 a80.www.baidu.com,端口爲 80的 url ,
通過 mysvr 找到----> upstream mysvr 對應的配置
並輪詢upstream mysvr 下配置的服務器
服務器的請求順序爲:ABABABABAB…

五、Gateway 動態配置

5.1、基於配置中心實現動態配置

直接添加配置信息到 配置中心即可

5.2、基於數據庫 (數據層框架採用Jpa)實現動態配置

下面介紹數據庫配置方式

1、添加數據表(mysql)


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for gateway_config
-- ----------------------------
DROP TABLE IF EXISTS `gateway_config`;
CREATE TABLE `gateway_config`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `route_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '路由Id',
  `route_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '名稱',
  `route_pattern` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '規則',
  `route_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '類型(1:絕對路徑,0:根據服務serverId匹配)',
  `route_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'url地址',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

參考配置

INSERT INTO `gateway_config` VALUES (1, 'baidu', '跳轉百度首頁', '/baidu/**', '1', 'https://www.baidu.com');
INSERT INTO `gateway_config` VALUES (2, 'alibaba-server', '生產者測試服務', '/alibaba-server/**', '0', 'lb://alibaba-server');

在這裏插入圖片描述

2、pom.xml 添加數據源等依賴

注意這裏druid 的版本爲1.1.10 版本會報錯

    <!-- 阿里巴巴數據源  -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>
        <!-- jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
            <scope>runtime</scope>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

3、yml 新添加數據源配置

spring:

  ### 數據庫連接信息
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://12.7.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useTimezone=true&serverTimezone=GMT%2B8
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    #最大活躍數
    maxActive: 20
    #初始化數量
    initialSize: 1
    #最大連接等待超時時間
    maxWait: 60000
    #打開PSCache,並且指定每個連接PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    #通過connectionProperties屬性來打開mergeSql功能;慢SQL記錄
    #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    minIdle: 1
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: select 1 from dual
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    #配置監控統計攔截的filters,去掉後監控界面sql將無法統計,'wall'用於防火牆
    filters: stat, wall, log4j
  main:
    allow-bean-definition-overriding: true #當遇到同樣名字的時候,是否允許覆蓋註冊
  jpa:
    hibernate:
      ddl-auto: update  # 第一次建表create  後面用update
    show-sql: true

4、GateWayEntity

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Data
@Entity
@Table(name = "gateway_config")
public class GateWayEntity {

    @Id
    private Long id;
    private String routeId;
    private String routeName;
    private String routePattern;
    private String routeType;
    private String routeUrl;
}


5、dao層

import com.example.alibabaclient.entity.GateWayEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Component;


@Component
public interface GatewayConfigDao extends JpaRepository<GateWayEntity,Long>, JpaSpecificationExecutor<GateWayEntity> {

}

6、service層

import com.example.alibabaclient.dao.GatewayConfigDao;
import com.example.alibabaclient.entity.GateWayEntity;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class GatewayService implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher publisher;

    @Autowired
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    private RouteDefinitionWriter routeDefinitionWriter;
    @Autowired
    private GatewayConfigDao gatewayConfigDao;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    public void initAllRoute() {
        // 從數據庫查詢配置的網關配置
        List<GateWayEntity> gateWayEntities = gatewayConfigDao.findAll();
        for (GateWayEntity gw : gateWayEntities) {
            loadRoute(gw);
        }
    }


    /**
     * TODO  配置更新
     */
    private String loadRoute(GateWayEntity gateWayEntity) {
        RouteDefinition definition = new RouteDefinition();
        Map<String, String> predicateParams = new HashMap<>(8);
        PredicateDefinition predicate = new PredicateDefinition();
        FilterDefinition filterDefinition = new FilterDefinition();
        Map<String, String> filterParams = new HashMap<>(8);
        // 如果配置路由type爲0的話,則從註冊中心獲取服務
        URI uri = null;
        if (gateWayEntity.getRouteType().equals("0")) {
            uri = uri = UriComponentsBuilder.fromUriString("lb://" + gateWayEntity.getRouteUrl() + "/").build().toUri();
        } else {
            uri = UriComponentsBuilder.fromHttpUrl(gateWayEntity.getRouteUrl()).build().toUri();
        }
        // 定義的路由唯一的id
        definition.setId(gateWayEntity.getRouteId());
        predicate.setName("Path");
        //路由轉發地址
        predicateParams.put("pattern", gateWayEntity.getRoutePattern());
        predicate.setArgs(predicateParams);

        // 名稱是固定的, 路徑去前綴
        filterDefinition.setName("StripPrefix");
        filterParams.put("_genkey_0", "1");
        filterDefinition.setArgs(filterParams);
        definition.setPredicates(Arrays.asList(predicate));
        definition.setFilters(Arrays.asList(filterDefinition));
        definition.setUri(uri);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }
}

6、controller 層

import com.example.alibabaclient.service.GatewayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * TODO  路由配置更新
 */
@RestController
public class RouteController {

    @Autowired
    private  GatewayService gatewayService;

    @GetMapping("/route")
    public String route(){
        gatewayService.initAllRoute();
        return "success";
    }
}

7、測試配置

1、添加數據庫數據(如第一步添加了忽視)

INSERT INTO `gateway_config` VALUES (1, 'baidu', '跳轉百度首頁', '/baidu/**', '1', 'https://www.baidu.com');
INSERT INTO `gateway_config` VALUES (2, 'alibaba-server', '生產者測試服務', '/alibaba-server/**', '0', 'lb://alibaba-server');

加載配置,訪問接口:http://127.0.0.1/route ,訪問: http://127.0.0.1/baidu 直接跳到百度首頁,表示配置成功

8、測試新添加/修改

1、數據庫添加一條新數據

INSERT INTO `gateway_config` VALUES (3, 'gitee', '跳轉碼雲', '/gitee/**', '1', 'https://gitee.com/');

加載配置,訪問接口:http://127.0.0.1/route ,訪問: http://127.0.0.1/gitee 直接跳到碼雲首頁,表示動態配置成功

到此就結束了,該配置直接修改、添加、刪除配置信息後,調用接口 /route 後都會立即生效

發佈了198 篇原創文章 · 獲贊 30 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章