SpringCloud详解06-网关

系列文章目录



前言

接上篇,本篇记录网关组件的使用


一、网关

1、什么是网关?

就是网络请求的统一入口,网关的本质就是过滤器的组合

2、为什么需要网关?

  • 如果我们的有成千上万个服务,我们在请求每个服务的时候都需要进行认证,难度与工作量可想而知,要控制用户对于整个服务的访问次数的限制。

  • 如果没有统一的入口,那么前端在与服务端交互的时候定位到各个服务,假设服务器端作服务的重构,那么前端也得跟着一起修改。

3、SpringCloudAlibaba GateWay

gateway是spring cloud的第二代网关,其性能是zuul的1.6倍左右,其内部是基于netty、reactor(多路复用)、webflux进行构建,性能强大。gateway需要从注册中心获取服务,然后通过网关来调用对应的服务。但是gateway不在web环境下运行,也就是说不能打成war包放在tomcat下运行

二、快速入门

1.导入依赖

提示:所有的案例代码,都是基于前几篇的基础上

创建springcloudalibaba-micro-service-gateway-9090子工程(使用maven创建),导入依赖

<?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">
    <parent>
    	<!--此处修改了父工程的组织id-->
        <artifactId>springcloudalibaba-GateWay</artifactId>
        <groupId>com.lzl</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloudalibaba-micro-service-gateway</artifactId>

    <dependencies>

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

            <exclusions>
                <!-- 排除掉springmvc相关的配置信息 -->
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-webmvc</artifactId>
                </exclusion>
                <!-- 排除掉tomcat相关的配置 -->
                <exclusion>
                    <groupId>org.springframework.bootk</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat.embed</groupId>
                    <artifactId>tomcat-embed-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat.embed</groupId>
                    <artifactId>tomcat-embed-el</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat.embed</groupId>
                    <artifactId>tomcat-embed-websocket</artifactId>
                </exclusion>
            </exclusions>

        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.lzl</groupId>
            <artifactId>springcloudalibaba-micro-service-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>
        <!--nacos配置管理-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
    </dependencies>

</project>

2.创建 GateWayApplication 启动类

package com.lzl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;


/**
 * --效率,是成功的核心关键--
 *
 * @Author lzl
 * @Date 2023/3/15 15:16
 */
@SpringBootApplication
public class GatewayApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(GatewayApplication.class,args);

    }
}

3.创建application.yml文件

spring:
  application:
    name: micro-service-gateway # 网关名称

  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        enabled: true
        register-enabled: false # 不注册到nacos上

    gateway:
      discovery:
        locator:
          enabled: true #开启网关端口并使用服务方的名称来访问
          #之前:http://localhost:8080/feign/getById?id=1
          #现在:http://localhost:9090/consumer/feign/getById?id=1
      
server:
  port: 9090

4.启动服务方,消费方,网关,进行测试

通过网关配置的端口9090,和消费方的服务名访问消费方接口

在这里插入图片描述
成功

三、谓词配置

谓词(predicate)是gateway内置的的一下关于请求相关的处理,在application.yml中增加routes的配置
注意:谓词配置后,访问时就不需要再写服务名称了,因为谓词里面已经配置过了
配置谓词之前:http://localhost:9090/consumer/feign/findAll
配置谓词之后:http://localhost:9090/feign/findAll

简言之,谓词配置,就是在原本访问的基础上,加一些条件。

yml文件的内容:

server:
  port: 9090

