SpringCloud - Gateway (1) 集成与配置详解

官网文档

快速集成

新创建服务:gateway-service

gateway-service

引入依赖:

<dependency>
 	<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

修改 bootstrap.yaml

server:
  port: 5000

spring:
  main:
    allow-bean-definition-overriding: true
  cloud:
    # 网关配置
    gateway:
      routes:
        - id: order_route             # 路由的唯一标识
          uri: http://localhost:6200  # 转发地址
          predicates:                 # 断言规则,用于路由匹配
            - Path=/order-service/**
          filters:
            - StripPrefix=1           # 剔除 url 中的第一层路径: /order-service/

分别启动:order-service、gateway-service

order-service 含有一个接口:

@RestController
public class OrderController {

    @Value("${server.port}")
    private String port;

    @GetMapping(value = "/echo/{string}")
    public String echo(@PathVariable String string) {
        return String.format("Order service %s %s", port, string);
    }
}

浏览器访问:localhost:5000/order-service/echo/cqq

浏览器输出:Order service 6200 cqq

gateway 集成 nacos

引入 nacos 服务治理与服务配置:

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

修改 bootstrap.yaml

spring:
  main:
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      # 通用配置
      # server-addr: 127.0.0.1:8000 # 1. nacos 集群服务地址
      server-addr: 127.0.0.1:8100 # 1. nacos 服务地址
      username: nacos
      password: nacos

      # 服务治理
      discovery:
        namespace: 0298b122-a60d-47f5-9be3-9ea149f17185
        group: DEFAULT_GROUP
        service: nacos-gateway-service
        cluster-name: gateway-service-cluster
        weight: 1

      # 服务配置
      config:
        namespace: 0298b122-a60d-47f5-9be3-9ea149f17185
        group: DEFAULT_GROUP
        name: gateway-service
        file-extension: yaml
        refresh-enabled: true

根据服务名称转发

修改 nacos 远程配置文件中的路由配置:负载均衡转发、使用服务名作为 uri

server:
  port: 5000

logging:
  level:
    root: info

spring:
  mvc:
    path-match:
      matching-strategy: ANT_PATH_MATCHER
  application:
    name: gateway-service
  cloud:
    config:
      override-none: true
      allow-override: true
      override-system-properties: false
    
    # 网关配置
    gateway:
      routes:
        - id: order_route                    # 路由的唯一标识
          uri: lb://nacos-order-service      # lb: load-balance 启用负载均衡,并根据服务名称自动转发
          predicates:                        # 断言规则,用于路由匹配
            - Path=/order-service/**
          filters:
            - StripPrefix=1                  # 剔除 url 中的第一层路径: /order-service/

查看一下 nacos 中已注册的服务列表:

gateway-nacos-service-discovery
浏览器访问:http://localhost:5000/order-service/echo/cqq

浏览器输出:Order service 6200 cqq

定位器模式

启用 discovery.locator.enabled 开启定位器模式后,可以帮助我们简化路由的配置。即使不配置路由转发,默认也可以通过服务名访问到服务了。

比如订单服务接口:localhost:6200/echo/{str} ,可以被映射为 localhost:5000/nacos-order-service/echo/{str},即使我们没有配置任何路由。

修改 nacos 远程配置文件中的路由配置:

# 网关配置
gateway:
  discovery:
    locator:
      enabled: true
  routes:
    - id: order_route                    # 路由的唯一标识
      uri: lb://nacos-order-service      # lb: load-balance 启用负载均衡,并根据服务名称自动转发
      predicates:                        # 断言规则,用于路由匹配
        - Path=/route-order-service/**
      filters:
        - StripPrefix=1                  # 剔除 url 中的第一层路径: /order-service/

并且定位器模式兼容手动路由配置,注意,我有意将手动路由中的断言配置改为了:/route-order-service/**,也就是说此时根据:http://localhost:5000/route-order-service/echo/cqqhttp://localhost:5000/nacos-order-service/echo/cqq 都可以访问到 order-service。

断言

内置断言工厂

gateway 提供了许多内置的路由断言方式,上面我们使用的 Path=/uri 即是其中的一种:PathRoutePredicateFactory

路由断言工厂
官网文档地址,里面介绍了所有内置断言工厂的配置。其实根据断言工厂名称就可以推测出是根据什么进行断言匹配的。

自定义断言工厂

spring:
  cloud:
    gateway:
      routes:
          predicates:
            - Path=/route-order-service/**

在创建自定义工厂前,先介绍一下快捷配置。Path = /route-order-service/** 就是一个快捷配置。其中,Path 会被拼接 RoutePredicateFactory 后在 IOC 容器中寻找对应的断言工厂 Bean 进行断言 Bean 的注册。每次请求进入后,就会执行该断言。其次,/route-order-service/** 会作为配置值注入到 PathRoutePredicateFactory 中,辅助进行断言。

下面我们创建一个自己的断言工厂:

  1. 必须是一个 Spring Bean
  2. 必须以 RoutePredicateFactory 作为类名后缀
  3. 继承 AbstractRoutePredicateFactory
  4. 设置配置类,声明配置属性接收配置文件中对应的断言信息
  5. 重写断言方法逻辑
// 1. 声明为 Bean
// 2、3. 以 RoutePredicateFactory 作为类名后缀,并继承 AbstractRoutePredicateFactory
@Component
public class MyPredicateRoutePredicateFactory extends AbstractRoutePredicateFactory<MyPredicateRoutePredicateFactory.Config> {
    
    // 4. 设置配置类
    public MyPredicateRoutePredicateFactory() {
        super(MyPredicateRoutePredicateFactory.Config.class);
    }
    
    // 5.断言逻辑
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return (GatewayPredicate) serverWebExchange -> config.getMyPredicate().equals("cqq");
    }
    
	// 配置注入 Config 配置类的配置字段值时,字段的获取循序
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("key2", "key1");
    }
    
    @Validated
    @Data
    public static class Config {
        
        private String key1;
        
        private String key2;
    }
}

新增断言配置(多个配置项,逗号分隔):

spring:
  cloud:
    gateway:
      routes:
          predicates:
            - MyPredicate=key1,key2

访问:http://localhost:5000/route-order-service/echo/123

断言配置类Debug

由于 key2 != cqq,故响应 404。

过滤器

  1. 作用:在请求的传递过程中对请求和响应做一些处理
  2. 处理阶段:
    • Pre 请求服务节点前:可用来实现选择集群中的服务节点进行调用、记录调试信息等
    • Post 请求服务节点后:可用来实现为响应添加 HTTP Response Header、收集统计信息和指标等。
  3. 类型:局部过滤器(作用在某一个路由上)全局过滤器(作用在全部路由上)

处理阶段:Pre、Post

过滤器处理阶段

内置局部过滤器

官方提供了许多内置的局部过滤器:官网文档地址

过滤器工厂作用参数
AddRequestHeader为原始请求添加HeaderHeader的名称及值
AddRequestParameter为原始请求添加请求参数参数名称及值
AddResponseHeader为原始响应添加HeaderHeader的名称及值
DedupeResponseHeader剔除响应头中重复的值需要去重的Header名称及去重策略
Hystrix为路由引入Hystrix的断路器保护Hystrixcommand的名称
FallbackHeaders为fallbackUri的请求头中添加具体的异常信息Header的名称
PrefixPath为原始请求路径添加前缀前缀路径
PreserveHostHeader为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
RequestRateLimiter用于对请求限流,限流算法为令牌桶keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的url
RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的一系列HeaderHeader名称
RemoveResponseHeader为原始请求删除某个HeaderHeader的名称
RewritePath重写原始的请求路径原始路径正则表达式以及重写后路径的正则表达式
RewriteResponseHeader重写原始响应中的某个HeaderHeader名称,值的正则表达式,重写后的值
SaveSession在转发请求之前,强制执行websession::save操作
secureHeaders为原始响应添加一系列起安全作用的响应头无,支持修改这些安全响应头的值
SetPath修改原始的请求路径修改后的路径
SetResponseHeader修改原始响应中某个Header的值Header名称,修改后的值
SetStatus修改原始响应的状态码HTTP状态码,可以是数字,也可以是字符串
StripPrefix用于截断原始请求的路径使用数字表示要截断的路径的数量
Retry针对不同的响应进行重试retries、statuses、methods、 series
RequestSize设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返413Payload Too Large请求包大小,单位为字节,默认值为5M
ModifyRequestBody在转发请求之前修改原始请求体内容修改后的请求体内容
ModifyResponseBody修改原始响应体的内容修改后的响应体内容

简单配置一个 AddRequestHeader Filter 进行演示:

修改配置文件(与配置断言一样):

spring:
  cloud:
    gateway:
      routes:
		filters:
          - StripPrefix=1
          - AddRequestHeader=my-header,mh

修改一下原来的接口,打印一下 header:

@RestController
public class OrderController {

    @Value("${server.port}")
    private String port;

    @GetMapping(value = "/echo/{string}")
    public String echo(
            @RequestHeader(value = "my-header", required = false) String mh,
            @PathVariable String string) {
        return String.format("Order service %s %s, header %s", port, string, mh);
    }
}

访问订单服务:http://localhost:5000/route-order-service/echo/123

浏览器输出:Order service 6200 123, header mh

自定义局部过滤器

  1. 必须是一个 Spring Bean
  2. 继承 AbstractGatewayFilterFactory
  3. 设置配置类,声明配置属性接收配置文件中对应的断言信息
  4. 重写拦截方法逻辑
@Component
public class MyFilter extends AbstractGatewayFilterFactory<MyFilter.Config> {
    
    public MyFilter() {
        super(MyFilter.Config.class);
    }
    
    
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("name", "value");
    }
    
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            exchange.getRequest().mutate().header(config.getName(), config.getValue());
            return chain.filter(exchange);
        };
    }
    
    @Validated
    @Data
    public static class Config {
        
        private String name;
        
        private String value;
    }
}

新增过滤器配置(多个配置项,逗号分隔):

spring:
  cloud:
    gateway:
      routes:
          filters:
            - StripPrefix=1
            # - AddRequestHeader=my-header,mh
            - MyFilter=my-header,mh

访问订单服务:http://localhost:5000/route-order-service/echo/123

浏览器输出:Order service 6200 123, header mh

内置全局过滤器

官方提供了许多默认生效的全局过滤器:官网文档地址

内置全局处理器

比如前面配置 uri 的 lb scheme,可以起到服务负载均衡调用的效果:

gateway:
  discovery:
    locator:
      enabled: true
  routes:
    - id: order_route
      uri: lb://nacos-order-service

实现类就是 LoadBalancerClientFilter,内部根据 LoadBalancerClient 规范接口的实现类,进行负载均衡调用。

自定义全局过滤器

  1. 必须是一个 Spring Bean
  2. 实现 GlobalFilter
@Slf4j
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    
    //配置执行优先级 
    @Override
    public int getOrder() {
        return 0;
    }
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Mono<Void> filter = chain.filter(exchange);
        log.info("log request, uir: {}", exchange.getRequest().getURI());
        return filter;
    }
}

跨域配置

配置文件

spring:
  cloud:
    gateway:
      globalcors:           # 全局的跨域处理
         add-to-simple-url-handler-mapping: true # 解决 options 请求被拦截问题
         corsConfigurations:
           '[/**]':
             allowedOrigins: # 允许哪些网站的可以跨域请求, '*' 表示所有 
               - "https://localhost:8001"
               - "https://localhost:8002"
               - "https://localhost:8003"
             allowedMethods: # 允许的跨域的请求方式, '*' 表示所有
               - "GET"
               - "POST"
               - "DELETE"
               - "PUT"
               - "OPTIONS"
             allowedHeaders: "*"    # 允许在请求中携带的请求头, '*' 表示所有
             allowCredentials: true # 是否允许携带 cookie
             maxAge: 360000         # 跨域检测的有效期

配置类

@Configuration
public class GlobalCorsConfig {

    private final List<String> allowOrigins = Arrays.asList("http://localhost:8001", "http://localhost:8002", "http://localhost:8003");
    
    private final List<String> allowMethods = Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS");
    
    private final List<String> allowHeaders = Arrays.asList("header1", "header2");

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        allowOrigins.forEach(config::addAllowedOrigin);
        allowMethods.forEach(config::addAllowedMethod);
        allowHeaders.forEach(config::addAllowedHeader);
        config.setAllowCredentials(true);
        config.setMaxAge(360000L);
        config.addExposedHeader("*"); // 暴露头部信息
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

gateway 整合 sentinel

集成配置

添加依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

Sentinel 的配置依旧分为代码、管控台配置两种。后面使用管控台的方式进行配置,所以需要在 bootstrap.yaml 中配置一下 Sentinel:

spring:
  cloud:
        # 服务监控
    sentinel:
      transport:
        # 控制台的地址(ip:port)
        dashboard: 127.0.0.1:8400
        # 与控制台通讯的端口,默认是8719,不可用会一直+1
        # 用于客户端暴露 sentinel 原生 api 的访问端口,用于管控台获取客户端簇点链路、监控数据、更新规则等
        port: 5001
        # 和控制台保持心跳的ip地址
        client-ip: 127.0.0.1
        # 发送心跳的周期,默认是10s
        heartbeat-interval-ms: 3000
      # 禁止收敛URL的入口 context
      web-context-unify: false
      # 饿加载
      eager: true
  1. 启动 sentinel server
  2. 启动 order-service(内置接口如下)
@RestController
public class OrderController {
    
    @Value("${server.port}")
    private String port;
    
    @GetMapping(value = "/echo/{string}")
    public String echo(
            @RequestHeader(value = "my-header", required = false) String mh,
            @PathVariable String string) {
        return String.format("Order service %s %s, header %s", port, string, mh);
    }
    
    @GetMapping(value = "/get")
    public String get() {
        return String.format("Order service %s Get %s ", port, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
    }
}
  1. 启动 gateway-service
  2. 访问:http://localhost:5000/route-order-service/echo/123
  3. 查看 Sentinel 管控台
    gateway-sentinel 管控台

网关服务与普通服务在 Sentinel 中的不同

普通服务菜单:

我们启动一个集成了 Sentinel 的服务:

普通服务的Sentinel菜单

网关服务菜单:

网关服务的Sentinel菜单

减少了:

  1. 热点规则
  2. 授权规则
  3. 集群流控

新增了:

  1. API 管理

并且流控规则的配置也多了很多属性(熔断降级依旧保持不变):

网关服务的流控规则

那我们就着重关注一下网关的流控规则配置 与 API 管理。

API 管理菜单

配置需要防护的接口组,当配置流控规则时进行关联。

新增API组

API组列表

一个 API 分组中可以配置多个匹配模式,匹配规则一目了然,上面分别配置了精确与前缀匹配(原本想通过正则配置 /echo/**的,不知为何只有配置前缀模式中才生效)。

网关的流控规则

配置好了 API 组后,我们在配置一下流控规则,并绑定刚刚创建好的 API 组:

流控规则关联API组
对比之前普通服务的流控规则,网关服务多出了很多配置:

  1. API 类型:
    • Route ID:配置文件中的路由主键
    • API 分组:API 管理中的 API 组
  2. 针对请求属性 & 属性值匹配:更细粒度的配置需要流控的请求,效果等价于配置文件中的路由断言。这两个属性是必须要同时配置的,这样才会有意义。
    • 请求属性:
    • Client IP:匹配指定的客户端 IP
    • Remote Host:匹配指定的客户端域名
    • Header:匹配指定名称的请求头
    • URL 参数:匹配指定名称的 URL 请求参数
    • Cookie:匹配指定名称的 Cookie
    • 匹配模式:
    • 精确
    • 子串:like {匹配串}%
    • 正则
  3. 间隔:熔断规则中的时间窗口
  4. Brust size:达到 QPS 阈值时,最多可在容忍的请求数。

全局异常处理器

@Configuration
public class SentinelHandlerConfig implements InitializingBean {
    
    @Data
    @Builder(toBuilder = true)
    @Accessors(chain = true)
    public static class R<T> {
        
        private long code;
        
        private T data;
        
        private String message;
        
        public R() {
        }
        
        public R(long code, T data, String message) {
            super();
            this.code = code;
            this.data = data;
            this.message = message;
        }
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
        GatewayCallbackManager.setBlockHandler(
                (exchange, throwable) ->
                        ServerResponse.status(HttpStatus.OK)
                                .contentType(MediaType.APPLICATION_JSON)
                                .body(
                                        BodyInserters.fromValue(
                                                R.builder().code(HttpStatus.TOO_MANY_REQUESTS.value()).message("已限流").build()
                                        )
                                ));
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值