SpringCloud GateWay 学习

SpringCloud GateWay

一 API 网关
1.什么是API 网关?
API 网关的作用就是把各个服务对外提供的API汇聚起来,让外界看起来是一个统一的入口,同时也可以在网关中提供额外的功能。
总结:网关就是所有项目的一个统一入口

2.网关组成

网关=路由转发+过滤器(编写额外的功能)

2.1 路由转发
接收外界请求,通过网关的路由转发,转发到后端的服务上。
如果只有这一个功能看起来跟nginx反向代理服务器很像,外界访问nginx,由nginx做负载均衡,后把请求转发到对应的服务器上。
2.2 过滤器
网关非常重要的作用就是过滤器。
过滤其中默认提供了25种内置的功能,还可以根据自己的额外的需求,自定义过滤器。对于日常开发中比较常用的功能有网关的容器、限流以及请求及相应的额外处理。

二 Spring Cloud Gareway介绍
1.简介
Spring Cloud Gateway 是Spring Cloud的二级子项目,提供了微服务网关的功能,包含:权限安全、监控、指标等功能。例如: 熔断、限流、重试、自定义过滤器等token校验、IP黑名单。
链接:
SpringCloud Gateway
你们项目里面用的什么网关? gateway zuul
spring cloud gaeway 是用来取代zuul(netflix) 的新一代网关组件。
(zuul: 1.0,2.0**, zuul的本质,一组过滤器,根据自定义的过滤器顺序来执行,本质就是web组件 web三大组件(监听器 过滤器 servlet) 拦截 SpringMVC**)。

SpringCloud Gateway是基于webFlux框架实现的,而webFlux框架底层使用了高性能的Reactor模式通信框架Netty。

Zuul1.0使用的是BIO(Blocking IO) tomcat7.0之前都是BIO,性能一般。Zuul2.0 性能好 NIO
AIO 异步非阻塞IO。 aio=async+no blocking io

2 名词解释
2.1 Route 路由
Route中文为路由,GateWay里面的Route是主要学习内容·,一个GateWay项目包含有多个Route。
一个路由包含 ID、URI、Predicate集合、Filter集合。
在Route中ID是自定义的,URI就是一个地址。
2.2 Predicate(断言)(就是一个返回bool的表达式)
Spring Cloud Gateway中断言函数输入类型是Spring 5.0框架中的ServerWebExchange。Spring Cloud Gateway的断言函数允许开发者去定义匹配来自于Http Request中的任何信息,比如请求头和参数。
2.3 Filter 过滤器
所有生效的Filter都是GatewayFilter的实例,在GateWay运行过程中Filter负责在代理服务之前和之后做一些事情。
Sping Cloud Gateway中的Filter分为两种类型的Filter.分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。
一个是针对某一个路由的Filter 对某一个接口做限流。
一个是针对全局的filter token\ip白名单
2.4 流程
在这里插入图片描述
网关客户端访问Gateway网关,Gateway中的 Handler Mapping对请求URI进行处理,处理完成后交换Web Handler. Web Handler会被Filter进行过滤。Filter中前半部分代理是处理请求的代码,处理完成后调用真实的被代理的服务。被代理的服务响应结果,结果会被Filter中后半部分代码进行操作,操作完成后把结果返回给Web Handler,再返回给Handler Mapping,最终响应给客户端。

三、相关实操

1.入门案例之配置文件路由
手写一个登录服务loginService的Controller

@RestController
public class LoginController {


    @GetMapping("/login")
    public String login(String userName,String Password){
        return UUID.randomUUID().toString();
    }
}

gateway-service 配置中维护

server:
  port: 80

spring:
  cloud:
    gateway:
      enabled: true  # 只要加了依赖 默认是开启的
      routes:
        - id: gateway-server-login # 路由的id 保持唯一即可
          uri: http://localhost:8086 #uri  统一资源标识符号
          predicates:
            - Path=/login # 匹配规则 只要path匹配上了  /login  就往uri转发  http://localhost:8086/login

预期效果:
在这里插入图片描述
2. 入门案例之代码路由
1.自定义配置类,注入相关的bean

@Configuration
public class GatewayConfig {


