7-Spring Cloud Alibaba实战-Gateway网关核心原理

专栏目录

  1. 1-Spring Cloud Alibaba实战-系统架构演变与微服务常见问题
  2. 2-Spring Cloud Alibaba实战-简介与组件说明
  3. 3-Spring Cloud Alibaba实战-全组件项目搭建手册
  4. 4-Spring Cloud Alibaba实战-服务治理
  5. 5-Spring Cloud Alibaba实战-Nacos Config–配置中心
  6. 6-Spring Cloud Alibaba实战-Gateway网关核心架构
  7. 7-Spring Cloud Alibaba实战-Gateway网关核心原理
  8. 8-Spring Cloud Alibaba实战-分布式服务治理机制
  9. 9-Spring Cloud Alibaba实战-Sentinel原理
  10. 10-Spring Cloud Alibaba实战-Sleuth–链路追踪
  11. 11-Spring Cloud Alibaba实战-Nacos核心原理

Gateway核心架构

基本概念

路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:

id,路由标识符,区别于其他 Route。

uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。

order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。

predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。

fifilter,过滤器用于修改请求和响应信息。

执行流程

执行流程大体如下:

GatewayClient向GatewayServer发送请求

请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文

然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给

RoutePredicateHandlerMapping

RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用

如果过断言成功,由FilteringWebHandler创建过滤器链并调用

请求会一次经过PreFilter–微服务-PostFilter的方法,最终返回响应

断言

Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。断言就是说:在 什么条件下 才能进行路由转发

内置路由断言工厂

SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。具体如下:

基于Datetime类型的断言工厂

此类型的断言根据时间做判断,主要有三个:

AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内

-After=2019-12-31T23: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

内置路由断言工厂的使用

接下来我们验证几个内置断言的使用:

server:
	port: 7000
spring:
	application:
		name: api-gateway 
	cloud:
		nacos:
			discovery:
				server-addr: 127.0.0.1:8848
	gateway:	
		discovery:
			locator:
				enabled: true
		routes:
			- id: product_route
			  uri: lb://service-product
			  predicates:
				- Path=/product-serv/** 
				- Before=2019-11-28T00:00:00.000+08:00 #限制请求时间在2019-11-28之前
				- Method=POST #限制请求方式为POST filters:
				- StripPrefix=1

自定义路由断言工厂

spring: 
	application: 
		name: api-gateway 
	cloud: 
		nacos: 
			discovery: 
				server-addr: 127.0.0.1:8848 
		gateway: 
			discovery: 
				locator: 
					enabled: true 
			routes: 
				- id: product-route 
				uri: lb://service-product 
				predicates: 
					- Path=/product-serv/** 
					- Age=18,60 # 限制年龄只有在18到60岁之间的人能访问 
				filters: - StripPrefix=1

我们来设定一个场景: 假设我们的应用仅仅让age在(min,max)之间的人来访问。第1步:在配置文件中,添加一个Age的断言配置

第2步:自定义一个断言工厂, 实现断言方法

package com.naixue.predicates; //泛型 用于接收一个配置类,配置类用于接收中配置文件中的配置 
@Component 
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> { public AgeRoutePredicateFactory() { 	 	super(AgeRoutePredicateFactory.Config.class);
} 

//用于从配置文件中获取参数值赋值到配置类中的属性上@Override

public List<String> shortcutFieldOrder() {
//这里的顺序要跟配置文件中的参数顺序一致
return Arrays.asList("minAge", "maxAge");
}
//断言@Override
public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config
config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
//从serverWebExchange获取传入的参数String ageStr =
serverWebExchange.getRequest().getQueryParams().getFirst("age");
if (StringUtils.isNotEmpty(ageStr)) {
int age =Integer.parseInt(ageStr);
return age >config.getMinAge() && age< config.getMaxAge();
}
return true;
}
};
}
}
//自定义一个配置类, 用于接收配置文件中的参数
@Data
private intminAge; private intmaxAge; 
}

第4步:启动测试

#测试发现当age在(20,60)可以访问,其它范围不能访问
http://localhost:7000/product-serv/product/1?age=30
http://localhost:7000/product-serv/product/1?age=10

过滤器

三个知识点:

作用: 过滤器就是在请求的传递过程中,对请求和响应做一些手脚

生命周期: PrePost

分类: 局部过滤器(作用在某一个路由上) 全局过滤器(作用全部路由上)

在Gateway中, Filter的生命周期只有两个:“pre” 和 “post”。

PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。 POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

在这里插入图片描述

Gateway的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。

GatewayFilter:应用到单个路由或者一个分组的路由上。GlobalFilter:应用到所有的路由上。

局部过滤器

局部过滤器是针对单个路由的过滤器。

内置局部过滤器

在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下:

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

自定义局部过滤

第1步:在配置文件中,添加一个Log的过滤器配置

server: 
  port: 7000 
spring: 
  application: 
    name: api-gateway 
    cloud: 
      nacos: 
        discovery: 
          server-addr: 
            localhost:8848 
    gateway: 
      discovery: 
        locator: 
          enabled: true 
      routes: 
        - id: consumer 
        order: -1 
        uri: lb://consumer 
        predicates: 
          - Path=/consumer-serv/** 
        filters: 
          - StripPrefix=1 
          - Log=true,false # 控制日志是否开启

第2步:自定义一个过滤器工厂,实现方法

//自定义局部过滤器@Component
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
public class LogGatewayFilterFactory
        extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
    //构造函数
    public LogGatewayFilterFactory() {
        super(LogGatewayFilterFactory.Config.class);
    }

    //读取配置文件中的参数 赋值到 配置类中
    @Override
    public List<String> shortcutFieldOrder() {

        return Arrays.asList("consoleLog", "cacheLog");

    }

    //过滤器逻辑
    @Override
    public GatewayFilter apply(LogGatewayFilterFactory.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;
}

第3步:启动测试

全局过滤器

全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。

内置全局过滤器

SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:
在这里插入图片描述

自定义全局过滤器

内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己 编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。

开发中的鉴权逻辑:

当客户端第一次请求服务时,服务端对用户进行信息认证(登录)

认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证 以后每次请求,客户端都携带认证的token

服务端对token进行解密,判断是否有效。

在这里插入图片描述

如上图,对于验证用户是否已经登录鉴权的过程可以在网关统一检验。检验的标准就是请求中是否携带token凭证以及token的正确性。

下面的我们自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求 参数“token”则不转发路由,否则执行正常的逻辑。

package com.naixue.filters;

import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

//自定义全局过滤器需要实现GlobalFilter和Ordered接口
@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.isBlank(token)) {
            System.out.println("鉴权失败");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        //调用chain.filter继续向下游执行
        return chain.filter(exchange);
    }

    //顺序,数值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

网关限流

官方指南:https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81

网关是所有请求的公共入口,所以可以在网关进行限流,而且限流的方式也很多,我们本次采用前面学 过的Sentinel组件来实现网关的限流。Sentinel支持对SpringCloudGateway、Zuul等主流网关进行限流。

从1.6.0版本开始,Sentinel提供了SpringCloud Gateway的适配模块,可以提供两种资源维度的限流: route维度:即在Spring配置文件中配置的路由条目,资源名为对应的routeId

自定义API维度:用户可以利用Sentinel提供的API来自定义一些API分组

导入依赖
<dependency> 
<groupId>com.alibaba.csp</groupId> 
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> </dependency>

编写配置类

基于Sentinel 的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的

SentinelGatewayFilter实例以及 SentinelGatewayBlockExceptionHandler 实例即可。

/**
 * @author kojon
 * @date 2020/4/17
 */
@Configuration
public class SCGConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

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

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

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

    @PostConstruct
    public void doInit() {
        initCustomizedApis();
        initGatewayRules();
        initBlockHandlers();
    }

    private void initCustomizedApis() {
        Set<ApiDefinition> definitions = new HashSet<>();
        ApiDefinition api = new ApiDefinition("gateway-route")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    //分组定义
                    add(new ApiPathPredicateItem().setPattern("/group/**")
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                    add(new ApiPathPredicateItem().setPattern("/discovery/**")
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
        definitions.add(api);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

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

    /**
     * 自定义限流异常页面
     */
    public void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable)
                -> ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS).contentType(MediaType.APPLICATION_JSON).
//                body(BodyInserters.fromValue(HttpStatus.TOO_MANY_REQUESTS));
                body(BodyInserters.fromValue("GatewayApi current limiting"));
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
} 
测试

在一秒钟内多次访问http://localhost:7000/group/路径下的接口就可以看到限流启作用了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xianghan收藏册

极简精品作,一分也是一份鼓励哦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值