【微服务专题】深入理解与实践微服务架构(十一)之整合Gateway服务网关

2,838 阅读33分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第11篇文章,点击查看活动详情

为什么使用网关

网关接管所有的入口流量(类似Nginx的作用),将所有用户的请求转发给后端的服务器。但网关做的不仅仅只是简单的转发,也会针对流量做一些扩展,比如鉴权、限流、权限、熔断、协议转换、错误码统一、缓存、日志、监控、告警等。

这样将通用的逻辑抽离,由网关统一去做,业务方也能够更专注于业务逻辑提升迭代的效率。 通过引入API网关,客户端只需要与API网关交互,而不用与各个业务方的接口分别通讯,但多引入一个组件就多引入了一个潜在的故障点,因此要实现一个高性能、稳定的网关,也会涉及到很多点。

技术选型

在使用Spring Cloud Gateway之前,我们先了解下有哪些网关可以使用它们之间的区别,这样有助于我们了解为什么使用Spring Cloud Gateway。

常见网关对比

下面对一些主流网关框架的介绍:

  • Apiaxle: Nodejs 实现的一个开源API管理平台。
  • Api-Umbrella: Ruby 实现的一个开源API管理平台。
  • Tyk:Tyk是一个用Go语言实现的开源企业级API网关,它是快速、可扩展和现代的网关,支持 REST、GraphQL、TCP 和 gRPC 协议。Tyk提供了一个API管理平台,其中包括API网关、API分析、开发人员门户和API管理面板。
  • Zuul:Zuul是Netflix用Java语言实现的开源服务网关。Zuul 是一个 L7 应用程序网关,提供动态路由、监控、弹性、安全等功能。
  • Gateway:Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,用于取代Zuul 1.x。
  • Fizz Gateway:Fizz Gateway是一个基于 Java开发的微服务聚合网关,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等功能。并且有完整的企业级可立即商用的后台管理系统,并且官方给的基准测试在业务网关中比Gateway好一点;是基于Java研发的,因此可以考虑替换Gateway作为业务网关系统。
  • OpenResty:基于 Nginx 和 LuaJIT 的高性能 Web 平台,可扩展为高性能网关。
  • Orange:一个基于 OpenResty(Nginx + Lua) 的网关,用于 API 监控和管理。和Kong类似,也是基于OpenResty的一个API网关程序,由国人开发。Orange是一个基础的OpenRestyAPI。除了 Nginx 的流量转发功能外,网关还有 API监控访问控制(鉴权、WAF)流量筛选访问限速AB测试静/动态分流 功能。
  • Kong:Kong是一个可扩展的开源API Layer(也称为API网关或API中间件)。Kong 在任何Restful API的前面运行,通过插件扩展,它提供了超越核心平台的额外功能和服务。
  • Goku:Goku API Gateway(悟空网关) 是一个基于 Golang 的微服务网关,可实现高性能的动态路由、服务编排、多租户管理、API 访问控制等,也适用于微服务系统下的 API 管理。停止维护后推出新一代悟空网关Apinto,相同环境下,Apinto 比 Nginx、Kong 等产品快约50%,稳定性上也有所优化。
  • Apisix:Apache APISIX是一个动态、实时、高性能的 API 网关。APISIX API Gateway 提供了丰富的流量管理功能,例如负载均衡、动态上游、金丝雀发布、熔断、认证、可观察性等。支持OpenResty和Tenengine,设计架构为云原生网关,分为数据面和控制面。

图片

网关分类

目前常见的开源网关大致上按照语言分类有如下几类:

  • Nginx+lua:OpenResty、Kong、Orange、Abtesting gateway 等
  • Java:Zuul/Zuul2、Spring Cloud Gateway、Kaazing KWG、gravitee、Dromara soul 等
  • Go:Janus、fagongzi、Grpc-gateway
  • Dotnet:Ocelot
  • NodeJs:Express Gateway、Micro Gateway

按照使用数量成熟度等来划分,主流的有 4 个:

  • OpenResty
  • Kong、Apisix(流量网关推荐)
  • Zuul/Zuul2
  • Spring Cloud Gateway(业务网关推荐)

流量网关与业务网关

什么是流量网关与业务网关?

  • 流量网关:顾名思义就是控制流量进入的网关。有很多非法、无效的请求需要将其拒之门外,并且主要提供负载均衡能力,来降低服务集群的压力。定义全局性的、跟具体的后端业务应用和服务完全无关的策略网关就是上图所示的架构模型——流量网关。流量网关通常只专注于全局的Api管理策略,比如全局流量监控、日志记录、全局限流、黑白名单控制、接入请求到业务系统的负载均衡等,有点类似防火墙。Kong 就是典型的流量网关。