  @Bean
  public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("demo1", r -> r.path("/v/kichiku").uri("https://www.bilibili.com/"))
        .route("demo2",r->r.path("/v/douga").uri("https://www.bilibili.com/"))
        .route("demo3",r->r.path("/v/knowledge").uri("https://www.bilibili.com/"))
        .build();
  }
  
}

关键代码逻辑是实现RouteLocator接口,该接口的实现可以通过RouteLocatorBuilder 类中的routes 定义若干个route,route中含有两个参数,一个是route的id,一个是函数Function,可以通过链式编程依次实现断言、URI等参数的注入。

实现效果:
在这里插入图片描述
3.动态路由
从之前的配置可以看出,URL的配置项是写死的,这不符合微服务的要求。微服务的要求是只要知道服务的名字,根据名字去找,而直接写死就没有负载均衡的效果了。
默认情况下,GateWay会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。
需要注意的是,uri的协议为lb(load balance) ,表示启动GateWay的负载均衡的功能。lb://serviceName 是Spring Cloud Gateway 在微服务中自动为我们创建的负载均衡uri
动态路由需要结合注册中心进行服务发现,以EureKa注册中心为例:
配置文件的配置

server:
  port: 80



spring:
  cloud:
    gateway:
      enabled: true  # 只要加了依赖 默认是开启的
      routes: #如果一个服务里面有100个路径 如果我想做负载均衡?? 动态路由
        - id: gateway-server-login # 路由的id 保持唯一即可
          uri: lb://login-service #uri  统一资源标识符号
          predicates:
            - Path=/login # 匹配规则 只要path匹配上了  /login  就往uri转发  http://localhost:8086/login

      discovery:
        locator:
          enabled: true  #开启动态路由  开启通过应用名称找到服务的功能
          lower-case-service-id: true  #开启服务名称小写
  application:
    name: gateway-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
    registry-fetch-interval-seconds: 3 #网关拉取服务列表的时间缩短
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

GateWay启动类添加Eureka的客户端启动类

@EnableEurekaClient
@SpringBootApplication
public class LoginApplication {

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

效果:
在这里插入图片描述
eureka server的配置项

server:
  port: 8761

spring:
  application:
    name: eureka-server

启动类的代码

@EnableEurekaServer
@SpringBootApplication
public class EureKaApplication {

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

服务注册效果

在这里插入图片描述
4. 断言工厂的使用
在gateway启动时会去加载一些路由断言工厂(判断一句话是否正确 一个boolean表达式)
在这里插入图片描述
After断言demo

spring:
  cloud:
    gateway:
      enabled: true  # 只要加了依赖 默认是开启的
      routes: #如果一个服务里面有100个路径 如果我想做负载均衡?? 动态路由
        - id: gateway-server-login # 路由的id 保持唯一即可
          uri: lb://login-service #uri  统一资源标识符号
          predicates:
            - Path=/login # 匹配规则 只要path匹配上了  /login  就往uri转发  http://localhost:8086/login
            - After=2024-02-15T21:26:41.466+08:00[Asia/Shanghai]
            - Method=POST,GET

在2024-02-15 21:26之前访问:
在这里插入图片描述
在2024-02-15 21:26之后访问:
在这里插入图片描述
断言Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。

自定义断言工厂
自定义断言工厂可以继承AbstractRoutePredicateFactory 抽象工厂类,重写apply方法的逻辑。
在apply方法中可以通过exchange.getRequest()拿到ServerHttpRequest对象,从而可以获取到请求的参数、请求方式、请求头等信息。
apply方法的参数是自定义的配置类,在使用的时候配置参数,在apply方法中直接获取使用。
源码demo:

public class HeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<HeaderRoutePredicateFactory.Config> {
  public static final String HEADER_KEY = "header";
  public static final String REGEXP_KEY = "regexp";

  public HeaderRoutePredicateFactory() {
    super(HeaderRoutePredicateFactory.Config.class);
  }

  public List<String> shortcutFieldOrder() {
    return Arrays.asList("header", "regexp");
  }

  public Predicate<ServerWebExchange> apply(HeaderRoutePredicateFactory.Config config) {
    final boolean hasRegex = !StringUtils.isEmpty(config.regexp);
    return new GatewayPredicate() {
      public boolean test(ServerWebExchange exchange) {
        List<String> values = (List)exchange.getRequest().getHeaders().getOrDefault(config.header, Collections.emptyList());
        if (values.isEmpty()) {
          return false;
        } else {
          return hasRegex ? values.stream().anyMatch((value) -> {
            return value.matches(config.regexp);
          }) : true;
        }
      }

      public String toString() {
        return String.format("Header: %s regexp=%s", config.header, config.regexp);
      }
    };
  }

