【黑马-SpringCloudAlibaba】学习笔记04-Gateway:服务网关组件

三 Gateway服务网关组件

网关的作用:解除客户端与微服务的耦合,方便微服务访问地址的维护;处理鉴权认证跨域问题。
网关也是一个微服务注册在nacos上,作为客户端和其他微服务的中转点,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
  • 相关技术栈有:
    • Nginx+lua :使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用;lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本
    • Kong : 基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。
      问题:只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
    • Zuul : Netflix开源的网关,功能丰富,使用JAVA开发,易于二次开发
      问题:缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如NginxSpring Cloud Gateway
    • Gateway : Spring公司为了替换Zuul而开发的网关服务;设计优雅,容易扩展
注意: SpringCloud alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud Gateway来做网关
Gateway配置详解
  • id:自定义不重复
  • uri:转发的目标地址
  • order:可能有很多路由的path一样,这时候根据order值选择先去哪个路由。
  • predicates:断言,这里可以配置很多规则,判断path值是否符合规则,如果符合则转发请求。
  • filters:可以对请求做一些手脚(增加headers,修改请求uri)
1 测试:通过网关访问商品微服务
 <dependencies>
    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-gatewayartifactId>
    dependency>

    
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
    dependency>
server:
  port: 7000
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 让gateway从nacos中获取服务信息
      routes:    #路由数组〔路由就是指当请求满足什么样的条件的时候转发到哪个微服务上]
        - id: product_route       #当前路由发的标识,要求唯—
          uri: http://localhost:8081    #lb://service-product      #请求最终要被转发到的地址
          order: 1   #路由的优先级,数字越小代表路由的优先级越高
          predicates:  #断言(条件判断,返回值是boolean转发请求要满足的条件)。集合
            - Path=/product-serv/**   #当请求路径满足Path指定的规则时,此路由信息才会正常转发(以product-serv开头)
          # 上面路径是 uri+predicates=》http://localhost:7000/product-serv/product/1
          filters:     #过滤器(在请求传递过程中对请求做一些手脚)
            - StripPrefix=1  #在请求转发之前去掉第一层路径=》http://localhost:8081/product/1
测试:原测试接口:http://localhost:8081/product/1;使用网关后访问http://localhost:7000/product-serv/product/1如果能访问通即整合成功
不写gateway.routes配置,默认 uri: lb://service-product==》http://localhost:7000/service-product/product/1

---------------------------------

2 请求最终要被转发到的地址,写死:http://localhost:8081=>从nacos获取lb://
1,注册nacos=> cloud.nacos.discovery.server-addr: localhost:8848
2,改写规则=>  gateway.routes.uri: lb://service-product     #从nacos获取lb://
server:
  port: 7000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # 将gateway注册到nacos   等同  127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true # 让gateway从nacos中获取服务信息
      routes:    #路由数组〔路由就是指当请求满足什么样的条件的时候转发到哪个微服务上]
        - id: product_route       #当前路由发的标识,要求唯—
          uri: lb://service-product      #请求最终要被转发到的地址,写死:http://localhost:8081=>从nacos获取lb://
          order: 1   #路由的优先级,数字越小代表路由的优先级越高
          predicates:  #断言(条件判断,返回值是boolean转发请求要满足的条件)。集合
            - Path=/product-serv/**   #当请求路径满足Path指定的规则时,此路由信息才会正常转发(以product-serv开头)
          # 上面路径是 uri+predicates=》http://localhost:7000/product-serv/product/1(访问)
          filters:     #过滤器(在请求传递过程中对请求做一些手脚)
            - StripPrefix=1  #在请求转发之前去掉第一层路径=》http://localhost:8081/product/1
        - id: order_route
          uri: lb://service-order   #lb是负载均衡,后面是微服务在nacos上的标识
          order: 1
          predicates:
            - Path=/order-serv/**
          filters:
            - StripPrefix=1
测试:原测试接口:http://localhost:8081/product/1;使用网关后访问http://localhost:7000/product-serv/product/1如果能访问通即整合成功
测试:原测试接口:http://localhost:8091/order/prod/1;使用网关后访问http://localhost:7000/service-order/order/prod/1如果能访问通即整合成功
端口统一了

----------------------------

3 predicates内置断言工厂

Predicate(断言,谓词)用于进行条件判断,只有断言都返回真,才会真正执行路由。
断言就是说:在什么条件下才能进行路由转发
  • 基于Datetime类型的断言工厂,此类型的断言根据时间做判断,主要有三个:
    • AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
    • BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
    • BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
- After=2021-08-01T23:59:59.789+08:00[Asia/Shanghai] #在2020-08-01时间后才可以访问
- Before=2021-08-25T23:59:59.789+08:00[Asia/Shanghai] #在2020-08-01时间后之前才可以访问
- Between=2020-08-03T23:59:59.789+08:00[Asia/Shanghai],2023-08-05T23:59:59.789+08:00[Asia/Shanghai]

  • 基于远程地址的断言工厂
    • RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中
-RemoteAddr=192.168.1.1/24 
  • 基于Cookie的断言工厂
    • CookieRoutePredicateFactory:接收两个参数, cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。
-Cookie=chocolate, ch.
  • 基于Header的断言工厂
    • HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。
-Header=X-Request-Id, \d+
  • 基于Host的断言工厂
    • HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。
-Host=**.testhost.org
  • 基于Method请求方法的断言工厂
    • MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
-Method=GET
  • 基于Path请求路径的断言工厂
    • PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
-Path=/foo/{segment}
  • 基于Query请求参数的断言工厂
    • QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。
-Query=baz, ba.
  • 基于路由权重的断言工厂
    • WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发。
  routes:
    -id: weight_route1 
    uri: host1 
    predicates:
      -Path=/product/**
      -Weight=group3, 1  # 组名,路由权重
    -id: weight_route2 
    uri: host2 
    predicates:
      -Path=/product/**
      -Weight= group3, 9

-------------------------------

1 测试设置断言
server:
  port: 7000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 #localhost:8848 # 将gateway注册到nacos   等同  127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true # 让gateway从nacos中获取服务信息
      routes:    #路由数组〔路由就是指当请求满足什么样的条件的时候转发到哪个微服务上]
        - id: product_route       #当前路由发的标识,要求唯—
          uri: lb://service-product      #请求最终要被转发到的地址,写死:http://localhost:8081=>从nacos获取lb://
          order: 1   #路由的优先级,数字越小代表路由的优先级越高
          predicates:  #断言(条件判断,返回值是boolean转发请求要满足的条件)。集合
            - Path=/product-serv/**   #当请求路径满足Path指定的规则时,此路由信息才会正常转发(以product-serv开头)
            - Age=18,60   #年龄18,60
          # 上面路径是 uri+predicates=》http://localhost:7000/product-serv/product/1(访问)
          filters:     #过滤器(在请求传递过程中对请求做一些手脚)
            - StripPrefix=1  #在请求转发之前去掉第一层路径=》http://localhost:8081/product/1
2 predicates包AgeRoutePredicateFactory.java
//这是一个自定义的路由断言工厂类,要求有两个
//1 名字必须是 配置+RoutePredicateFactory
//2 必须继承AbstractRoutePredicateFactory<配置类>
//@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {

    //构造函数
    public AgeRoutePredicateFactory() {
        super(Config.class);
    }

    //读取配置文件的中参数值 给他赋值到配置类中的属性上
    public List<String> shortcutFieldOrder() {
        //这个位置的顺序必须跟配置文件中的值的顺序对应
        return Arrays.asList("minAge", "maxAge");
    }

    //断言逻辑
    public Predicate<ServerWebExchange> apply(Config config) {
        return new Predicate<ServerWebExchange>() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                //1 接收前台传入的age参数
                String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age");

                //2 先判断是否为空
                if (StringUtils.isNotEmpty(ageStr)) {
                    //3 如果不为空,再进行路由逻辑判断
                    int age = Integer.parseInt(ageStr);
                    if (age < config.getMaxAge() && age > config.getMinAge()) {
                        return true;
                    } else {
                        return false;
                    }
                }
                return false;
            }
        };
    }

    //配置类,用于接收配置文件中的对应参数
    @Data
    @NoArgsConstructor
    public static class Config {
        private int minAge;//18
        private int maxAge;//60
    }
}
3 访问网址 http://localhost:7000/product-serv/product/2?age=25

--------------------------------

gateway过滤器
生命周期: Pre Post
PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
Gateway 的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。
GatewayFilter:应用到单个路由或者一个分组的路由上。
GlobalFilter:应用到所有的路由上。
局部过滤器
1 内置过滤器
1.1直接添加yaml
filters:     #过滤器(在请求传递过程中对请求做一些手脚)
        - SetStatus=250
1.2访问 http://localhost:7000/product-serv/product/2 =》F12 =》 status=250
-------------------------
2 自定义过滤器
2.1 添加配置
filters:     #过滤器(在请求传递过程中对请求做一些手脚)
  - Log=true ,false  #控制日志是否开启
2.2 自定义配置类
//测试 自定义局部过滤器   @Component 没开等于没用
@Component
public class LogGatewayFilterFactory
        extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {

    //构造函数
    public LogGatewayFilterFactory() {
        super(Config.class);
    }

    //读取配置文件中的参数 赋值到 配置类中
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("consoleLog", "cacheLog");
    }

    //过滤器逻辑
    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                if (config.isCacheLog()) {
                    System.out.println("cacheLog已经开启了....");
                }
                if (config.isConsoleLog()) {
                    System.out.println("consoleLog已经开启了....");
                }

                return chain.filter(exchange);
            }
        };
    }

    //配置类 接收配置参数
    @Data
    @NoArgsConstructor
    public static class Config {
        private boolean consoleLog;
        private boolean cacheLog;
    }
}
2.3访问 http://localhost:7000/product-serv/product/2 =》F12 =》 status=250
-------------------------------------
全局过滤器
1 内置过滤器
uri: lb://service-product
lb就是负载均衡过滤器
2 自定义过滤器
//自定义全局过滤器需要实现GlobalFilter和Ordered接口  ,作用是统一鉴权
@Component    //@Component 没开等于没用
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    //完成判断逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (!StringUtils.equals(token, "admin")) {
            System.out.println("鉴权失败");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        //调用chain.filter继续向下游执行
        return chain.filter(exchange);
    }

    //顺序,数值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}
只有访问token才有结果 http://localhost:7000/product-serv/product/1?token=admin

------------------------------------------

网关限流

Sentinel提供了SpringCloud Gateway的适配模块,提供两种限流维度
  • route维度:即在Spring配置文件中配置的路由条目,资源名为对应的routeld
  • 自定义API维度:用户可以利用Sentinel提供的APl来自定义一些API分组
依赖
<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-spring-cloud-gateway-adapterartifactId>
dependency>

1 route维度

1.1 配置类GatewayConfiguration
主要编写initGatewayRules方法设置限流规则,编写initBlockHandlers方法自定义异常界面。

@Configuration
public class GatewayConfiguration {
    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    // 初始化一个限流的过滤器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    // 配置初始化的限流参数
    @PostConstruct
    public void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(
                new GatewayFlowRule("product_route") //资源名称,对应路由id
                        .setCount(1) // 限流阈值
                        .setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
        );
        GatewayRuleManager.loadRules(rules);
    }

    // 配置限流的异常处理器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    // 自定义限流异常页面
    @PostConstruct
    public void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map map = new HashMap<>();
                map.put("code", 0);
                map.put("message", "接口被限流了");
                return ServerResponse.status(HttpStatus.OK).
                        contentType(MediaType.APPLICATION_JSON_UTF8).
                        body(BodyInserters.fromObject(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
}
1.2在一秒钟内多次访问http://localhost:7000/product-serv/product/1就可以看到限流启作用了。
------------------------
2 自定义API维度
2.1 配置类GatewayConfiguration
主要编写initGatewayRules方法设置限流规则,编写initBlockHandlers方法自定义异常界面。

@Configuration
public class GatewayConfiguration {
    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    // 初始化一个限流的过滤器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    // 配置初始化的限流参数
    @PostConstruct
    public void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("product_api1").setCount(1).setIntervalSec(1));
        rules.add(new GatewayFlowRule("product_api2").setCount(1).setIntervalSec(1));
        GatewayRuleManager.loadRules(rules);
    }

    // 配置限流的异常处理器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    // 自定义限流异常页面
    @PostConstruct
    public void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map map = new HashMap<>();
                map.put("code", 0);
                map.put("message", "接口被限流了");
                return ServerResponse.status(HttpStatus.OK).
                        contentType(MediaType.APPLICATION_JSON_UTF8).
                        body(BodyInserters.fromObject(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }

    //自定义API分组
    @PostConstruct
    private void initCustomizedApis() {
        Set<ApiDefinition> definitions = new HashSet<>();
        ApiDefinition api1 = new ApiDefinition("product_api1")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    // 以/product-serv/product/api1 开头的请求
                    add(new ApiPathPredicateItem().setPattern("/product-serv/product/api1/**").
                            setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
        ApiDefinition api2 = new ApiDefinition("product_api2")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    // 以/product-serv/product/api2/demo1 完成的url路径匹配
                    add(new ApiPathPredicateItem().setPattern("/product-serv/product/api2/demo1"));
                }});
        definitions.add(api1);
        definitions.add(api2);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

}
2.2 在shop-product中多加几个test方法
@RestController
@Slf4j
public class ProductController {
  @RequestMapping("/product/api1/demo1")
  public String demo1() {
    return "demo";
  }

  @RequestMapping("/product/api1/demo2")
  public String demo2() {
    return "demo";
  }

  @RequestMapping("/product/api2/demo1")
  public String demo3() {
    return "demo";
  }

  @RequestMapping("/product/api2/demo2")
  public String demo4() {
    return "demo";
  }
}
2.3 在一秒钟内多次访问http://localhost:7000/product-serv/product/api2/demo2 不会限流。
在一秒钟内多次访问http://localhost:7000/product-serv/product/api1/demo1 就可以看到限流启作用了。
在一秒钟内多次访问http://localhost:7000/product-serv/product/api1/demo2 就可以看到限流启作用了。
在一秒钟内多次访问http://localhost:7000/product-serv/product/api2/demo1 就可以看到限流启作用了。

你可能感兴趣的:(学习,gateway,java)