作为SpringCloud 生态系统中的网关,目标是代替Zuul,在SpringCloud2.0版本中,没有对新版本的Zuul2.0以上最新高性能版本进行集成,任然是Zuul1.x ,非Reactor 模式的老版本。而为了提升网关的性能,SpringCloud Gateway 是基于WebFlux 框架实现的,而webFlux框架底层则使用了高性能的Reactor 模式通信框架Netty
SpringCloud Gateway 的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
Webflux 框架插播:
在Servlet3.1 之后有了异步非阻塞的支持。而Webflux是一个典型非阻塞异步的框架,它的核心是基于Reactor相关Api 实现的。相对于传统的Web 框架,他可以运行在诸如Netty,Undertow 及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5 必须让你使用java8)
Spring Cloud Gateway 和 Zuul 的区别
Spring Cloud Gateway 具有如下特性:
基于Spring Framework5, Project Reactor 和 Spring Boot 2.0 进行构建
动态路由:能够匹配任何请求属性
可以对路由指定 Predicate (断言) 和Filter(过滤器)
集成Hystrix 断路器功能
集成Spring Cloud 服务发现
易于编写的Predicate (断言) 和Filter (过滤器)
请求限流功能
支持路径重写
Zuul 1.x 基于servlet 2.0 阻塞模型,在高并发的情况下效率低下
而SpringCloud Gateway 底层使用了webflux(Spring 5.0引入的新的响应式框架,区别于SpringMvc 它不需要依赖于Servlet) 而webflux底层又使用Netty 一个非阻塞高性能的网络通信框架,非常适用于高并发场景。
SpringCloud Gateway 工作的流程
路由:id+uri请求通过断言是否符合我的路由规则然后通过过滤器Filter在请求前和请求后做一些精细化的操作。
断言:参考 1.8 的Predicate (断言)
Filter过滤链:一堆Filter
官网截图
官网的翻译
客户端向 Spring Cloud Gateway 发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关 Web 处理程序。此处理程序通过特定于请求的过滤器链运行请求。过滤器被虚线分隔的原因是过滤器可以在发送代理请求之前和之后运行逻辑。执行所有“预”过滤器逻辑。然后进行代理请求。发出代理请求后,将运行“post”过滤器逻辑。
在没有端口的路由中定义的 URI 分别获得 HTTP 和 HTTPS URI 的默认端口值 80 和 443。
Gateway配置
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500">//创建一个新的gateway 功能的模块引入pom 依赖</span>
<span style="color:#981a1a"><</span><span style="color:#000000">dependency</span><span style="color:#981a1a">></span>
<span style="color:#981a1a"><</span><span style="color:#000000">groupId</span><span style="color:#981a1a">></span><span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">cloud</span><span style="color:#981a1a"></</span><span style="color:#000000">groupId</span><span style="color:#981a1a">></span>
<span style="color:#981a1a"><</span><span style="color:#000000">artifactId</span><span style="color:#981a1a">></span><span style="color:#000000">spring</span><span style="color:#981a1a">-</span><span style="color:#000000">cloud</span><span style="color:#981a1a">-</span><span style="color:#000000">starter</span><span style="color:#981a1a">-</span><span style="color:#000000">gateway</span><span style="color:#981a1a"></</span><span style="color:#000000">artifactId</span><span style="color:#981a1a">></span>
<span style="color:#981a1a"></</span><span style="color:#000000">dependency</span><span style="color:#981a1a">></span>
<span style="color:#aa5500">//修改yml</span>
<span style="color:#000000">server</span>:
<span style="color:#000000">port</span>: <span style="color:#116644">9527</span>
<span style="color:#000000">spring</span>:
<span style="color:#000000">application</span>:
<span style="color:#000000">name</span>: <span style="color:#000000">cloud</span><span style="color:#981a1a">-</span><span style="color:#000000">gateway9527</span>
<span style="color:#000000">cloud</span>:
<span style="color:#000000">gateway</span>:
<span style="color:#000000">routes</span>:
<span style="color:#981a1a">-</span> <span style="color:#000000">id</span>: <span style="color:#000000">payment_routh</span>
<span style="color:#000000">uri</span>: <span style="color:#000000">http</span>:<span style="color:#aa5500">//localhost:8001 ##要路由转发的uri</span>
<span style="color:#000000">predicates</span>:
<span style="color:#981a1a">-</span> <span style="color:#000000">Path</span><span style="color:#981a1a">=/</span><span style="color:#000000">payment</span><span style="color:#981a1a">/</span><span style="color:#000000">findById</span><span style="color:#aa5500">/** ##断言请求的地址是否符合路由的规则</span>
<span style="color:#aa5500">- id: payment_routh2 ##要路由转发的uri2</span>
<span style="color:#aa5500">uri: http://localhost:8001</span>
<span style="color:#aa5500">predicates:</span>
<span style="color:#aa5500">- Path=/payment/lb/**</span>
<span style="color:#aa5500">eureka: ##由于gateway 也是一个单独的服务所以也要注册进eureka</span>
<span style="color:#aa5500">instance:</span>
<span style="color:#aa5500">hostname: cloud-gateway-service</span>
<span style="color:#aa5500">client:</span>
<span style="color:#aa5500">#表示是否将自己注册进EurekaServer</span>
<span style="color:#aa5500">register-with-eureka: true</span>
<span style="color:#aa5500">#是否要从EurekaServer抓取注册信息</span>
<span style="color:#aa5500">fetch-registry: true</span>
<span style="color:#aa5500">service-url:</span>
<span style="color:#aa5500">#单机版</span>
<span style="color:#aa5500">defaultZone: http://eureka7001.com:7001/eureka/ </span></span></span>
注意:
引入gateway 依赖了就不要引入
<span style="background-color:#f8f8f8"><span style="color:#981a1a"><</span><span style="color:#000000">dependency</span><span style="color:#981a1a">></span> <span style="color:#981a1a"><</span><span style="color:#000000">groupId</span><span style="color:#981a1a">></span><span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">boot</span><span style="color:#981a1a"></</span><span style="color:#000000">groupId</span><span style="color:#981a1a">></span> <span style="color:#981a1a"><</span><span style="color:#000000">artifactId</span><span style="color:#981a1a">></span><span style="color:#000000">spring</span><span style="color:#981a1a">-</span><span style="color:#000000">boot</span><span style="color:#981a1a">-</span><span style="color:#000000">starter</span><span style="color:#981a1a">-</span><span style="color:#000000">web</span><span style="color:#981a1a"></</span><span style="color:#000000">artifactId</span><span style="color:#981a1a">></span> <span style="color:#981a1a"></</span><span style="color:#000000">dependency</span><span style="color:#981a1a">></span> <span style="color:#981a1a"><</span><span style="color:#000000">dependency</span><span style="color:#981a1a">></span> <span style="color:#981a1a"><</span><span style="color:#000000">groupId</span><span style="color:#981a1a">></span><span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">boot</span><span style="color:#981a1a"></</span><span style="color:#000000">groupId</span><span style="color:#981a1a">></span> <span style="color:#981a1a"><</span><span style="color:#000000">artifactId</span><span style="color:#981a1a">></span><span style="color:#000000">spring</span><span style="color:#981a1a">-</span><span style="color:#000000">boot</span><span style="color:#981a1a">-</span><span style="color:#000000">starter</span><span style="color:#981a1a">-</span><span style="color:#000000">actuator</span><span style="color:#981a1a"></</span><span style="color:#000000">artifactId</span><span style="color:#981a1a">></span> <span style="color:#981a1a"></</span><span style="color:#000000">dependency</span><span style="color:#981a1a">></span></span>
下面测试进过gateway一系列操作后为我们生成的代理 访问 http://localhost:9527/payment/findById/ id后访问成功
代码层次的配置
<span style="background-color:#f8f8f8"><span style="color:#333333">
<span style="color:#555555">@Configuration</span>
<span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">GatewayConfig</span> {
<span style="color:#555555">@Bean</span>
<span style="color:#770088">public</span> <span style="color:#000000">RouteLocator</span> <span style="color:#000000">routeLocator</span>(<span style="color:#000000">RouteLocatorBuilder</span> <span style="color:#000000">builder</span>){
<span style="color:#770088">return</span> <span style="color:#000000">builder</span>.<span style="color:#000000">routes</span>().<span style="color:#000000">route</span>(<span style="color:#aa1111">"route_baidu_tieba"</span>,
<span style="color:#000000">r</span><span style="color:#981a1a">-></span><span style="color:#000000">r</span>.<span style="color:#000000">path</span>(<span style="color:#aa1111">"/tieba"</span>) <span style="color:#aa5500">//代理访问路径</span>
.<span style="color:#000000">uri</span>(<span style="color:#aa1111">"https://tieba.baidu.com/index.html"</span>)) <span style="color:#aa5500">//被代理的访问路径</span>
.<span style="color:#000000">build</span>();
}
<span style="color:#555555">@Bean</span>
<span style="color:#770088">public</span> <span style="color:#000000">RouteLocator</span> <span style="color:#000000">routeLocator2</span>(<span style="color:#000000">RouteLocatorBuilder</span> <span style="color:#000000">builder</span>){
<span style="color:#770088">return</span> <span style="color:#000000">builder</span>.<span style="color:#000000">routes</span>()
.<span style="color:#000000">route</span>(<span style="color:#aa1111">"route_baidu_news"</span>,
<span style="color:#000000">r</span><span style="color:#981a1a">-></span><span style="color:#000000">r</span>.<span style="color:#000000">path</span>(<span style="color:#aa1111">"/news"</span>)
.<span style="color:#000000">uri</span>(<span style="color:#aa1111">"https://news.baidu.com/"</span>))
.<span style="color:#000000">build</span>();
}
}
</span></span>
Gateway 动态路由
原来的负载均衡由Ribbon通过寻找注册中心中的名称通过内部的负载均衡算法实现,而现在我们需要使用gateway 的动态路由功能来实现。
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500">// yml 配置</span>
<span style="color:#000000">server</span>:
<span style="color:#000000">port</span>: <span style="color:#116644">9527</span>
<span style="color:#000000">spring</span>:
<span style="color:#000000">application</span>:
<span style="color:#000000">name</span>: <span style="color:#000000">cloud</span><span style="color:#981a1a">-</span><span style="color:#000000">gateway9527</span>
<span style="color:#000000">cloud</span>:
<span style="color:#000000">gateway</span>:
<span style="color:#000000">discovery</span>:
<span style="color:#000000">locator</span>:
<span style="color:#000000">enabled</span>: <span style="color:#221199">true</span> <span style="color:#000000">#开启从注册中心动态路由的功能,利用微服务名进行路由</span>
<span style="color:#000000">routes</span>:
<span style="color:#981a1a">-</span> <span style="color:#000000">id</span>: <span style="color:#000000">payment_routh</span>
<span style="color:#000000">#uri</span>: <span style="color:#000000">http</span>:<span style="color:#aa5500">//localhost:8001</span>
<span style="color:#000000">uri</span>: <span style="color:#000000">lb</span>:<span style="color:#aa5500">//cloud-payment-service #该路径由SpringCloud提供 lb://+注册中心名称</span>
<span style="color:#000000">predicates</span>:
<span style="color:#981a1a">-</span> <span style="color:#000000">Path</span><span style="color:#981a1a">=/</span><span style="color:#000000">payment</span><span style="color:#981a1a">/</span><span style="color:#000000">findById</span><span style="color:#aa5500">/**</span>
<span style="color:#aa5500">- id: payment_routh2</span>
<span style="color:#aa5500">#uri: http://localhost:8001</span>
<span style="color:#aa5500">uri: lb://cloud-payment-service</span>
<span style="color:#aa5500">predicates:</span>
<span style="color:#aa5500">- Path=/payment/lb/**</span>
<span style="color:#aa5500">eureka:</span>
<span style="color:#aa5500">instance:</span>
<span style="color:#aa5500">hostname: cloud-gateway-service</span>
<span style="color:#aa5500">client:</span>
<span style="color:#aa5500">#表示是否将自己注册进EurekaServer</span>
<span style="color:#aa5500">register-with-eureka: true</span>
<span style="color:#aa5500">#是否要从EurekaServer抓取注册信息</span>
<span style="color:#aa5500">fetch-registry: true</span>
<span style="color:#aa5500">service-url:</span>
<span style="color:#aa5500">#单机版</span>
<span style="color:#aa5500">defaultZone: http://eureka7001.com:7001/eureka/</span></span></span>
Gateway 的Predicates
断言(只有满足断言配置的内容,接口才能访问发否否则就是404) 启动Gateway 后通过日志可以查看 断言的配置选项
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#000000">使用ZonedDateTime</span> <span style="color:#000000">now</span> <span style="color:#981a1a">=</span> <span style="color:#000000">ZonedDateTime</span>.<span style="color:#000000">now</span>(); <span style="color:#000000">获取当前时区</span>
<span style="color:#000000">打印now后返回</span> <span style="color:#116644">2021</span><span style="color:#981a1a">-</span><span style="color:#116644">08</span><span style="color:#981a1a">-</span><span style="color:#116644">05</span><span style="color:#000000">T17</span>:<span style="color:#116644">25</span>:<span style="color:#116644">24.249</span><span style="color:#981a1a">+</span><span style="color:#116644">08</span>:<span style="color:#116644">00</span>[<span style="color:#000000">Asia</span><span style="color:#981a1a">/</span><span style="color:#000000">Shanghai</span>]
<span style="color:#000000">spring</span>:
<span style="color:#000000">application</span>:
<span style="color:#000000">name</span>: <span style="color:#000000">cloud</span><span style="color:#981a1a">-</span><span style="color:#000000">gateway9527</span>
<span style="color:#000000">cloud</span>:
<span style="color:#000000">gateway</span>:
<span style="color:#000000">discovery</span>:
<span style="color:#000000">locator</span>:
<span style="color:#000000">enabled</span>: <span style="color:#221199">true</span> <span style="color:#000000">#开启从注册中心动态路由的功能,利用微服务名进行路由</span>
<span style="color:#000000">routes</span>:
<span style="color:#981a1a">-</span> <span style="color:#000000">id</span>: <span style="color:#000000">payment_routh</span>
<span style="color:#000000">#uri</span>: <span style="color:#000000">http</span>:<span style="color:#aa5500">//localhost:8001</span>
<span style="color:#000000">uri</span>: <span style="color:#000000">lb</span>:<span style="color:#aa5500">//cloud-payment-service</span>
<span style="color:#000000">predicates</span>:
<span style="color:#981a1a">-</span> <span style="color:#000000">Path</span><span style="color:#981a1a">=/</span><span style="color:#000000">payment</span><span style="color:#981a1a">/</span><span style="color:#000000">findById</span><span style="color:#aa5500">/**</span>
<span style="color:#aa5500">- id: payment_routh2</span>
<span style="color:#aa5500">#uri: http://localhost:8001</span>
<span style="color:#aa5500">uri: lb://cloud-payment-service</span>
<span style="color:#aa5500">predicates:</span>
<span style="color:#aa5500">- Path=/payment/lb/**</span>
<span style="color:#aa5500">- After=2021-08-05T17:25:24.249+08:00[Asia/Shanghai] #代表在17点25分24秒后才能访问</span></span></span>
使用curl在黑窗口上测试接口
命令:curl http://localhost:9527/payment/lb
使用curl在黑窗口上测试携带cookie的接口
命令:curl -b "username=zhangsan" http://localhost:9527/payment/lb
测试这个我们需要在
predicates: - Path=/payment/lb/** - After=2021-08-05T15:25:24.249+08:00[Asia/Shanghai] - Cookie=username,zhangsan #代表必须携带一个name=username,value=zhangsan 的cookie 才能访问
其他的如有所需可以自行添加
Gateway Filter
在Gateway 工作流程图中我们可以看到有Filter的存在,用虚线隔开 主要的功能:拦截请求指定规则只有满足的才能放行,或是在访问前后做一些例如 全局的日志记录,统一网关鉴权 等等一些操作。
SpringCloud 在Gateway 中加入了许多可配置的Fileter,需要时可查看官网进行配置
自定义配置(常用) 需要 implements GlobalFilter,Ordered 这两个接口
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#000000">实例:</span>
<span style="color:#555555">@Component</span>
<span style="color:#555555">@Slf4j</span>
<span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">MyGatewayFilter</span> <span style="color:#770088">implements</span> <span style="color:#000000">GlobalFilter</span>, <span style="color:#000000">Ordered</span> {
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#000000">Mono</span><span style="color:#981a1a"><</span><span style="color:#008855">Void</span><span style="color:#981a1a">></span> <span style="color:#000000">filter</span>(<span style="color:#000000">ServerWebExchange</span> <span style="color:#000000">exchange</span>, <span style="color:#000000">GatewayFilterChain</span> <span style="color:#000000">chain</span>) {
<span style="color:#000000">log</span>.<span style="color:#000000">info</span>(<span style="color:#aa1111">"******* Gateway Filter starter:"</span><span style="color:#981a1a">+</span><span style="color:#770088">new</span> <span style="color:#000000">Date</span>());
<span style="color:#aa5500">//判断请求中是否携带username 这个参数</span>
<span style="color:#008855">String</span> <span style="color:#000000">username</span> <span style="color:#981a1a">=</span> <span style="color:#000000">exchange</span>.<span style="color:#000000">getRequest</span>().<span style="color:#000000">getQueryParams</span>().<span style="color:#000000">getFirst</span>(<span style="color:#aa1111">"uname"</span>);
<span style="color:#aa5500">//如果为null</span>
<span style="color:#770088">if</span>(<span style="color:#000000">StrUtil</span>.<span style="color:#000000">isBlank</span>(<span style="color:#000000">username</span>)){
<span style="color:#000000">log</span>.<span style="color:#000000">info</span>(<span style="color:#aa1111">"*** 用户名称为null"</span>);
<span style="color:#aa5500">//响应请求状态</span>
<span style="color:#000000">exchange</span>.<span style="color:#000000">getResponse</span>().<span style="color:#000000">setStatusCode</span>(<span style="color:#000000">HttpStatus</span>.<span style="color:#000000">NOT_ACCEPTABLE</span>);
<span style="color:#aa5500">//代表结束此次请求</span>
<span style="color:#770088">return</span> <span style="color:#000000">exchange</span>.<span style="color:#000000">getResponse</span>().<span style="color:#000000">setComplete</span>();
}
<span style="color:#aa5500">//代表放行到下一个过滤器</span>
<span style="color:#770088">return</span> <span style="color:#000000">chain</span>.<span style="color:#000000">filter</span>(<span style="color:#000000">exchange</span>);
}
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#008855">int</span> <span style="color:#000000">getOrder</span>() {
<span style="color:#770088">return</span> <span style="color:#116644">0</span>;
}
}</span></span>