这里需要补充一点的是,业务网关一般部署在流量网关之后、业务微服务之前,比流量网关更靠近业务系统。通常API网关指的是业务网关,当然在BFF架构中,API网关单独从服务中抽离出来单独作为一层。 有时候我们也会模糊流量网关和业务网关,让一个网关承担所有的工作,所以这两者之间并没有严格的界线。

一、流量网关

1、全局性流控 匹配路由 gateway转发前端地址、后端地址、文件服务器、调度器、消息中心 OK!

2、日志统计 可以统计到所有到前端后端接口 OK

3、防止SQL注入 -- 未使用

4、防止web攻击 --未使用

5、屏蔽工具扫描 --未使用

6、黑白IP名单 通过filter过滤IP禁止访问

7、证书/加解密处理 --未使用

二、业务网关

1、服务级别流控 前端访问后端通过网关 OK!

2、服务降级和熔断 统一后端接口熔断降级、POST接口限流1s/1次 OK!

3、路由与负载均衡、灰度的策略 负载均衡OK! 灰度需要部署集群

4、服务过滤、聚合与发现 通过注册中心,自定义谓词与自定义过滤器 OK!

5、权限验证与用户等级策略 -- 使用app端的权限

6、业务规则与参数校验 --未使用

7、多级缓存策略 --未使用

上面有流量网关和业务网关,业务网关适合复杂业务场景开发,流量网关适合流量负载

业务网关选型

下面是Spring Cloud微服务网关的比较:

  • Kong 网关:Kong 的性能非常好,非常适合做流量网关,但是对于复杂系统不建议业务网关用 Kong,主要是工程性方面的考虑。
  • Zuul 1.x 网关:Zuul 1.0 的落地经验丰富,但是存在性能差、基于同步阻塞IO的问题。适合中小架构,不适合并发流量高的场景,因为容易产生线程耗尽导致请求被拒绝的情况。
  • Gateway 网关:功能强大丰富,性能好,官方基准测试 RPS (每秒请求数)是Zuul的1.6倍,能与 SpringCloud 生态很好兼容,单从流式编程+支持异步(Spring Cloud Gateway 底层使用了高性能的通信框架Netty)上也足以让开发者选择它作为业务网关了。
  • Zuul 2.x:性能与 gateway 差不多,基于非阻塞的,支持长连接,但 SpringCloud 没有集成 Zuul 2.x 的计划,并且 Netflix 相关组件都宣布进入维护期,前景未知。
img

综上可知:Gateway 网关更加适合Spring Cloud 项目,而从发展趋势上看,Gateway 作为替代 Zuul 也是必然的。

Gateway和基于Nginx生态的网关有啥区别呢?

区别主要在:

  • 1.它是Spring Cloud生态的产品, 和Spring Cloud天然契合;
  • 2.它的功能比Nginx 更多。安全、指标监控和限流基本都是配置式实现,而Nginx 要自己写脚本;
  • 3.它的性能比Nginx 稍弱。如果只是路由到微服务, 用它更好。如果需要提供静态资源, 那么还是Nginx更好;

在前后端分离的架构设计中, 通常会结合起来使用,作为流量网关和业务网关。例如我司用Vue做前端,Java做后端,那么采用就是Vue -> Nginx -> Gateway -> 微服务这样子的设计架构。

API网关与BFF架构

什么是API网关

既然有了流量网关与业务网关,那么API网关又是什么呢?

其实API网关就是流量或业务网关,只是换了一个称呼。API服务网关是微服务架构里的轻量级的服务总线(ESB),用于解决服务管控和服务治理的相关问题。API网关是一个服务,也是进入微服务后端的唯一入口

什么是BFF架构

要探究BFF层的由来,就要从微服务架构的演进开始分析了,架构的演进步骤为:

  • 分层架构
  • SOA架构
  • 微服务架构
  • 事件驱动/云原生微服务架构

架构不断发展并迭代设计,慢慢从单体架构逐步演进为事件驱动/云原生的微服务架构。

发展到了微服务架构后,随着系统复杂度提高,一个大的系统被拆分为多个小的微服务。但因为依赖的服务众多,开发语言也可能不一致。引入微服务后出现的服务治理的新问题,例如:动态服务发现负载均衡TLS加密HTTP/2 & gRPC代理熔断器和重试路由规则故障注入可观测性