spring:
  application:
    name: micro-service-gateway # 网关名称

  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        enabled: true
        register-enabled: false # 不注册到nacos上

    gateway:
      discovery:
        locator:
          enabled: true #开启网关端口并使用服务方的名称来访问
          #之前:http://localhost:8080/feign/getById?id=1
                #现在:http://localhost:9090/consumer/feign/getById?id=1

      routes:
        # id可以不是服务名,名字任意,但是不能重复,推荐使用服务名的方式
        - id: consumer
          # uri才是控制着某个具体的访问到达我们特定的服务
          uri: lb://consumer
          # 谓词: 就是满足的条件,可以在org.springframework.cloud.gateway.handler.predicate这个包下
          predicates:
            # 配置访问消费方controller的一级目录名称,这样就可以通过http://localhost:9090/feign/findAll来访问了
            - Path=/feign/**
            # 请求的参数中必须携带origin这个参数名,参数值符合[a-zA-Z]+ 这个正则
            - Query=origin,[a-zA-Z]+
            # 请求的方式
            - Method=get,post
            # 设置时间区间内访问: 2020年12月31日 - 2030年12月31日,可以访问,+08:00表示时区
            - After=2020-12-31T00:00:00+08:00[Asia/Shanghai]
            - Before=2030-12-31T00:00:00+08:00[Asia/Shanghai]
            # 描述IP在10.8.13.1~10.8.13.255之间的地址才可以访问
          	- RemoteAddr=10.8.162.0/24
          	# 请求的头中必须得携带token, value值符合[a-zA-Z0-9]+ 这个正则
          	#- Header=token,[a-zA-Z0-9]+


在这里插入图片描述

不满足谓词条件时,访问不到对应的接口,origin必须是英文大小写,数字不满足谓词条件
在这里插入图片描述

满足谓词条件,可以访问

四、过滤器配置

1.令牌桶算法

GateWay提供了很多内置的过滤器让我们使用,具体的过滤器在spring-cloud-gateway-core-2.1.2.RELEASE.jar下的org.springframework.cloud.gateway.filter.factory包下,接下来我们挑其中一个非常常用的过滤来讲解用法,在实际的开发过程中,有这样一种业务需求,就是限制同一个IP对服务器频繁的请求,例如我们限制每个IP在每秒只能访问3次,那么要怎么实现呢?其实spring-boot已经帮我们实现好了一个,只需要做一定的配置就可以了。
IP限制的原理就是令牌桶算法, 随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token,如果桶已经满了就不再加了。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。如下图所示:

在这里插入图片描述

2.使用步骤

(1)添加依赖

在springcloudalibaba-micro-service-gateway-9090工程中添加依赖

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

(2)创建RedisHostKeyResovler类

package com.lzl.resolver;


import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
 * --效率,是成功的核心关键--
 *
 * @Author lzl
 * @Date 2023/3/17 14:12
 */

@Component
public class RedisHostKeyResolver implements KeyResolver {
    
    

    /**
     * webflux:
     *      Mono: 用于返回单个值
     *      Flux: 用于返回集合数据
     */
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
    
    
        //获取用户的访问的 ip
        String host = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
        return Mono.just(host); //构建 Mono<String>
    }
}

(3)在application.yml中增加filters和redis的配置

server:
  port: 9090