  @Validated
  public static class Config {
    @NotEmpty
    private String header;
    private String regexp;

    public Config() {
    }

    public String getHeader() {
      return this.header;
    }

    public HeaderRoutePredicateFactory.Config setHeader(String header) {
      this.header = header;
      return this;
    }

    public String getRegexp() {
      return this.regexp;
    }

    public HeaderRoutePredicateFactory.Config setRegexp(String regexp) {
      this.regexp = regexp;
      return this;
    }
  }
}

5.Filter 过滤器工厂(重点)

gateway里面的过滤器和Servlet里面的过滤器功能差不多,路由过滤器可以用于修改进入Http请求和返回Http响应。
分类
1.按照生命周期分为两种: pre 在业务逻辑之前 post 在业务逻辑之后
2.按照种类划分也是两种:
GatewayFilter 需要配置某个路由,才能过滤。如果需要使用全局过滤,需要配置DefaultFilters.
GlobalFilter 全局过滤器,不需要配置路由,系统初始化作用到所有路由上。
全局过滤器 统计请求次数 限流 token的校验 ip黑名单的拦截 跨域本质(filter) 144开头的电话 限制一些ip的访问等
具体过滤器的详情,可以参考官方文档
过滤器官方文档
自定义过滤器
自定义过滤器主要实现GlobalFilter, Ordered两个接口,具体的IP黑名单的自定义
过滤器逻辑如下:

package com.sgm.esb.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author: Runqiang_Jiang
 * @Time: 2024/2/15  22:38
 */

/**
 * IP 过滤器
 */
@Component
public class IPCheckFilter implements GlobalFilter, Ordered {

  /**
   * 网关的并发量比较高,不要在网关里面直接操作数据库
   */
  public static final List<String> BLACK_LIST= Arrays.asList("127.0.0.1");

  /**
   * 1.拿到ip
   * 2.检查ip是否符合规范
   * 3.放行\拦截
   * @param exchange
   * @param chain
   * @return
   */
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    String ip = request.getHeaders().getHost().getHostString();
    // ip 是否存在黑名单里面
    if(!BLACK_LIST.contains(ip)){
      //放行

      return chain.filter(exchange);
    }
    //拦截
    //响应相关的内容
    ServerHttpResponse response = exchange.getResponse();
    response.getHeaders().set("content-type","application/json;charSet=UTF-8");
    HashMap<String, Object> hashMap = new HashMap<>(4);
    hashMap.put("code", 438);
    hashMap.put("msg","你是黑名单");
    ObjectMapper objectMapper = new ObjectMapper();
    DataBuffer wrap=null;
    try {
      byte[] bytes = objectMapper.writeValueAsBytes(hashMap);
       wrap = response.bufferFactory().wrap(bytes);

    } catch (JsonProcessingException e) {
    }

    return response.writeWith(Mono.just(wrap));
  }

  @Override
  public int getOrder() {
    return -5;
  }
}

结果展示:
拦截:
在这里插入图片描述
放行:
在这里插入图片描述
网关全局过滤器token的校验

在这里插入图片描述
演示demo

package com.sgm.esb.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.w3c.dom.stylesheets.LinkStyle;
import reactor.core.publisher.Mono;

/**
 * @author: Runqiang_Jiang
 * @Time: 2024/2/15  23:34
 */
@Component
public class TokenCheckFilter implements GlobalFilter, Ordered {

  @Autowired
  private StringRedisTemplate redisTemplate;