因此,微服务架构面临着一些挑战:

  • 业务服务侵入性。微服务框架本身的引入需要业务服务有感知,需要修改代码或引入框架;

    解决方法:采用JavaAgent或边车代理。这里分别是框架代理对象和云原生架构中对流量的代理两种不同方式的解决方案。

  • 微服务难以重构。框架本身的升级成本高,需要结合业务状态重启业务以更新框架。

    解决方法:采用配置中心。通过配置中心的版本号控制服务的版本,然后通过动态配置、灰度发布和一键回滚控制升级风险。

  • 内部服务通信协议兼容性问题。可能使用 Thrift、gRPC,也可能使用 AMQP 消息传递协议。

    当迁移到基于微服务的应用程序时,最大的挑战之一是通信机制的变化。因为微服务是分布式的,微服务之间的通信是通过网络层面的服务间通信完成的,每个微服务都有自己的实例和进程。因此,服务必须使用服务间通信协议,如 HTTPgRPC 或消息代理协议 AMQP 进行交互。但是不同通信协议之间因为格式和底层支持协议的原因,并不能直接通信;因此,微服务之间需要解决协议兼容性才能进行正常通信

    解决方法:统一序列化框架。

  • 多端平台数据分类适配。不同终端平台需求的服务数据和每个微服务暴露的细粒度 API 不匹配,服务数据需要聚合和裁剪。

    解决方法:引入BFF层。通过BFF层,作为前端与后端的中间数据聚合和裁剪层。因为不同端的差异性存在,服务端的功能要针对端的差异进行适配和裁剪;而服务端的业务功能本身是相对单一的,这就产生了一个矛盾——服务端的单一业务功能和端的差异性诉求之间的矛盾。那么这个问题怎么解决呢?这也是文章的副标题所描述的"Single-purpose Edge Services for UIs and external parties",引入BFF,由BFF来针对多端差异做适配,这也是目前业界广泛使用的一种模式。

b89931cc182bf629d76e441ef42c86a1.png

那么到底什么是BFF层呢?

BFF(backend for frontend,服务于前端的后端),是一个服务于不同前端的后台服务,所有的前端(比如 iOSAndroidWeb) 都依赖它。而且BFF是一个整合服务,它负责把前端的请求统一分发到各个具体的微服务上,然后把返回数据整合统一返回给前端

好像类似于前端的防抖节流

  • 防抖:将多次执行请求合并,在最后一次执行;
  • 节流:高频事件时触发,将多次执行变成每隔一段时间执行。

参考技术实现:GraphQL(只是数据层面的)

API网关与BFF层的联系

请求通过网关后一般不会直接打到具体微服务上的,而是会通过BFF层,所谓的BFF,即 backend for frontend 面向前端的后端。具体来说它的职能包括:

  • api数据裁剪
  • 接口编排
  • 接口调用

有了BFF层,前后端就会更好的解耦,前端不用再调用多个接口,然后再组织数据,微服务后端也只需要关心自己服务边界内的事情。

然而在实践的过程中会出现一些问题:

  • 大量业务逻辑从前后端集中在了BFF层
  • BFF层逻辑复杂,代码量越来越大,难以维护
  • BFF API版本维护复杂

前端接口职责不清,扯皮的结果就是放在BFF层。

以上是我真实遇到过的场景。所以在后面的架构设计和实施中,这些情况会尽量避免,但没有从技术上解决根本问题。

加入了BFF的前后端架构中,最大的区别就是前端(Mobile, Web) 不再直接访问后端微服务,而是通过 BFF 层进行访问。并且每种客户端都会有一个BFF服务。从微服务的角度来看,有了 BFF 之后,微服务之间的相互调用更少了。这是因为一些UI的逻辑在 BFF 层进行了处理。而BFF层的实现是GraphQL协议,主要是为了替代传统的 Restful接口。因此,在我看来,非必要其实不需要使用BFF层来替换Restful接口。数据聚合和裁剪应该交给API网关的路由转发功能来做,因此,相当于API网关就相当于BFF层。

image-20220624050007840

什么时候需要 BFF?

  • 后端被诸多客户端使用,并且每种客户端对于同一类 Api 存在定制化诉求
  • 后端微服务较多,并且每个微服务都只关注于自己的领域
  • 需要针对前端使用的 Api 做一些个性化的优化

什么时候不推荐使用 BFF?

  • 后端服务仅被一种客户端使用
  • 后端服务比较简单,提供为公共服务的机会不多

参考:

Service Mesh和API网关

微服务中的服务网格是处理进程间通信的可配置网络基础设施层,这类似于通常被称为边车代理或边车(sidecar)网关