spring:
  application:
    name: micro-service-gateway # 网关名称

  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        enabled: true
        register-enabled: false # 不注册到nacos上

    gateway:
      discovery:
        locator:
          enabled: true #开启网关端口并使用服务方的名称来访问
          #之前:http://localhost:8080/feign/getById?id=1002
                #现在:http://localhost:9090/micro-service-consumer/feign/getById?id=1002

      routes:
        # id可以不是服务名,名字任意,但是不能重复,推荐使用服务名的方式
        - id: consumer
          # uri才是控制着某个具体的访问到达我们特定的服务
          uri: lb://consumer
          # 谓词: 就是满足的条件,可以在org.springframework.cloud.gateway.handler.predicate这个包下
          predicates:
            # 配置访问消费方controller的一级目录名称,这样就可以通过http://localhost:9090/feign-user-consumer/getUsers来访问了
            - Path=/feign/**
            # 请求的参数中必须携带origin这个参数名,参数值符合[a-zA-Z]+ 这个正则
            - Query=origin,[a-zA-Z]+
            # 请求的方式
            - Method=get,post
            # 设置时间区间内访问: 2020年12月31日 - 2030年12月31日,可以访问,+08:00表示时区
            #- After=2020-12-31T00:00:00+08:00[Asia/Shanghai]
            #- Before=2030-12-31T00:00:00+08:00[Asia/Shanghai]
            # 描述IP在10.8.13.1~10.8.13.255之间的地址才可以访问
            #- RemoteAddr=10.8.162.0/24
            # 请求的头中必须得携带token, value值符合[a-zA-Z0-9]+ 这个正则
            #- Header=token,[a-zA-Z0-9]+


          filters:
            # RequestRateLimiter是固定值
            - name: RequestRateLimiter
              args:
                # key-resolver是用于限流的bean对象,通过SpEL的方式 #{@XXX} 取出spring容器中的bean
                keyResolver: '#{@redisHostKeyResolver}'
                # 每秒往令牌桶中存放的数量
                redis-rate-limiter.replenishRate: 1
                # 令牌桶中最多的令牌的数量
                redis-rate-limiter.burstCapacity: 3
                #redis配置
    redis:
      host: 127.0.0.1
      port: 6379

(4)启动Redis,测试

启动windows版redis
在这里插入图片描述

可以让局域网内的其他用户访问本机:http://本机ip:9090/feign/findAll?origin=abc
本机访问如下:
在这里插入图片描述

五、自定义全局过滤器

1.导入依赖

在GateWay子工程导入下边依赖

<dependency>
    <groupId>com.qf</groupId>
    <artifactId>springcloudalibaba-micro-service-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.79</version>
</dependency>

2.创建全局过滤器类

package com.lzl.filters;


import com.alibaba.fastjson.JSON;
import com.lzl.utils.JsonResult;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;
/**
 * --效率,是成功的核心关键--
 *
 * @Author lzl
 * @Date 2023/3/17 15:00
 */

//创建多个全局过滤器
@Configuration
public class FilterConfig {
    
    

    @Bean
    @Order(-100)//正数值越小,负数的绝对值越大,优先级越高
    public GlobalFilter loginFilter(){
    
    
        return new GlobalFilter() {
    
    
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
                System.out.println("-100");

                //获取传入的用户名和密码
                ServerHttpRequest request = exchange.getRequest();
                //获取所有参数
                MultiValueMap<String, String> queryParams = request.getQueryParams();
                //获取前端传过来的name以及password
                List<String> nameList = queryParams.get("name");
                List<String> passwordList = queryParams.get("password");
                System.out.println(nameList);
                //判断
                if(nameList !=null && nameList.size() > 0 && passwordList !=null && passwordList.size() > 0){
    
    
                    //模拟数据库查询
                    if("jack".equals(nameList.get(0)) && "123".equals(passwordList.get(0))){
    
    
                        //放行
                        return chain.filter(exchange);
                    }else{
    
    
                        //参数输入错误
                        JsonResult jsonResult = JsonResult.error();
                        jsonResult.setData("name or password is error");

                        //返回
                        ServerHttpResponse response = exchange.getResponse();
                        String jsonString = JSON.toJSONString(jsonResult);
                        DataBuffer dataBuffer = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
                        return response.writeWith(Mono.just(dataBuffer));
                    }

                }else{
    
    
                    //参数输入错误
                    JsonResult jsonResult = JsonResult.error();
                    jsonResult.setData("name or password is null");
                    //返回
                    ServerHttpResponse response = exchange.getResponse();
                    String jsonString = JSON.toJSONString(jsonResult);
                    DataBuffer dataBuffer = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
                    return response.writeWith(Mono.just(dataBuffer));
                }

            }
        };
    }

    @Bean
    @Order(-90)//正数值越小,负数的绝对值越大,优先级越高
    public GlobalFilter otherFilter(){
    
    
        return new GlobalFilter() {
    
    
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
                System.out.println("-90");
                return chain.filter(exchange);
            }
        };
    }

}

3.测试

浏览器访问http://localhost:9090/feign/getUsers?origin=qwer&name=jack&password=123,进行测试
在这里插入图片描述
没有携带过滤器指定参数时,报错
在这里插入图片描述
参数不正确,报错

在这里插入图片描述
参数输入正确,成功访问


总结

本篇记录了,springcloudAlibab的网关组件GateWay组件的使用,更多内容见下篇

猜你喜欢

转载自blog.csdn.net/l_zl2021/article/details/129627129