你们好,我是小菜。java
一个但愿可以成为 吹着牛X谈架构 的男人!若是你也想成为我想成为的人,否则点个关注作个伴,让小菜再也不孤单!web
本文主要介绍
SpringCloud之服务网关Gateway
正则表达式若有须要,能够参考算法
若有帮助,不忘 点赞 ❥spring
微信公众号已开启,小菜良记,没关注的同窗们记得关注哦!编程
前段时间与小伙伴闲聊时说到他们公司的现状,近来与未来,公司将全面把单体服务向微服务架构过渡。这里面咱们听到了关键词 --- 微服务。乍听之下,以为也很合理。互联网在不断的发展,这里不仅是行业的发展,更是系统架构的发展。如今市面上架构大体也经历了这几个阶段:数组
这是好事吗?是好事,毋庸置疑。由于不跟上时代的浪潮,总会被拍死在沙滩上。但彻底是好事吗?那也不见得。安全
咱们先要明白微服务解决了什么问题?大方面上应该就是应用层面和人层面的问题微信
独立
、多语言生态
也是微服务的标签。在单体服务中,投入的人力资源越多不见得越高效,反而越容易出错。但微服务不一样,每一个服务都是独立出来的,团队能够更加容易的协做开发,甚至一套系统,多个服务,多种语言,毫无冲突。可是咱们凡事不能被好处蒙蔽。微服务的缺点也是一直存在的:websocket
不少人认为微服务的核心就是在于 微
。服务分的越细越好,就好像平时写代码的时候丝绝不考虑的单一原则,反而在服务拆分上用到淋漓尽致。这里就须要明白一个核心概念:微服务的关键不在微,而是在于合适的大小
这句话好像很简单的样子,可是多大才算合适的大小?这可能据不一样团队,不一样项目而定。合适的大小可能取决于更少的代码仓库,更少的部署队列,更少的语言... 这里更没法一槌定音!
若是无法作到合适的大小,而无脑的拆分服务,那么微服务可能反而成为你项目的累赘。所以,有时全面转型微服务反而不是好事,你以为呢?
话题有点跑远了,我们努力扯回来。既然微服务已经成为主流,那么如何设计微服务即是咱们应该作的事,而不是谈及微服务之时,想到的只是与人如何争论如何拒用微服务。那么这篇咱们要讲的是SpringCloud之服务网关Gateway
什么是服务网关?不要给本身当头一棒。咱们换个问法,为何须要服务网关?
服务网关是跨一个或多个服务节点提供单个统一的访问入口
它的做用并非无关紧要的存在,而是相当重要。咱们能够在服务网关作路由转发
和过滤器
的实现。优势简述以下:
根据图中内容,咱们能够得出如下信息:
路由转发
,API监控
、权限控制
、限流
而这些即是 服务网关 存在的意义!
SpringCloud Gateway 是 SpringCloud 的一个全新项目,目标是取代Netflix Zuul。它是基于 Spring5.0 + SpringBoot2.0 + WebFlux
等技术开发的,性能高于 Zuul,据官方提供的信息来看,性能是 Zuul 的1.6倍,意在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
SpringCloud Gateway 不只提供了统一的路由方式(反向代理),而且基于 Filter 链(定义过滤器对请求过滤)提供了网关基本的功能,例如:鉴权、流量控制、熔断、路径重写、日志监控等。
其实说到 Netflix Zuul,在使用或准备使用微服务架构的小伙伴应该并不陌生,毕竟Netflix 是一个老牌微服务开源者。新秀与老牌之间的争夺,若是新秀没有点硬实力,如何让人安心转型!
这里咱们能够顺带了解一下 Weflux,Webflux 的出现填补了 Spring 在响应式编程上的空白。
可能有不少小伙伴并不知道 Webflux,小菜接下来也会出一篇关于 Webflux 的讲解,实则真香!
Webflux 的响应式编程不只仅是编程风格上的改变,而是对于一系列著名的框架都提供了响应式访问的开发包,好比 Netty、Redis(若是不知道 Netty 的实力,能够想一想为何 Nginx 能够承载那么大的并发,底层就是基于Netty)
那么说这么多,跟 Zuul 有什么关系呢?咱们能够看下 Zuul 的 IO 模型
SpringCloud 中所集成的 Zuul 版本,采用的是 Tomcat 容器,使用的是传统的 Servlet IO 处理模型。Servlet 是由 Servlet Container 管理生命周期的。
但问题就在于 Servlet 是一个简单的网络IO模型,当请求进入到 ServletContainer就会为其绑定一个线程,在并发不高的场景下这种模型是没有问题的,可是一旦并发上来了,线程数量就会增长。那致使的问题就是频繁进行上下文切换,内存消耗严重,处理请求的时间就会变长。正所谓牵一发而动全身!
而 SpriingCloud Zuul 即是基于 servlet 之上的一个阻塞式处理模型,即Spring实现了处理全部 request 请求的一个 servlet(DispatcherServlet),并由该 Servlet 阻塞式处理。虽然 SpringCloud Zuul 2.0 开始,也是用了 Netty 做为并发IO框架,可是 SpringCloud 官方已经没有集成该版本的计划!
注:这里没有推崇 Gateway 的意思,具体使用依具体项目而定
最关键的一步即是引入网关的依赖
<!--gateway网关--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
我这里简单的建立了一个微服务项目,项目里有一个 store-gateway
服务网关 和一个 store-order
订单服务。由于咱们这篇只说明服务网关的做用,不须要太多服务提供者和消费者!
在store-order
订单服务中只有一个控制器OrderController
,里面也只有一个简单到发指的API
@RestController @RequestMapping("order") public class OrderController { @GetMapping("/{id:.+}") public String detail(@PathVariable String id) { return StringUtils.join("获取到了ID为", id, "的商品"); } }
咱们分别启动两个服务,而后访问订单服务的API:
结果确定是符合预期的,不至于翻车。8001
是订单服务的接口,这个时候咱们能够了解到,原来微服务架构每一个服务独立启动,都是能够独立访问的,也就至关于传统的单体服务。
咱们想一想看,若是用端口来区分每一个服务,是否也能够达到微服务的效果?理论上好像是能够的,可是若是成百上千个服务呢?端口爆炸,维护爆炸,治理爆炸... 不说别的,心态直接爆炸了!这个时候咱们就想着若是只用统一的一个端口访问各个服务,那该多好!端口一致,路由前缀不一致,经过路由前缀来区分服务,这种方式将咱们带入了服务网关的场景。是的,这里就说到了服务网关的功能之一 --- 路由转发
。
既然要用到网关,那咱们上面建立的服务之一 store-gateway
就派上用场了!怎么用?咱们在配置文件作个简单的修改:
spring: application: name: store-gateway cloud: gateway: routes: - id: store-order uri: http://localhost:8001 order: 1 predicates: - Path=/store-order/** filters: - StripPrefix=1
很少废话,咱们直接启动网关,经过访问http://localhost:8000/store-order/order/123
看是否能获取到订单?
很顺利,咱们成功拿到了ID 为 123 的订单商品!
咱们看下 URL 的组成:
可以访问到咱们的服务,说明网关配置生效了,咱们再来看下这么配置项是怎么一回事!
spring.cloud.gateway
这个是服务网关 Gateway 的配置前缀,没什么好说的,本身须要记住就是了。
routes
如下就是咱们值得关注的了,routes
是个复数形式,那么能够确定,这个配置项是个数组的形式,所以意味着咱们能够配多个路由转发,当请求知足什么条件的时候转到哪一个微服务上。
惟一
标识或
的关系)了解完必要的参数,咱们也高高兴兴去部署使用了,可是好景不长,咱们又迎来了新的问题。我订单服务原先使用的 8001
端口,由于某些缘由给其余服务使用了,这个时候小脑壳又大了,这种状况确定不会出现 上错花轿嫁对郎
的结果!
我们想一想看这种问题要怎么解决比较合适?既然都采用微服务了,那咱们能不能采用服务名的方式跳转访问,这样子不管端口怎么变,都不会影响到咱们的正常业务!那若是采用服务的方式,就须要一个注册中心
,这样子咱们启动的服务能够同步到注册中心
的 注册表 中,这样子网关就能够根据 服务名
去注册中心中寻找服务进行路由跳转了!那我们就须要一个注册中心,这里就采用 Nacos 做为注册中心.
关于 Nacos 的了解,能够空降 微服务新秀之Nacos,看了就会,我说的!
咱们分别在服务网关 和 订单服务的配置文件都作了如下配置:
启动两个服务后,咱们能够在 Nacos 的控制台服务列表中看到两个服务:
这个时候能够看到 订单服务 的服务名为:store-order
,那咱们就能够在网关配置文件部分作如下修改:
这里的配置与上述不一样点之一 http
换成了 lb
(lb 指的是从nacos中按照名称获取微服务,并遵循负载均衡策略),之二 端口
换成了 服务名
那咱们继续访问上述URL看是否可以成功访问到订单服务:
结果依然没有翻车!这样子,无论订单服务的端口如何改变,只要咱们的服务名不变,那么始终均可以访问到咱们的对应的服务!
日子一天一天的过去~ 服务也一点一点的增长!终于有一天小菜烦了,由于每次增长服务都得去配置文件中增长一个routes
的配置列,虽然也只是 CV 的操做,可是,哪一天小菜不当心手一抖,那么~~~ 算了算了,找找看有没有什么能够偷懒的写法,终于不负小菜心,找到了一种简化版!配置以下:
就这?是的,就这!管你服务再怎么多,我配置文件都不用修改。这样子配置的目的即是请求统一方式都是变成了 网关地址:网关端口/服务名/接口名
的方式访问。再次访问页面,依然行得通!
可是方便归方便,在方便的同时也限制了不少扩展功能,所以使用需三思!不可贪懒
!
上面已经说完了网关的简单使用,看完的小伙伴确定已经能够上手了!接下来咱们继续趁热打铁,了解下 Gateway 网关的核心。不说别的,路由转发 确定是网关的核心!咱们从上面已经了解到一个具体路由信息载体,主要定义了如下几个信息(回忆下):
这里来梳理一下访问流程:
这张图很清楚的描述服务网关的调用流程(盲目自信)
过程了解了,咱们抽取一下其中的关键!断言
和 过滤器
Predicate 也就是断言,主要适用于进行条件判断,只有断言都返回真,才会真正执行路由
SpringCloud Gateway 中内置了许多断言工厂,全部的这些断言都和 HTTP 请求不一样的属性相匹配,具体以下;
该类型的断言工厂是根据时间作判断的
一、AfterRoutePredicateFactory: 接收一个
日期参数,判断请求日期是否晚于指定日期
二、BeforeRoutePredicateFactory:接收一个
日期参数,判断请求日期是否早于指定日期
三、BetweenRoutePredicateFactory:接收两个
日期参数,判断请求日期是否在指定时间段内
该类型的断言工厂是接收一个参数
,IP 地址端,判断请求主机地址是否在地址段中。(eq:-RemoteAddr=192.168.1.1/24)
该类型的断言工厂接收两个参数
,Cookie 名字和一个正则表达式,判断请求 cookie 是否具备给定名称且值与正则表达式匹配。(eq:-Cookie=cbuc)
该类型的断言工厂接收两个参数
,标题名称和正则表达式。判断请求 Header 是否具备给定名称且值与正则表达式匹配。(eq:-Header=X-Request)
该类型的断言工厂接收一个参数
,主机名模式。判断请求的host 是否知足匹配规则。(eq:-Host=**.cbuc.cn)
该类型的断言工厂接收一个参数
,判断请求类型是否跟指定的类型匹配。(eq:-Method=GET)
该类型的断言工厂接收一个参数
,判断请求的URI部分是否知足路径规则。(-eq:-Path=/order/)
该类型的断言工厂接收两个参数
,请求 Param 和 正则表达式。判断请求参数是否具备给定名称且值与正则表达式匹配。(eq:-Query=cbuc)
该类型的断言工厂接收一个[组名,权重]
,而后对于同一个组内的路由按照权重转发
这么多断言工厂,这里就不一一使用演示了,咱们结合几个断言工厂的使用演示一下。
咱们老样子很少废话,直接上代码:
CustomPredicateRouteFactory
配置文件
测试结果
success
fail
惊呼 Amazing 的同时,不要着急的往下看,咱们回归代码,看看,为何一个能够访问成功,一个却访问失败了。两个方面:1. 二者访问的URL有哪些不一样 2. 代码哪部分对 URL 作出了处理
先养成独立思考,再去看解决方法
当你思考完后,可能部分同窗已经有结果了,那让咱们继续往下看!首先是一个 CustomRoutePredicateFactory
类,这个类的做用有点像拦截器,在作请求转发的时候进行了拦截,咱们请求的时候能够打个断点:
能够看到,确实是拦截器的功能,在每一个请求发起的时候作了拦截。那问题2 的结果就出来了,原来URL处理是在 RoutePredicateFactory 中作了处理,在 apply 方法中能够经过 exchange.getRequest()
拿到 ServerHttpRequest 对象,从而能够获取到请求的参数、请求方式、请求头等信息。 shortcutFieldOrder()
方法也是重写的关键之一,咱们须要这里返回,咱们实体类中定义的属性,而后在apply()
方法中才能接收到咱们赋值的属性参数!
注意:若是自定义的实体中有多个属性须要判断,
shortcutFieldOrder()
方法中的顺序要跟配置文件中的参数顺序一致
那么当咱们编写了该断言工厂后,若是让之生效?@Component
这个注解确定必不可少了,目的就是让 Spring 容器管理。那么已经注册的断言工厂如何声明使用呢?那就得回到配置文件了!
咱们这里重点看 predicates
这个配置项下的配置,分别有三个配置,一个是咱们已经熟悉的 Path
,其余两个有点陌生,可是这里再看看 Custom
是否是又有点眼熟?是的,咱们在上面好像定义了一个叫 CustomRoutePredicate
的断言工厂,二者有点类似,又好像差点什么。那我就再给你一个提示:
咱们看下抽象的断言工厂有哪些自实现的类!其中是否是有 PathRoutePredicateFactory
,没错,就是你想的那样!有没有一种拨开雨雾见青天的感受!原来咱们配置文件的 key 是以类名的前缀声明的,也就是说断言工厂类的格式必须是: 自定义名称
+ RoutePredicateFactory 为后缀,而后在配置文件中声明。这样子触类旁通,咱们天然而然的就清楚了 - Before
的做用,该做用即是:限制请求时间在 xxx 以前。
而 - Custom=cbuc
,这个 cbuc 即是咱们限制的规则,只有 name 为 cbuc 的用户才能请求成功。若是有多个参数,能够用 ,
隔开,顺序须要与断言工厂中shortcutFieldOrder()
返回参数的顺序一致!
若是在自定义断言工厂的途中遇到了什么阻碍,否则看看内置的断言工厂是如何实现的。
多看源码总没错!
接下来进入第二个核心,也就是过滤器。该核心的做用也挺简单,就是在请求的传递过程当中,对请求和响应作一系列的手脚。为了怕你划回去看请求流程过于麻烦,小菜贴心的再贴一遍流程图:
在 Gateway 的过滤器中又能够分为 局部过滤器 和 全局过滤器。听名称就知道其做用,局部 是用于某一个路由上的,全局 是用于全部路由上的。不过无论是 局部 仍是 全局,生命周期都分为 pre
和 post
。
局部过滤器是针对于单个路由的过滤器。一样 Gateway 已经内置了许多过滤器
咱们选几种经常使用的过滤器进行说明:(下列过滤器省略后缀 GaewayFilterFactory
,完整名称为 前缀+后缀)
过滤器前缀 | 做用 | 参数 |
---|---|---|
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径数量 |
AddRequestHeader | 为原始请求添加 Header | Header 的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
Retry | 针对不一样的响应进行重试 | reties、statuses、methods、series |
RequestSize | 设置容许接收最大请求包的大小 | 请求包大小,单位字节,默认5M |
SetPath | 修改原始请求的路径 | 修改后的路径 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
RequestRateLimiter | 对请求限流,限流算法为令牌桶 | KeyResolver、reteLimiter、statusCode、denyEmptyKey |
内置的过滤器小伙伴们能够本身尝试一番,有问题欢迎提问!
咱们接下来说讲如何自定义过滤器工厂。don't say so much
,咱们上代码
CustomGatewayFilterFactory
配置文件
当咱们开启请求计数的时候,能够看到控制台对于请求次数做了统计:
所以咱们能够经过这种方式轻松实现局部过滤器
全局过滤器做用于全部路由,无需配置。经过全局过滤器能够实现对权限的统一校验,安全性验证等功能
老样子,咱们先看看 Gateway 中存在哪些全局过滤器:
相对于局部过滤器,全局过滤器的命名就没有太多约束了,毕竟不须要在配置文件中进行配置。
咱们熟悉一下经典的全局过滤器
过滤器名称 | 做用 |
---|---|
ForwardPathFilter / ForwardRoutingFilter | 路径转发相关过滤器 |
LoadBalanceerClientFilter | 负载均衡客户端相关过滤器 |
NettyRoutingFilter / NettyWriteResponseFilter | Http 客户端相关过滤器 |
RouteToRequestUrlFilter | 路由 URL 相关过滤器 |
WebClientHttpRoutingFilter / WebClientWriteResponseFilter | 请求 WebClient 客户端转发请求真实的URL并将响应写入到当前的请求响应中 |
WebsocketRoutingFilter | websocket 相关过滤器 |
了解完内置的过滤器,咱们再看看如何定义全局的过滤器!
CustomerGlobalFilter
对于全局过滤器,咱们不须要在配置文件中配置,由于是做用于全部路由
测试结果
success
fail
能够看到,咱们使用全局过滤器进行了鉴权
处理,若是没有携带 token 将没法访问!
到这里咱们已经了解到了服务网关的路由转发,权限校验甚至于能够基于断言和过滤器
作出粗略简单的 API监控和限流
但其实对于 API监控和 限流,SpringCloud 中已经有了更好的组件完成这两项工做。毕竟单一原则,作的越多每每错的也越多!
后面会继续整理关于 SpringCloud 组件的文章,敬请关注!
对于微服务的框架,孰好孰坏由咱们本身断定。可是无论孰好孰坏,面对一门新技术的产生,咱们最须要作的即是接收它,包容它,而后用好它,是骡子是马,本身溜溜就知道了。
不要空谈,不要贪懒,和小菜一块儿作个吹着牛X作架构
的程序猿吧~点个关注作个伴,让小菜再也不孤单。我们下文见!
今天的你多努力一点,明天的你就能少说一句求人的话!
我是小菜,一个和你一块儿变强的男人。
💋
微信公众号已开启,小菜良记,没关注的同窗们记得关注哦!