从表面上看,API网关和服务网格似乎解决了相同的问题,因此有点冗余。

它们在不同的背景下解决了相同的问题:

  • API网关作为业务解决方案的一部分部署,可以处理南北流量
  • 然而,服务网格处理东西流量(在不同的微服务之间)。

服务网格的实现方式避免了弹性通信模式(但HSF实现了业务无感的弹性通信模式) ,如断路器、服务发现、运行状况检查、服务可观察性等。对于少量的微服务,应该考虑故障管理的可替代方案,因为服务网格可能比较复杂。对于更多数量的微服务,这将是有益的。

这两种技术结合可以确保应用程序长时间正常运行弹性扩展,同时确保应用程序易于使用。两种技术在微服务部署中相互补充,它们同时涉及微服务和API。

img

API网关实现的注意事项:

  • 可能的单点故障或瓶颈;
  • 增加响应时间,由于通过API网关增加额外的网络跳转,以及复杂性风险。

阿里内部的高速服务框架HSF(High-speed Service Framework),就解决了上面提到的所有问题:

  • 长时间正常运行(长连接故障剔除机制 -- 有限状态机的应用);
  • 弹性扩展(配置服务器能在几秒内完成新服务的注册发布,并推送更新后的服务列表到服务调用方,以分担服务压力);
  • 易于使用(在注入Bean的xml中定义或者服务实现类声明注解,例如:@HSFProvider(serviceInterface = ItemService.class, serviceVersion = "1.0.0",serviceGroup = "TEST_HSF")就可以开启服务);
  • 服务端单点故障问题(HSF作为一个纯客户端架构的RPC框架,没有服务端集群,所有HSF服务调用均是通过服务消费方Consumer与服务提供方Provider点对点进行。启动时就缓存了服务列表到本地,直接通过本地服务列表的客户端地址通信,服务端挂了也能成功通信。)
  • 增加响应时间问题(客户端端直接通信,不会像增加一跳流量转发)

事件驱动微服务架构

事件驱动架构的两种模式

Broker模式 Broker模式就是使用一个消息中间件,每个服务将自己的消息发送到某一个队列,其他的服务,根据自己的需要,自己去订阅相应的队列,来实现业务逻辑。

这种方式的缺点就是,每个服务都需要知道自己需要的事件在哪里。例如一个微服务系统,有一个订单服务、库存服务和用户服务,当订单服务产生一个订单以后,将该事件发送到新订单的队列,库存服务要订阅这个事件,进行库存的修改等,用户服务也要订阅,进行用户订单等一些处理。如果整个系统中有多个事件会对库存有更新的话,库存服务就需要订阅所有这些队列上的事件。所以,这种方式就变成,把原先微服务之间的通信的网状关系,移到了事件队列上。

Mediator模式 Mediator模式就是增加了一个Mediator(协调者)的角色,他可能是一个服务,也可能是某种具有流程处理功能的消息中间件。它的作用就是当有一个事件的时候,根据业务流程,产生一个个的事件,来进行业务流程的编排。例如在上面订单、库存和用户服务的例子中,当有一个订单的时候,这个协调者(Mediator)就将该事件发送到库存修改的队列里,以及用户的相应队列里。

这种模式的好处就是,每个服务,每个方法,只需要订阅一个自己的队列,根据里面事件的数据,来判断该事件的来源。这样就能进一步降低服务和事件之间的耦合性。这时候,这个事件和服务之间的耦合性,就放到了这个协调者(Mediator)上。但是,由于所有的耦合关系是在统一的一个地方保存,而不是分散在整个系统的多有的服务上,这在可维护性上还是有好处的。

扩展(阿里HSF高速服务框架的异步调用):

SAE服务HSF应用开发异步调用

目前阿里的HSF架构正在往这个方向上进行迁移,从Dubbo 3.0支持RSocket协议就可见端倪。

参考:使用Vantiq实现事件驱动的微服务架构

参考问题和扩展

参考问题和回答

1、网关也是微服务吗?

网关是构建微服务基础设施的一个核心组件,网关部署以后也可以说对外提供服务(反向路由,安全认证,日志监控等),它属于技术基础服务,但不属于业务服务。

2、OpenResty/Kong网关与Nginx之间的关系?

Kong可以认为是专门针对API网关场景的升级版的Nginx,OpenResty是对Nginx的一种扩展,kong其实也是基于OpenResty扩展的。Nginx历史悠久,成熟稳定,应用场景丰富。这些产品总体是互补的,不能简单理解为替代关系。OpenResty/Kong都属于可编程网关。

3、服务分层具体是怎么理解的?

服务分层一般按照职能划分:

  • 微服务层:提供基础业务和技术服务;
  • BFF:聚合裁剪适配服务,面向各种端用户体验(PC, mobile, 第三方接入等);
  • 网关层:负责反向路由,安全,监控等跨横切面功能。

实际每一层和具体协议没有严格对应关系,微服务可以用rpc,也可以http/rest,BFF和网关也可以支持rpc或者http/rest。当然,微服务用dubbo rpc框架,BFF转成http/rest,网关再暴露http,也是一种架构方式。

4、BFF 现在流行的说法是FaaS,网关可以由是 Servless 方式实现,弱化DevOps?

BFF有很多玩法,之前看到过用动态脚本(可在运行时上传动态运行)做BFF,也有尝试用GraphQL做BFF,FaaS/serless做BFF还没有怎么听说,可能是一种新的尝试,anyway,BFF目标是帮助前端快速迭代。

5、是不是应该在BFF与微服务层之间再加一个网关层?如何发现服务?

BFF层可以直接调微服务,走基于注册中心的服务发现即可。如果在BFF和微服务之间再加一层网关(或者nginx反向代理),相当于集中式负载均衡+反向路由,也不是不可以,只是性能损耗会变大,而且维护成本会变高。

比较典型的微服务分层方式:外部client -> 网关GW -> BFF -> Microservices。GW如何发现BFF?BFF如何发现Microservices?这个可以借助注册中心,例如Eureka。如果在k8s环境中,可以不需要注册中心,因为k8s平台本身就支持服务发现。

BFF是聚合服务层,是介于后台基础服务和外部端设备之间的一层,也能算中台的一部分,但只能算偏前端的一小部分。

6、使用nginx作为服务入口,后端微服务为什么还需要很多域名呢,如果只暴露Nginx的ip,那么内部服务也不会暴露在公网上?

在v2版本中,nginx可以只暴露统一ip和域名,然后根据请求path或者参数再转发到后台服务,这样nginx就相当于是一个网关。但在互联网研发早期,很多公司没有把nginx当网关用,运维通常规定所有对外暴露服务必须独立申请公网域名。

搭建Spring Cloud Gateway网关

Spring Cloud Gateway是由 WebFlux + Netty + Reactor实现的响应式的 API 网关。

本项目提供了一个构建在 Spring 生态之上的 API Gateway,包括:Spring 5Spring Boot 2Project Reactor。Spring Cloud Gateway 旨在提供一种简单而有效的方式来路由到 API,并为它们提供横切关注点,例如:安全性监控/指标弹性

服务网关 = 路由转发 + 过滤器

可以实现用户的登录认证跨域请求日志拦截权限控制限流熔断负载均衡黑名单与白名单机制等功能

1. 创建网关子模块

创建 service-gateway 子模块项目:

image-20220623013914142

2. 添加依赖

    <dependencies>
        <!-- nacos 服务发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- gateway 服务网关 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- 无需引入webflux,最新版本默认已集成 -->
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-webflux</artifactId>-->
<!--        </dependency>-->
        <!-- 网关服务名动态路由需要依赖于loadbalancer -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
    </dependencies>

注意:Spring Cloud 2021.0.1版本下的Gateway 3.1.1默认集成了 spring-boot-starter-webflux

3. 配置主启动类服务发现

编写主启动类src/main/java/com/deepinsea/ServiceGatewayApplication.java

package com.deepinsea;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
​
/**
 * Created by deepinsea on 2022/6/24.
 * 网关主启动类
 */
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceGatewayApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(ServiceGatewayApplication.class, args);
    }
}

4. 创建yml配置文件

server:
  # 服务运行端口
  port: 9070
spring:
  application:
    # 应用名称(为了测试负载均衡,与service-provider-nacos服务保持一致)
    name: service-gateway
  cloud:
    nacos:
      discovery:
        # 服务名称(默认就是应用名)
        service: ${spring.application.name}
        # 服务注册地址
        server-addr: localhost:8848
        # Nacos认证信息
        username: nacos
        password: nacos
        # 注册到 nacos 的指定 namespace,默认为 public
        namespace: public

5. 网关路由转发功能

我们可以看到Spring Cloud Gateway的设计其实和Spring MVC的过滤器(相当于Gateway中的谓词)和拦截器(相当于Gateway中的Filter)是类似的,在早期版本也是基于Sevelet实现的。不过后来,更换为Netty服务器实现后,就脱离了Sevelet模型。因此,可以得出一个结论:那就是所有的网关架构都是依赖于底层服务器实现的,包括Kong以及Apisix,说白了就是服务器的条件和功能封装。

