有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步, 认准 https://blog.zysicyj.top
SpringCloud 技术栈 开发分布式系统可能具有挑战性,复杂性已从应用程序层转移到网络层,并要求服务之间进行更多的交互。将代码设为 “cloud-native” 就需要解决 12-factor,例如外部配置,服务无状态,日志记录以及连接到备份服务之类的问 题,Spring Cloud 项目套件包含使您的应用程序在云中运行所需的许多服务。
SpringCloud 技术栈 SpringCloud 技术栈非常丰富,这也是 SpringCloud 为什么在微服务领域中 如此受欢迎的原因之一,技术栈如上图,在服务注册与配置、服务调用、微服务网关、消息组件、链路追踪、配置中心、安全控制、将极限流等诸多方面技 术栈都比较完善,而且阿里巴巴也出了一套 SpringCloudAlibaba 版本,主要集 成了 Alibaba 中主流的技术栈。
SpringCloud 经典技术介绍 微服务项目近几年非常火爆,推出来的相关技术解决方案热度也非常活 跃,但 SpringCloud 技术栈中也有一部分技术组件在逐步被淘汰或者闭源,但都有更优秀的技术方案替代。在不久的将来,那些闭源或者将被淘汰的技术有很大概率将不在项目中使用,所以我们学习的时候可以直接学习更优秀的替代技术方案。
Eureka 闭源 上面英文大概意思是:Eureka2.0 的开源工作已经停止,依赖于开源库 里面的 Eureka2.x 分支构建的项目或者相关代码,风险自负。
Eureka 在微服务项目中主要承担服务注册与发现工作,可以替代的技术方案比较多,而且很多方案都比 Eureka 优秀,比如 Consul、Nacos 等。
Hystrix 停止更新
Hystrix 在项目中主要做服务熔断、降级,但官方宣布将不在开发,目前处于维护状态,但官方表示 1.5.18 版本的 Hystrix 已经足够稳定,可以满足 Netflix 现有应用的需求。
关于 Hystrix 可替代的产品也比较多,比如官方推荐的 resilience4j,resilience4j 是一个轻量级熔断框架,但 resilience4j 目前在国内使用频率还不高,功能也不够强,我们更推荐使用功能更加强悍的 SpringCloudAlibabaSentinel。
Zuul 过时 Zuul 是一个微服务网关技术,但 Zuul1.x 使用的是阻塞式的 API,不支持长连接,没有提供异步,高并发场景下性能低。SpringCloud 官网推出了全新的微服务网关技术 SpringCloudGateway,比 Zuul 性能更强悍、功能更丰富、且支持异步等多种特性。
SpringCloud Config 实用性差 SpringCloudConfig 主要用于管理项目的配置文件,每次要使用 SpringCloudConfig 的时候,总得经过一波操作和配置的折腾,才可以使用 SpringCloudConfig 实现配置管理,而且单独使用无法支持配置实时刷新,在项目中用起来,真比较头疼的。
当前有很多技术可以取代 SpringCloudConfig,比如携程的 Apollo、SpringCloudAlibabaNacos,功能都比 SpringCloudConfig 强,并且支持实时刷新配置。
SpringCloud Bus 实用性差 SpringCloud Bus 是服务消息总线,主要实现通知多个服务执行某个任务,一般和 SpringCloud Config 一起使用。这个功能其实不太使用,因为很多任务组件基本都具备消息通知功能,比如 Nacos、Apollo 都能实现所有服务订阅执行相关操作。
SpringCloud 项目场景
微服务技术目前已经在很多国内外大厂中都在广泛使用,那么在项目中该如何使用微服务技术呢?我们以滴滴快车业务未来,来讲解一下微服务技术结合业务应用讲解一下。
打车业务如上图:
1 2 3 4 5 6 1: 打车的时候会选择车型,选择车型我们调用过程是:Gateway->Driver(加载司机列表) 2: 选择车型后确认打车,相当于要下单了,调用过程是:Gateway->Order(下单)->Driver(司机状态更改) 3: 打车结束后,用户进入支付,调用过程是:Gateway->Pay(支付)->Driver(更新司机状态) ->Order (更新订单状态)
SpringCloud Gateway Spring Cloud Gateway 是 Spring Cloud 团队的一个全新项目,基于 Spring5.0、SpringBoot2.0、Project Reactor 等技术开发的网关。旨在为微服务架构提供一种简单有效统一的 API 路由管理方式。
Spring Cloud Gateway 作为 SpringCloud 生态系统中的网关,目标是替代 Netflix Zuul。Gateway 不仅提供统一路由方式,并且基于 Filter 链的方式提供网关的基本功能。例如:安全,监控 / 指标,和限流。
** 总结:微服务网关就是一个系统,通过暴露该微服务网关系统,方便我们进行相关的鉴权,安全控制,日志统一处理,易于监控,限流等相关功能。 **
现微服务网关的技术有很多,
nginx:Nginx(engine x) 是一个高性能的 HTTP 和反向代理 web 服务器,同时也提供了 IMAP/POP3/SMTP 服务
zuul:Zuul 是 Netflflix 出品的一个基于 JVM 路由和服务端的负载均衡器。
spring-cloud-gateway:是 spring 出品的基于 spring 的网关项目,集成断路器,路径重写,性能比 Zuul 好。 我们使用 gateway 这个网关技术,无缝衔接到基于 springcloud 的微服务开发中来。
gateway 官网:https://spring.io/projects/spring-cloud-gateway
Gateway 工作原理 我们在学习 Gateway 之前,先弄清楚 Gateway 的工作原理,后面使用它的各个功能时,就知道该如何使用了,工作流程图如下:
Gateway 的执行流程如下:
1 2 3 4 5 6 1:Gateway 的客户端回向 SpringCloudGateway 发起请求,请求首先会被 HttpWebHandlerAdapter 进行提取组装成网关的上下文,然后网关的上下文会传递到 DispatcherHandler。 2:DispatcherHandler 是所有请求的分发处理器,DispatcherHandler 主要负责分发请求对应的处理器,比如将请求分发到对应 RoutePredicateHandlerMapping(路由断言处理器映射器)。 3: 路由断言处理映射器主要用于路由的查找,以及找到路由后返回对应的 FilteringWebHandler。 4:FilteringWebHandler 主要负责组装 Filter 链表并调用 Filter 执行一系列 Filter 处理,然后把请求转到后端对应的代理服务处理,处理完毕后,将 Response 返回到 Gateway 客户端。在 Filter 链中,通过虚线分割 Filter 的原因是,过滤器可以在转发请求之前处 理或者接收到被代理服务的返回结果之后处理。所有的 Pre 类型的 Filter 执行完毕之后,才会转发请求到被代理的服务处理。被代理的服务把所有请求完毕之后,才会执行 Post 类型的过滤器。
Gateway 路由 Gateway 路由配置分为基于配置的静态路由设置和基于代码动态路由配置,静态路由是指在 application.yml 中把路由信息配置好了,而动态路由则支持在代码中动态加载路由信息,更加灵活,我们接下来把这 2 种路由操作都实现一次。
业务说明
1 2 3 4 1: 用户所有请求以 /order 开始的请求,都路由到 hailtaxi-order 服务 2: 用户所有请求以 /driver 开始的请求,都路由到 hailtaxi-driver 服务 3: 用户所有请求以 /pay 开始的请求,都路由到 hailtaxi-pay 服务
基于配置路由设置 如上图所示,正是 Gateway 静态路由配置:
1 2 3 4 1: 用户所有请求以 /order 开始的请求,都路由到 hailtaxi-order 服务 2: 用户所有请求以 /driver 开始的请求,都路由到 hailtaxi-driver 服务 3: 用户所有请求以 /pay 开始的请求,都路由到 hailtaxi-pay 服务
配置参数说明:
1 2 3 4 5 6 7 8 9 routes: 路由配置 -id: 唯一标识符 uri: 路由地址,可以是 lb://IP: 端口 也可以是 lb://${spring.application.name} predicates: 断言,是指路由条件 -Path=/driver/**: 路由条件。Predicate 接受一个输入参数,返回一个布尔值结果。这里表示匹配所有以 driver 开始的请求。 filters: 过滤器 -StripPrefix=1: 真实路由的时候,去掉第 1 个路径,路径个数以 / 分割区分
测试 url:http://localhost:8001/driver/info/1
基于代码路由配置 我们同样实现上面的功能,但这里基于代码方式实现。所有路由规则我们可以从数据库中读取并加载到程序中。基于代码的路由配置我们只需要创建 RouteLocator 并添加路由配置即可,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /*** * 路由配置 *@parambuilder *@return */ @Bean publicRouteLocatorrouteLocator(RouteLocatorBuilderbuilder) { returnbuilder.routes() .route("hailtaxi-driver",r-> r.path("/driver/**").uri("lb://hailtaxi-driver")) .route("hailtaxi-order",r-> r.path("/order/**").uri("lb://hailtaxi-order")) .route("hailtaxi-pay",r-> r.path("/pay/**").uri("lb://hailtaxi-pay")) .build(); }
在真实场景中,基于配置文件的方式更直观、简介,但代码的路由配置是更强大,可以实现很丰富的功能,可以把路由规则存在数据库中,每次直接从数据库中加载规则,这样的好处是可以动态刷新路由规则,通常应用于权限系统动态配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 spring: cloud: gateway: #路由配置 routes: #唯一标识符 -id:hailtaxi-driver uri:lb://hailtaxi-driver #路由断言 predicates: -Path=/driver/** #唯一标识符 -id:hailtaxi-order uri:lb://hailtaxi-order #路由断言 predicates: -Path=/order/** #唯一标识符 -id:hailtaxi-pay uri:lb://hailtaxi-pay #路由断言 predicates: -Path=/pay/**
Gateway-Predicate 上面路由匹配规则中我们都用了 -Path 方式,其实就是路径匹配方式,除了路径匹配方式,Gateway 还支持很多丰富的匹配方式,我们对这些方式分别进行讲解。 关于 Predicate 学习地址,可以参考官网:https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gateway-request-predicates-factories
或者:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1RELEASE/single/spring-cloud-gateway.html#gateway-request-predicates-factories
routes 下面的属性含义如下:
1 2 3 id:我们自定义的路由 ID,保持唯一 uri:目标服务地址 predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该属性包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)
Predicate 来源于 Java 8,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,通过 Header、请求参数等不同的条件来作为条件匹配到对应的路由。
下面的一张图 (来自网络) 总结了 Spring Cloud 内置的几种 Predicate 的实现:
我们在这里讲解几个断言匹配 方式。
Cookie Gateway 的 Cookie 匹配接收两个参数:一个是 Cookie name, 一个是正则表达式。路由规则就是通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。如下配置:
1 2 3 4 5 6 7 gateway: routes: -id:hailtaxi-driver uri:lb://hailtaxi-driver predicates: -Path=/driver/** -Cookie=username,itheima
这里表示请求携带了 cookie 为 username 的数据,并且值为 itheima,就允许通过。
Header 匹配 和 Cookie 匹配 一样,也是接收两个参数,一个 header 中属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。配置如下
1 2 3 4 5 6 7 gateway: routes: -id:hailtaxi-driver uri:lb://hailtaxi-driver predicates: -Path=/driver/** -Header=token,^(?!\d+$)[\da-zA-Z]+$
上面的匹配规则,就是请求头要有 token 属性,并且值必须为数字和字母组合的正则表达式,例如携带 token=19and30 就可以通过访问。
请求方式匹配 通过请求的方式是 POST、GET、PUT、DELETE 等进行路由。配置如下:
1 2 3 4 5 6 7 gateway: routes: -id:hailtaxi-driver uri:lb://hailtaxi-driver predicates: -Path=/driver/** -Method=GET,POST
通过 yml 的完整代码如下(注释掉 java 的配置)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 server: port:8001 spring: application: name:hailtaxi-gateway main: allow-bean-definition-overriding:true cloud: #Consul 配置 consul: host:127.0.0.1 port:8500 discovery: #注册到 Consul 中的服务名字 s e r v i c e - n a m e : ${spring.application.name} #注册的服务的实例 Id,最好不要重复,这里参考官网建议的方式带 随机数默认:应用名:port #instance-id: ${spring.application.name}:${vcap.application.instance_id:${s pring.application.instance_id:${random.value}}} #自定义实例 id 为: 应用名:ip:port instance-id: ${spring.application.name}:${spring.cloud.client.ip- address}:${server.port} prefer-ip-address:true #开启服务注册 register:true #开启服务发现 enabled:true #2 分钟之后健康检查未通过取消注册 health-check-critical-timeout:2m #consul 健康检查的轮询周期 health-check-interval:10s gateway: #路由配置 routes: #唯一标识符 -id:hailtaxi-driver uri:lb://hailtaxi-driver #路由断言 predicates: -Path=/driver/** -Cookie=username,itheima -Header=token,^(?!\d+$)[\da-zA-Z]+$ -Method=GET,POST #唯一标识符 -id:hailtaxi-order uri:lb://hailtaxi-order #路由断言 predicates: -Path=/order/** #唯一标识符 -id:hailtaxi-pay uri:lb://hailtaxi-pay #路由断言 predicates: -Path=/pay/**
断言源码剖析 拿 Cookie 断言来说,首先看它的体系结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 publicclassCookieRoutePredicateFactory extends AbstractRoutePredicateFactory<CookieRoutePredicateFactory.Con fig>{ /** *Namekey. */ publicstaticfinalStringNAME_KEY="name"; /** *Regexpkey. */ publicstaticfinalStringREGEXP_KEY="regexp"; publicCookieRoutePredicateFactory(){ super(Config.class); } /* 通过 shortcutFieldOrder 方法设置 Config 配置类中的属性, 需要根据 具体的规则来设置 通过 shortcutType 方法获取具体规则,具体参看: org.springframework.cloud.gateway.support.ShortcutConfigurabl e.ShortcutType 规则包括以下几种: DEFAULT: 按照 shortcutFieldOrder 顺序依次赋值 */ @Override publicList<String>shortcutFieldOrder(){ returnArrays.asList(NAME_KEY,REGEXP_KEY); } @Override publicPredicate<ServerWebExchange>apply(Configconfig) { returnnewGatewayPredicate(){ @Override publicbooleantest(ServerWebExchangeexchange){ List<HttpCookie>cookies= exchange.getRequest().getCookies() .get(config.name); if(cookies==null){ returnfalse; } for(HttpCookiecookie:cookies){ if (cookie.getValue().matches(config.regexp)){ returntrue; } } returnfalse; } @Override publicStringtoString(){ returnString.format("Cookie:name=%s regexp=%s",config.name, config.regexp); } }; } /* 内部配置类是用来接收在配置文件中配置的参数的 routes: #唯一标识符 -id:hailtaxi-driver uri:lb://hailtaxi-driver #路由断言 predicates: -Cookie=username,itheima */ @Validated publicstaticclassConfig{ @NotEmpty privateStringname; @NotEmpty privateStringregexp; publicStringgetName(){ returnname; } publicConfigsetName(Stringname){ this.name=name; returnthis; } publicStringgetRegexp(){ returnregexp; } publicConfigsetRegexp(Stringregexp){ this.regexp=regexp; returnthis; } } }
尽管 Spring Cloud Gateway 已经包含了很多路由匹配规则,有时候我们需要开发自定义路由匹配规则来满足需求,下面简单的介绍一下如何自定义路由匹配规则。
案例 需求:转发带 token 的请求到 hailtaxi-drvier 服务中,这里定义请求带 token 是指包含某个请求头的请求,至于是什么请求头可以由配置指定
修改配置文件
1 2 3 4 5 6 7 8 9 10 11 12 gateway: #路由配置 routes: #唯一标识符 -id:hailtaxi-driver uri:lb://hailtaxi-driver #路由断言 predicates: #自定义一个 Token 断言, 如果请求包含 Authorization 的 token 信息则通过 -Token=Authorization
创建 RoutePredicateFactory断言工厂默认命名规则必须按照 “ 名称 “+RoutePredicateFactory,如上 TokenRoutePredicateFactory 的断言名称为 Token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Slf4j @Component// 要交给 spring 容器管理 publicclassTokenRoutePredicateFactoryextends AbstractRoutePredicateFactory<TokenRoutePredicateFactory.Conf ig>{ publicTokenRoutePredicateFactory(){ super(Config.class); } publicPredicate<ServerWebExchange>apply(Configconfig) { returnexchange->{ // 打印配置文件参数值 StringheaderName=config.getHeaderName(); HttpHeadersheaders= exchange.getRequest().getHeaders(); List<String>header=headers.get(headerName); log.info("TokenPredicateheaders:{}",header); // 断言返回的是 boolean 值 returnheader!=null&&header.size()>0; }; } @Override publicList<String>shortcutFieldOrder(){ returnArrays.asList("headerName");// 指定配置文件中加载 到的配置信息应填充到 Config 的哪个属性上 } @Override publicShortcutTypeshortcutType(){ returnShortcutType.DEFAULT; } @Data publicstaticclassConfig{//staticclass privateStringheaderName;// 存储从配置文件中加载的配置 } }
启动测试:http://localhost:8001/driver/info/1
Gateway 过滤器 Spring Cloud Gateway 根据作用范围划分为GatewayFilter 和GlobalFilter ,二者区别如下:
GatewayFilter: 需要通过 spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过 spring.cloud.gateway.default-filters 配置在全局,作用在所有路由上;gateway 内置了多种过滤器工厂,配套的过滤器可以直接使用,如下图所示:
GlobalFilter: 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过 GatewayFilterAdapter 包装成 GatewayFilterChain 可识别的过滤器,它为请求业务以及路由的 URI 转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。 过滤器作为 Gateway 的重要功能。常用于请求鉴权、服务调用时长统计、修改请求或响应 header、限流、去除路径等等。 关于 Gateway 过滤器的更多使用,大家可以参考官方地址:https://docs.springio/spring-cloud-gateway/docs!RELEASE/reference/html/#gatewayfilter-factories
或者:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#gatewayfilterfactories
过滤器分类 1 2 3 4 5 6 7 8 默认过滤器: 出厂自带,实现好了拿来就用,不需要实现 全局默认过滤器 局部默认过滤器 自定义过滤器: 根据需求自己实现,实现后需配置,然后才能用哦。 全局过滤器: 作用在所有路由上。 局部过滤器: 配置在具体路由下,只作用在当前路由上。
默认过滤器十好几个,常见如下:
默认过滤器的使用 所谓默认过滤器就是系统自带的。有很多,这里简要说明几个:(通过 java 配置,注释掉 yaml 配置)
1. 添加响应头 AddResponseHeaderGatewayFilterFactory 属于 GatewayFilter
对输出响应头设置属性,比如对输出的响应设置其头部属性名称为:X-Response-Default-MyName, 值为 itheima
修改配置文件,配置如下:
1 2 3 4 5 6 7 spring: cloud: gateway: # 配置全局默认过滤器 作用在所有路由上,也可单独为某个路由配置 default-filters: # 往响应过滤器中加入信息 - AddResponseHeader=X-Response-Default-MyName,itheima
请求http://localhost:8001/driver/info/1 ,响应数据添加了 X-Response-Default-MyName: itheima,如下图:
前缀处理 在项目中做开发对接接口的时候,我们很多时候需要统一 API 路径,比如统一以 /api 开始的请求调用 hailtaxi-driver 服务,但真实服务接口地址又没有 /api 路径,我们可以使用 Gateway 的过滤器处理请求路径。
在 gateway 中可以通过配置路由的过滤器 StripPrefix 实现映射路径中的前缀处理,我们来使用一下该过滤器,再进一步做说明。
1 2 3 4 5 6 7 8 gateway: routes: - id: hailtaxi-driver uri: lb://hailtaxi-driver predicates: - Path=/api/driver/** filters: - StripPrefix=1
此处 - StripPrefix=1 表示真实请求地址是当前用户请求以 /api 开始的 uri 中去除第 1 个路径 /api
上面配置最终执行如下表:
有时候为了简化用户请求地址,比如用户请求 http://localhost:8001/info/1
我们想统一路由到 http://localhost:18081/driver/info/1
,可以使用 PrefixPath 过滤器增加前缀。
1 2 3 4 5 6 7 8 9 gateway: routes: - id: hailtaxi-driver uri: lb://hailtaxi-driver predicates: - Path=/** filters: - PrefixPath=/driver
上面配置最终执行如下表
自定义 GatewayFilter 实现 GatewayFilter 接口 GatewayFilter 一般作用在某一个路由上,需要实例化创建才能使用,局部过滤器需要实现接口 GatewayFilter、Ordered。 创建 com.itheima.filter.PayFilter 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class PayFilter implements GatewayFilter,Ordered { /*** * 过滤器执行拦截 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("GatewayFilter 拦截器执行 ---pre----- PayFilter"); return chain.filter(exchange).then(Mono.fromRunnable(()->{ System.out.println("GatewayFilter 拦截器执行 ---post- ----PayFilter"); })); } @Override public int getOrder() { return 0; } }
使用局部过滤器:(使用下面 RouteLocator 的时候,配置文件中的路由记得注释或删除)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 /*** * 路由配置 * @param builder * @return */ @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes() .route("hailtaxi-driver", r -> r.path("/api/driver/**") .and().cookie("username","itheima") .and().header("token","123456") .filters(f->f.filter(new PayFilter()).addResponseHeader("X-Response-Default-MyName", "itheima") .addRequestHeader("myheader", "1234567") .stripPrefix(1) ) // .and().method(HttpMethod.POST) .uri("lb://hailtaxi-driver") //.filter(new PayFilter()) ) .route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order")) .route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay")) .build(); }
为了更好看到效果,我们在 RouterFilter 添加 System.out.println(“GlobalFilter 拦截器执行 “); 再访问测试。
访问:http://localhost:8001/api/driver/info/1
,注意使用 postman 发送请求时添加请求头,添加 cookie。
继承 GatewayFilterFactory 如果定义局部过滤器,想在配置文件中进行配置来使用,可以继承 AbstractGatewayFilterFactory 抽象类或者 AbstractNameValueGatewayFilterFactory
这两个抽象类的区别就是前者接收一个参数(像 StripPrefix 和我们创建的这种),后者接收两个参数(像 AddResponseHeader)
代码的编写可以参考:StripPrefixGatewayFilterFactory 和 AddRequestHeaderGatewayFilterFactory
过滤器工厂默认命名规则必须按照 “ 名称 “+GatewayFilterFactory`,如上 StripPrefixGatewayFilterFactory 的过滤器名称为 StripPrefix
继承 AbstractGatewayFilterFactory 在网关中统一支付方式,编写一个过滤器:PayMethodGatewayFilterFactory,
编写过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Slf4j @Component // 一定要将其交给 spring 容器管理 public class PayMethodGatewayFilterFactory extends AbstractGatewayFilterFactory<PayMethodGatewayFilterFactory.Co nfig> { public PayMethodGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { String paymethod = config.getPayMethod(); String msg = config.getMsg(); log.info("PayMethodGatewayFilterFactory 加载到的配 置信息为:{}---{}",paymethod,msg); // 将 paymethod 添加到请求头中 exchange.getRequest().mutate().header("paymethod",paymethod) ; return chain.filter(exchange); }; } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("payMethod","msg");// 指定从 yml 中 提前出来的配置信息填充到配置类中哪个属性, 按规则配置 } @Override public ShortcutType shortcutType() { return ShortcutType.DEFAULT;// 默认规则 } /** * 加载从 yml 中提取出来的配置信息 */ @Data public static class Config { private String payMethod; private String msg; } }
配置文件中使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 gateway: #路由配置 routes: #唯一标识符 - id: hailtaxi-driver uri: lb://hailtaxi-driver #路由断言 predicates: - Path=/driver/** - Cookie=username,itheima - Header=token,^(?!\d+$)[\da-zA-Z]+$ - Method=GET,POST - Token=Authorization filters: - PayMethod=alipay, 业务整合
再次测试,查看 hailtaxi-driver 服务接收到请求后是否多了 paymethod 请求头信息
继承 AbstractNameValueGatewayFilterFactory 直接查看 AddRequestHeaderGatewayFilterFactory 源码,分析即可!
自定义 GlobalFilter 定义全局过滤器需要实现 GlobalFilter,Ordered 接口:
1 2 3 4 GlobalFilter: 过滤器拦截处理方法 Ordered: 过滤器也有多个,这里主要定义过滤器执行顺序,里面有个方法 getOrder() 会返回过滤器执行顺序,返回值越小,越靠前执行
需求:
我们创建全局过滤器并完成常见业务用户权限校验,如果请求中有带有一个名字为 token 的请求参数,则认为请求有效放行,如果没有则拦截提示授权无效。
创建全局过滤器:com.itheima.filter.RouterFilter,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Component public class RouterFilter implements GlobalFilter,Ordered { /*** * 路由拦截 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("RouterFilter----------------"); // 获取请求参数 String token = exchange.getRequest().getQueryParams().getFirst("token"); // 如果 token 为空,则表示没有登录 if(StringUtils.isEmpty(token)){ // 没登录, 状态设置 403 exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_ LARGE); // 结束请求 return exchange.getResponse().setComplete(); } // 放行 return chain.filter(exchange); } /*** * 拦截器顺序 * @return */ @Override public int getOrder() { return 0; } }
此时请求,我们不携带 token 参数,效果如下:
我们携带 token 参数则可以正常访问,效果如下:
跨域配置 出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的 javascript 脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。
在 Spring Cloud Gateway 中配置跨域是非常简单的,如下面 application.yml 所示:
1 2 3 4 5 6 7 8 9 10 gateway: globalcors: corsConfigurations: '[/**]': allowedOrigins: "*" allowedMethods: - GET - POST - PUT
另外一种写法就需要创建 CorsWebFilter 过滤器,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /** * 配置跨域 * @return */ @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); // cookie 跨域 config.setAllowCredentials(Boolean.TRUE); config.addAllowedMethod("*"); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); // 配置前端 js 允许访问的自定义响应头 config.addExposedHeader("Authorization"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); }
限流 (自学) 网关可以做很多的事情,比如,限流,当我们的系统 被频繁的请求的时候,就有可能 将系统压垮,所以 为了解决这个问题,需要在每一个微服务中做限流操作,但是如果有了网关,那么就可以在网关系统做限流,因为所有的请求都需要先通过网关系统才能路由到微服务中。
令牌桶算法讲解 令牌桶算法是常见的限流算法之一,我们讲解一下漏桶算法:
1 2 3 4 5 6 7 8 1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理; 2)根据限流大小,设置按照一定的速率往桶里添加令牌; 3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝; 4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻 辑,处理完业务逻辑之后,将令牌直接删除; 5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不 会删除令牌,以此保证足够的限流
令牌桶算法的实现,有很多技术,Guaua 是其中之一,redis 客户端也有其实现。
限流案例 spring cloud gateway 默认使用 redis 的 RateLimter 限流算法来实现,外面来简要实现一下:
首先需要引入 redis 的依赖:
1 2 3 4 5 6 7 8 <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis- reactive</artifactId> <version>2.2.1.RELEASE</version> </dependency>
同时不要忘记 Redis 配置:
1 2 3 4 redis: host: 127.0.0.1 port: 6379
定义 KeyResolver 在 Application 引导类中添加如下代码,KeyResolver 用于计算某一个类型的限流的 KEY 也就是说,可以通过 KeyResolver 来指定限流的 Key。
我们可以根据 IP 来限流,比如每个 IP 每秒钟只能请求一次,在 GatewayApplication 定义 key 的获取,获取客户端 IP,将 IP 作为 key,如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /*** * IP 限流 * @return */ @Bean(name="ipKeyResolver") public KeyResolver userKeyResolver() { return new KeyResolver() { @Override public Mono<String> resolve(ServerWebExchange exchange) { // 获取远程客户端 IP String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHost Address(); System.out.println("hostName:"+hostName); return Mono.just(hostName); } }; }
在路由中配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 gateway: #路由配置 routes: #唯一标识符 - id: hailtaxi-driver uri: lb://hailtaxi-driver #路由断言 predicates: - Path=/driver/** - Cookie=username,itheima - Header=token,^(?!\d+$)[\da-zA-Z]+$ - Method=GET,POST - Token=Authorization filters: - PayMethod=alipay, 业务整合 - name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的 facatory args: key-resolver: "#{@ipKeyResolver}" redis-rate-limiter.replenishRate: 1 redis-rate-limiter.burstCapacity: 1
参数说明:
1 2 3 4 5 6 7 redis-rate-limiter.replenishRate 是您希望允许用户每秒执行多少请求, 而不会丢弃任何请求。这是令牌桶填充的速率 redis-rate-limiter.burstCapacity 是指令牌桶的容量,允许在一秒钟内完 成的最大请求数, 将此值设置为零将阻止所有请求。 key-resolver: “#{@ipKeyResolver}” 用于通过 SPEL 表达式来指定使用哪 一个 KeyResolver.
如上配置: 表示 一秒内,允许 一个请求通过,令牌桶的填充速率也是一秒钟添加一个令牌。
最大突发状况 也只允许 一秒内有一次请求,可以根据业务来调整。
我们请求 http://localhost:8001/driver/info/1?token=aa
执行测试,效果如下: