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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章