网关的路由功能相当于反向代理,真实后端接口地址还是能正常访问的。

我们测试基于断言(路径匹配)的路由规则,需要两个不同路径的接口作为对照,因此我们需要先创建一个控制器然后添加两个接口:

package com.deepinsea.controller;
​
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
/**
 * Created by deepinsea on 2022/6/24.
 */
@RestController
@RequestMapping("/gateway")
public class HelloController {
​
    @GetMapping("/api/hello")
    public String hello(){
        return "hello, 这里是service-gateway网关, 恭喜你请求了正确的路径!";
    }
​
    @GetMapping("/error/hello")
    public String error(){
        return "hello, 这里是service-gateway网关, 好小子你直接跳过网关来请求真实后端地址是吧!";
    }
}

下面使用网关的静态路由和动态路由分别进行测试:

基于IP的静态路由转发(网关服务本身测试)

首先配置网关路由规则如下:

    # 网关
    gateway:
      # 启用开关(默认开启)
      enabled: true
      # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务] --本质就是反向代理
      routes:
        - id: service-provider-nacos             # 当前路由的标识, 要求唯一
          uri: lb://service-provider-nacos       # lb指的是从 nacos 中按照名称获取微服务,并遵循负载均衡策略路由请求(动态路由)
          order: 10 # 路由的优先级,数字越小代表路由的优先级越高(最小为0)
          predicates: # 断言(就是路由转发要满足的条件)
            - Path=/provider-nacos/**             # 当请求路径满足Path指定的规则时,才进行路由转发
        # 我们⾃定义的路由 ID,保持唯⼀
        - id: service-gateway
          # ⽬标服务地址(部署多实例,不能加子路径)
          uri: http://localhost:9070 # 指定具体的微服务地址(原真实服务地址还是可以访问,如果要限制走网关可以加token验证机制)
#          uri: https://www.baidu.com # 指定具体的微服务地址
#          uri: lb://service-gateway
          # gateway⽹关从服务注册中⼼获取实例信息然后负载后路由
          # 断⾔:路由条件,Predicate 接受⼀个输⼊参数,返回⼀个布尔值结果。该接⼝包含多种默认⽅法来将 Predicate 组合成其他复杂的逻辑(⽐如:与,或,⾮)。
          predicates:
            # 当请求的路径为http://localhost:9070/looptest/gateway/api/hello时,转发到http://localhost:9070/gateway/api/hello
            - Path=/looptest/gateway/api/hello # 本身就是基于path的反向代理
#            - Path=/**
          filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
            - StripPrefix=1              # 转发之前去掉1层路径(去除原始请求路径中的前1级路径,即/looptest)

然后使用curl命令分别测试正常路由地址错误路由地址真实后端地址访问

C:\Users\deepinsea>curl http://localhost:9070/looptest/gateway/api/hello
hello, 这里是service-gateway网关, 恭喜你请求了正确的路径!
C:\Users\deepinsea>curl http://localhost:9070/looptest/gateway/error/hello
{"timestamp":"2022-06-24T22:07:31.299+00:00","path":"/looptest/gateway/error/hello","status":404,"error":"Not Found","message":null,"requestId":"f520b672-3"}
C:\Users\deepinsea>curl http://localhost:9070/gateway/error/hello
hello, 这里是service-gateway网关, 好小子你直接跳过网关来请求真实后端地址是吧!

和预期一致,因为走了网关的路由匹配的会进行条件判断,而直接访问真实地址没有走条件判断也是可以访问的。

这是因为,网关的路由本身就是反向代理;而反向代理是不会影响原来真实地址的接口的,也就是说:网关符合条件的请求和后端服务真实地址的请求这两种方式都是可以的

基于服务名的动态路由转发(网关服务本身测试)

这里有两层功能,一个是Gateway本身根据路由规则routes的反向代理功能,另一个是通过loadbalancer组件实现的服务名负载均衡调用。因此 ,只要是服务名调用就需要添加loadbalancer依赖

首先配置yml路由规则:

    # 网关
    gateway:
      # 启用开关(默认开启)
      enabled: true
      # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务] --本质就是反向代理
      routes:
        - id: service-provider-nacos             # 当前路由的标识, 要求唯一
          uri: lb://service-provider-nacos       # lb指的是从 nacos 中按照名称获取微服务,并遵循负载均衡策略路由请求(动态路由)
          order: 10 # 路由的优先级,数字越小代表路由的优先级越高(最小为0)
          predicates: # 断言(就是路由转发要满足的条件)
            - Path=/provider-nacos/**             # 当请求路径满足Path指定的规则时,才进行路由转发
        # 我们⾃定义的路由 ID,保持唯⼀
        - id: service-gateway
          # ⽬标服务地址(部署多实例,不能加子路径)
#          uri: http://localhost:9070 # 指定具体的微服务地址(原真实服务地址还是可以访问,如果要限制走网关可以加token验证机制)
#          uri: https://www.baidu.com # 指定具体的微服务地址
          uri: lb://service-gateway
          # gateway⽹关从服务注册中⼼获取实例信息然后负载后路由
          # 断⾔:路由条件,Predicate 接受⼀个输⼊参数,返回⼀个布尔值结果。该接⼝包含多种默认⽅法来将 Predicate 组合成其他复杂的逻辑(⽐如:与,或,⾮)。
          predicates:
            # 当请求的路径为http://localhost:9070/looptest/gateway/api/hello时,转发到http://localhost:9070/gateway/api/hello
            - Path=/looptest/gateway/api/hello # 本身就是基于path的反向代理
#            - Path=/**
          filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
            - StripPrefix=1              # 转发之前去掉1层路径(去除原始请求路径中的前1级路径,即/looptest)

因此,我们在pom文件中添加loadbalancer依赖:

        <!-- 网关服务名动态路由需要依赖于loadbalancer -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>

启动网关子模块后,使用curl命令测试 uri: lb//service-gateway 服务名动态路由功能:

C:\Users\deepinsea>curl http://localhost:9070/looptest/gateway/api/hello
hello, 这里是service-gateway网关, 恭喜你请求了正确的路径!

测试成功,成功通过服务名动态路由的方式访问了网关本身的接口!

注释loadbalancer依赖,测试uri: lb//service-gateway 的调用方式是否生效:

        <!-- 网关服务名动态路由需要依赖于loadbalancer -->
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-loadbalancer</artifactId>-->
<!--        </dependency>-->

然后继续调用同一个接口,结果如下:

C:\Users\deepinsea>curl http://localhost:9070/looptest/gateway/api/hello
{"timestamp":"2022-06-24T21:06:00.620+00:00","path":"/looptest/gateway/api/hello","status":503,"error":"Service Unavailable","requestId":"57c0aa39-2"}

注意网关基于服务名的动态路由需要依赖于loadbalancer依赖,如果不引入spring-cloud-loadbalancer依赖,会报以上错误。

那么,通过分析到原因

因为最新版的nacos默认没有集成loadbalancer(但默认支持loadbalancer),通过注册中心获取服务列表然后在网关进行负载均衡调用这个过程依赖于负载均衡组件,因此需要手动引入loadbalancer

我们引入loadbalancer,但是使用基于IP的静态路由转发,能调用成功吗

首先将uri的目标地址由 lb://service-gateway 改为 http://localhost:9070

          uri: http://localhost:9070 # 指定具体的微服务地址(原真实服务地址还是可以访问,如果要限制走网关可以加token验证机制)
#          uri: https://www.baidu.com # 指定具体的微服务地址
#          uri: lb://service-gateway

然后重启网关模块项目,进行调用:

C:\Users\deepinsea>curl http://localhost:9070/looptest/gateway/api/hello
hello, 这里是service-gateway网关, 恭喜你请求了正确的路径!

测试成功,说明引入loadbalancer后,IP调用还是生效的

网关路由到公网https服务(代理劫持百度)

首先配置网关服务(http://localhost:9070)的路由地址为www.baidu.com,然后配置路由的谓词(或称为断言)的路径验证参数为根(全)路径,因此我们直接访问http://localhost:9070就会代理到百度的首页:

    # 网关
    gateway:
      # 启用开关(默认开启)
      enabled: true
      # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务] --本质就是反向代理
      routes:
        - id: service-provider-nacos             # 当前路由的标识, 要求唯一
          uri: lb://service-provider-nacos       # lb指的是从 nacos 中按照名称获取微服务,并遵循负载均衡策略路由请求(动态路由)
          order: 10 # 路由的优先级,数字越小代表路由的优先级越高(最小为0)
          predicates: # 断言(就是路由转发要满足的条件)
            - Path=/provider-nacos/**             # 当请求路径满足Path指定的规则时,才进行路由转发
        # 我们⾃定义的路由 ID,保持唯⼀
        - id: service-gateway
          # ⽬标服务地址(部署多实例,不能加子路径)
#          uri: http://localhost:9070 # 指定具体的微服务地址(原真实服务地址还是可以访问,如果要限制走网关可以加token验证机制)
          uri: https://www.baidu.com # 指定具体的微服务地址
#          uri: http://blog.yuqiyu.com # 指定具体的微服务地址
#          uri: lb://service-gateway
          # gateway⽹关从服务注册中⼼获取实例信息然后负载后路由
          # 断⾔:路由条件,Predicate 接受⼀个输⼊参数,返回⼀个布尔值结果。该接⼝包含多种默认⽅法来将 Predicate 组合成其他复杂的逻辑(⽐如:与,或,⾮)。
          predicates:
            # 当请求的路径为http://localhost:9070/looptest/gateway/api/hello时,转发到http://localhost:9070/gateway/api/hello
#            - Path=/looptest/gateway/api/hello # 本身就是基于path的反向代理
            - Path=/**
          filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
#            - StripPrefix=1              # 转发之前去掉1层路径(去除原始请求路径中的前1级路径,即/looptest)

我们通过浏览器访问http://localhost:9070,然后可以看到经过网关的静态路由代理后,跳转到了百度的地址:

image-20220625035331088

测试成功!

基于负载均衡服务的动态路由调用

我们之前是通过网关单服务,测试了基于IP的静态路由匹配规则基于服务名的动态路由匹配规则以及真实后端服务接口是否不走路由匹配这三大用例,下面我们来测试其他多个互为负载均衡的外部服务的网关动态路由功能:

首先yml的网关配置如下:

    # 网关
    gateway:
      # 启用开关(默认开启)
      enabled: true
      # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务] --本质就是反向代理
      routes:
        - id: service-provider-nacos             # 当前路由的标识, 要求唯一
          uri: lb://service-provider-nacos       # lb指的是从 nacos 中按照名称获取微服务,并遵循负载均衡策略路由请求(动态路由)
          order: 10 # 路由的优先级,数字越小代表路由的优先级越高(最小为0)
          predicates: # 断言(就是路由转发要满足的条件)
            - Path=/provider-nacos/**             # 当请求路径满足Path指定的规则时,才进行路由转发
        # 我们⾃定义的路由 ID,保持唯⼀
        - id: service-gateway
          # ⽬标服务地址(部署多实例,不能加子路径)
          uri: http://localhost:9070 # 指定具体的微服务地址(原真实服务地址还是可以访问,如果要限制走网关可以加token验证机制)
#          uri: https://www.baidu.com # 指定具体的微服务地址
#          uri: lb://service-gateway
          # gateway⽹关从服务注册中⼼获取实例信息然后负载后路由
          # 断⾔:路由条件,Predicate 接受⼀个输⼊参数,返回⼀个布尔值结果。该接⼝包含多种默认⽅法来将 Predicate 组合成其他复杂的逻辑(⽐如:与,或,⾮)。
          predicates:
            # 当请求的路径为http://localhost:9070/looptest/gateway/api/hello时,转发到http://localhost:9070/gateway/api/hello
            - Path=/looptest/gateway/api/hello # 本身就是基于path的反向代理
#            - Path=/**
          filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
            - StripPrefix=1              # 转发之前去掉1层路径(去除原始请求路径中的前1级路径,即/looptest)

然后分别启动 service-provider-nacosservice-provider-apiservice-gateway这三个子模块项目,使用curl命令测试网关的动态路由功能(基于服务名的反向代理):

C:\Users\deepinsea>curl http://localhost:9070/provider-nacos/hello
hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9070/provider-nacos/hello
hi, this is service-provider-api!
C:\Users\deepinsea>curl http://localhost:9070/provider-nacos/hello
hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9070/provider-nacos/hello
hi, this is service-provider-api!

可以看到,我们通过网关地址请求,成功动态路由到了负载均衡服务上,并且生效了loadbalancer默认的轮询负载策略。

同样的,我们直接访问真实后端服务器的地址,也是正常访问的:

C:\Users\deepinsea>curl http://localhost:9010/provider-nacos/hello
hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9040/provider-nacos/hello
hi, this is service-provider-api!

通过内部真实服务地址请求,访问同样成功!

内部服务之间能够直接调用吗?

我们再启动一个 service-loadbalancer 服务,用于内部服务调用测试。启动完成后,使用curl命令测试:

C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-nacos!
C:\Users\deepinsea>curl http://localhost:9060/consumer-loadbalancer/hello
openfeign负载均衡调用成功,返回结果为: hi, this is service-provider-api!

可以看到内部服务调用成功,并且生效了 service-loadbalancer 的轮询负载策略!

欢迎点赞还有评论,谢谢大佬ヾ(◍°∇°◍)ノ゙