  public static final List<String> ALLOW_URL= Arrays.asList("/login-service/login","/myurl","/login");
  /**
   * token约定放在请求头中的Authorization
   * 1.拿到请求url
   * 2.判断放行
   * 3.拿到请求头
   * 4.拿到token
   * 5.校验
   * 6.放行\拦截
   * @param exchange
   * @param chain
   * @return
   */
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    String path = request.getURI().getPath();
    if(ALLOW_URL.contains(path)){
      return chain.filter(exchange);
    }
    //检查
    HttpHeaders headers = request.getHeaders();
    List<String> authorization = headers.get("Authorization");
    if(!CollectionUtils.isEmpty(authorization)){
      String token= authorization.get(0);
      if(StringUtils.hasText(token)){
         //约定好有前缀 bearer token
        String realToken  = token.replace("bearer ", "");
        if(StringUtils.hasText(realToken)&& redisTemplate.hasKey(realToken)){
          return  chain.filter(exchange);

        }
      }
    }
    //拦截
    ServerHttpResponse response = exchange.getResponse();
    response.getHeaders().set("Content-Type","application/json;charset=UTF-8");
    HashMap<String, Object> hashMap = new HashMap<>(4);
    hashMap.put("code", HttpStatus.SC_UNAUTHORIZED);
    hashMap.put("msg","未授权");
    ObjectMapper objectMapper = new ObjectMapper();
    byte[] bytes = new byte[0];
    try {
      bytes = objectMapper.writeValueAsBytes(hashMap);
    } catch (JsonProcessingException e) {
      e.printStackTrace();
    }
    DataBuffer wrap = response.bufferFactory().wrap(bytes);
    return response.writeWith(Mono.just(wrap));
  }

  @Override
  public int getOrder() {
    return 2;
  }
}

演示结果:
用户登录生成token
在这里插入图片描述
在这里插入图片描述
请求teach-service服务
成功
在这里插入图片描述
失败
在这里插入图片描述
6 gateway集成Redis限流
6.1 什么是限流?
通俗的说,限流就是限制一段时间内,用户访问资源的次数,减轻服务器的压力,限流大致分为两种:
1.IP限流(5s内同一个ip访问超过3次,则限制不让访问,过一段时间才可能继续访问)
2.请求量限流(只要在一段时间内(窗口期),请求次数达到阈值,就直接拒绝后面来得访问了,过一段时间才可以继续访问)(粒度可以细化到一个api,一个服务)
6.2本地限流模型
限流模型:漏斗算法 、令牌桶算法、滑动窗口算法、计数器算法
6.3 GateWay 结合redis实现请求量限流
Spring Cloud GateWay 已经内置了一个 RequestRateLimiterGatewayFilterFactory,可以直接使用
相关的依赖

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

代码实例:

/**
 * 自定义请求限制
 */
@Configuration
public class RequestLimitConfig {

  //针对某一个接口ip来限流 /login
  @Bean("ipKeyResolver")
  @Primary
  public KeyResolver ipKeyResolver(){
      return exchange ->
        Mono.just(exchange.getRequest().getHeaders().getHost().getHostString());
  }

  //针对某个路径进行限流  /login
  // api 就是接口
  @Bean("apiKeyResolver")
  public KeyResolver apiKeyResolver(){
    return exchange -> Mono.just(exchange.getRequest().getPath().value());
  }



}

相关的配置

spring:
  cloud:
    gateway:
      enabled: true  # 只要加了依赖 默认是开启的
      routes: #如果一个服务里面有100个路径 如果我想做负载均衡?? 动态路由
        - id: gateway-server-login # 路由的id 保持唯一即可
          uri: lb://login-service #uri  统一资源标识符号
          predicates:
            - Path=/login # 匹配规则 只要path匹配上了  /login  就往uri转发  http://localhost:8086/login
#            - After=2024-02-15T21:26:41.466+08:00[Asia/Shanghai]
            - Method=POST,GET
#            - Query=name,admin.  #正则表达式的值
          filters:
            - name: RequestRateLimiter # 过滤器的名称
              args:  #过滤器的参数
                key-resolver: "#{@ipKeyResolver}" #通过spel表达式取ioc容器中bean的值
                redis-rate-limiter.replenishRate: 1 #生成令牌的速度
                redis-rate-limiter.burstCapacity: 3 #桶容量
#                redis-rate-limiter.requestedTokens: 1

      discovery:
        locator:
          enabled: true  #开启动态路由  开启通过应用名称找到服务的功能
          lower-case-service-id: true  #开启服务名称小写
  application:
    name: gateway-service

链接:

https://docs.spring.io/spring-cloud-gateway/docs/4.0.9/reference/html/#the-requestratelimiter-gatewayfilter-factory

7.网关与跨域配置

  • 46
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值