• Spring之Gateway网关


    前言

            什么是网关?简单理解就是我们所有服务的入口,当我们使用了微服务以后,每个服务都会有一个对应的接口,比如我们有用户服务,订单服务等等,如果没有网关的话,那么前端是这样调用的

            很明显app和h5需要知道所有微服务的地址,显然会让前端变得很复杂,同时也不太安全,那如果有网关后是怎么样的呢?如下

    这样一来,所有流量就会从网关进来了,当然了网关会存在单点故障问题,这个可以通过负载均衡就可以解决了

    网关核心概念 

            路由:路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。通过ID使用微服务名称,因为微服务一般就是全局统一的

            断言:简单了解就是对参数、请求头,URI等的判断

            Filter:拦截器,对请求进行拦截处理等,跟mvc的拦截器差不多

    网关工作原理

    网关的使用 

    spring网关的官方api文档spring gateway

            网关的使用其实非常简单,只需要做以下的两步即可

    1. 引入maven依赖

    1. org.springframework.cloud
    2. spring-cloud-starter-gateway
    3. com.alibaba.cloud
    4. spring-cloud-starter-alibaba-nacos-discovery

           2. 配置yml文件

    server:
      port: 9999
    spring:
      application:
        name: gateway
      #配置nacos注册中心地址
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
    
        gateway:
          #设置路由:路由id、路由到微服务的uri、断言
          routes:
            - id: user_route   #路由ID,全局唯一,建议配置服务名
              uri: lb://user  #lb 整合负载均衡器ribbon,loadbalancer
              predicates:
                - Path=/user/**   # 断言,路径相匹配的进行路由

     3. 定义springBoot配置类启动即可使用了

    网关使用遇到的问题

     首先,你在使用网关的时候需要排除webmvc,因为网关是基于webflux实现的,所以必须排除这个,否则启动不了

    
    
        org.springframework.cloud
        spring-cloud-starter-gateway
        
            
                org.springframework
                spring-webmvc
            
        
    

     其次,不知道为啥我启动的时候报下面的错

    1. ***************************
    2. APPLICATION FAILED TO START
    3. ***************************
    4. Description:
    5. An attempt was made to call a method that does not exist. The attempt was made from the following location:
    6. org.springframework.cloud.gateway.config.GatewayAutoConfiguration$NettyConfiguration.buildConnectionProvider(GatewayAutoConfiguration.java:798)
    7. The following method did not exist:
    8. reactor.netty.resources.ConnectionProvider$Builder.evictInBackground(Ljava/time/Duration;)Lreactor/netty/resources/ConnectionProvider$ConnectionPoolSpec;
    9. The method's class, reactor.netty.resources.ConnectionProvider$Builder, is available from the following locations:
    10. jar:file:/E:/javaEvn/mvn-need/io/projectreactor/netty/reactor-netty/0.9.10.RELEASE/reactor-netty-0.9.10.RELEASE.jar!/reactor/netty/resources/ConnectionProvider$Builder.class
    11. The class hierarchy was loaded from the following locations:
    12. reactor.netty.resources.ConnectionProvider.Builder: file:/E:/javaEvn/mvn-need/io/projectreactor/netty/reactor-netty/0.9.10.RELEASE/reactor-netty-0.9.10.RELEASE.jar
    13. reactor.netty.resources.ConnectionProvider.ConnectionPoolSpec: file:/E:/javaEvn/mvn-need/io/projectreactor/netty/reactor-netty/0.9.10.RELEASE/reactor-netty-0.9.10.RELEASE.jar
    14. Action:
    15. Correct the classpath of your application so that it contains a single, compatible version of reactor.netty.resources.ConnectionProvider$Builder
    16. Process finished with exit code 1

    看报错信息就是说找不到类之类的,所以尝试了几个解决方案

    1. 删除maven对应的依赖,因为有时可能因为网络问题引起jar包有问题,但是无法解决
    2. 看网上说可能是版本不匹配的问题,官网的推荐是版本说明 · alibaba/spring-cloud-alibaba Wiki · GitHub
    3. 但是我的版本也是没问题,都是匹配的

    Hoxton.SR12
    2.2.8.RELEASE
    spring-boot-starter-parent
    2.3.12.RELEASE

    最后实在没办法了,找到了网关启动类GatewayAutoConfiguration,里面有个方法叫org.springframework.cloud.gateway.config.GatewayAutoConfiguration.NettyConfiguration#buildConnectionProvider的builder.evictInBackground(pool.getEvictionInterval());报错了,这也是上面报错的原因,所以就尝试了一下对reactor.netty.resources.ConnectionProvider依赖进行了升级,如下

    
        io.projectreactor.netty
        reactor-netty
        0.9.25.RELEASE
    
    

    这样一来,就可以解决了,但是官网推荐的版本匹配岂不是有问题吗,这个有待于后续的研究

    这个问题解决以后,gateway就可以正常启动了

    断言工具

            我们常用的是Path断言,断言就是个判断

    predicates:
      - Path=/user/**   # 断言,路径相匹配的进行路由

    其他更多的可以查看官网:spring 断言类

    如果提供的不够还可以进行自定义,找一个参考就行了,命令也是要按照规范来即可

    GatewayFilter拦截链

    网关提供了GatewayFilter接口,默认提供了很多实现,当然我们也可以自己进行实现,然后放到容器中,他就会走我们的逻辑了,比如我定义了一个拦截器的实现,如下

    1. @Component
    2. public class ZxcGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
    3. @Override
    4. public GatewayFilter apply(NameValueConfig config) {
    5. return new GatewayFilter() {
    6. @Override
    7. public Mono filter(ServerWebExchange exchange,
    8. GatewayFilterChain chain) {
    9. String name = config.getName();
    10. String value = config.getValue();
    11. System.out.println(name);
    12. System.out.println(value);
    13. return chain.filter(exchange);
    14. }
    15. };
    16. }
    17. }

    注意:名字要规范ZxcGatewayFilterFactory,Zxc为你使用的名字,后面的格式是固定的,然后放到ioc容器中就可以了,然后是在yml配置中进行配置,如下

    gateway:
      #设置路由:路由id、路由到微服务的uri、断言
      routes:
        - id: user_route   #路由ID,全局唯一,建议配置服务名
          uri: lb://user  #lb 整合负载均衡器ribbon,loadbalancer
          filters:
            - Zxc=zxc,ttt
          predicates:
            - Path=/user/**   # 断言,路径相匹配的进行路由

    spring提供了多个GatewayFilterFactory,地址:Spring GatewayFilterFactory

    配置比较简单,不过这种是局部配置的,我们一般会使用全局的,如下

    GlobalFilter

    这个是全局的,spring一样提供了很多,地址:Spring GlobalFilter

    如果这些还不够,我们可以自己再提供一些实现,如下面定义了校验权限和校验白名单的类

    1. @Component
    2. public class AuthGlobalFilter implements GlobalFilter {
    3. @Override
    4. public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    5. String token = exchange.getRequest().getQueryParams().getFirst("token");
    6. ServerHttpResponse response = exchange.getResponse();
    7. if(token == null) {
    8. return response.writeWith(Mono.just(response.bufferFactory().wrap("没有权限".getBytes())));
    9. }
    10. return chain.filter(exchange);
    11. }
    12. }
    1. package com.zxc.gateway.filter;
    2. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    3. import org.springframework.cloud.gateway.filter.GlobalFilter;
    4. import org.springframework.stereotype.Component;
    5. import org.springframework.web.server.ServerWebExchange;
    6. import reactor.core.publisher.Mono;
    7. @Component
    8. public class WhiteGlobalFilter implements GlobalFilter {
    9. @Override
    10. public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    11. //todo 校验白名单逻辑
    12. return chain.filter(exchange);
    13. }
    14. }

    然后只要把实现类放到ioc容器中即可,以上都是网关提供的一些扩展点

    适配器模式

            网关提供的Filter过滤器包含了两种,一种是局部的GatewayFilter,一种是全局的GlobalFilter,先看看下面的两种接口的定义

    1. public interface GatewayFilter extends ShortcutConfigurable {
    2. /**
    3. * Name key.
    4. */
    5. String NAME_KEY = "name";
    6. /**
    7. * Value key.
    8. */
    9. String VALUE_KEY = "value";
    10. /**
    11. * Process the Web request and (optionally) delegate to the next {@code WebFilter}
    12. * through the given {@link GatewayFilterChain}.
    13. * @param exchange the current server exchange
    14. * @param chain provides a way to delegate to the next filter
    15. * @return {@code Mono} to indicate when request processing is complete
    16. */
    17. Mono filter(ServerWebExchange exchange, GatewayFilterChain chain);
    18. }
    1. public interface GlobalFilter {
    2. /**
    3. * Process the Web request and (optionally) delegate to the next {@code WebFilter}
    4. * through the given {@link GatewayFilterChain}.
    5. * @param exchange the current server exchange
    6. * @param chain provides a way to delegate to the next filter
    7. * @return {@code Mono} to indicate when request processing is complete
    8. */
    9. Mono filter(ServerWebExchange exchange, GatewayFilterChain chain);
    10. }

    现在有个问题是说,比如我需要把两种拦截器都放在一个列表里,但是他们是不同接口类型的,那么我可以怎么做?这时候就可以使用适配器了,这是个很巧妙的设计,在源码中有这么一个适配器的类,如下

    1. private static class GatewayFilterAdapter implements GatewayFilter {
    2. private final GlobalFilter delegate;
    3. GatewayFilterAdapter(GlobalFilter delegate) {
    4. this.delegate = delegate;
    5. }
    6. @Override
    7. public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    8. return this.delegate.filter(exchange, chain);
    9. }
    10. @Override
    11. public String toString() {
    12. final StringBuilder sb = new StringBuilder("GatewayFilterAdapter{");
    13. sb.append("delegate=").append(delegate);
    14. sb.append('}');
    15. return sb.toString();
    16. }
    17. }

    是不是很巧妙,把GlobalFilter通过GatewayFilterAdapter包装一下,就可以变成了GatewayFilter类型的,这样一来两种Filter就可以放在同一个list里面的,设计核心点:

    1.  GatewayFilterAdapter包含了一个GlobalFilter的对象

    2.  GlobalFilter实现了GatewayFilter接口

    3. 在GatewayFilterAdapter实现GatewayFilter的接口使用内部的GlobalFilter来进行实现

    通过以上的设计,就间接把GlobalFilter和GatewayFilter整合在一起了,很值得我们去学习

    网关限流整合

    官网提供的

    基于redis+lua脚本方式采用令牌桶算法实现了限流

    具体文档为:gateway基于 redis+lua 限流

    整合sentinel限流

    参考地址

    网关限流 · alibaba/Sentinel Wiki · GitHub

  • 相关阅读:
    前端 WebSocket 的使用
    java计算机毕业设计-损失赔偿保险的客户情况登记及管理-源程序+mysql+系统+lw文档+远程调试
    SMT贴片加工中回流焊接机的关键工艺
    组件中的那么属性作用
    【C语言】联合(共用体)
    Python爬虫:如何下载汽车之家的数据(完整代码)
    python+django网吧会员管理系统
    数字通信世界杂志数字通信世界杂志社数字通信世界编辑部2022年第6期目录
    linux-ubuntu-16.04 安装系统、安装 SSH 服务、设置root用户密码
    VERTU广告登陆央视:情怀与创新的恪守之道
  • 原文地址:https://blog.csdn.net/zxc_user/article/details/127931161