SpringCloud微服务网关
。一、什么是微服务网关
在微服务架构中,整个系统是由很多个微服务组成的,每一个微服务负责一块独立的业务,当外部的系统需要调用微服务中的一些功能的时候,我们对外提供接口(暴露接口)。
微服务架构中,服务的实例地址经常发生变化,所以我们无法直接将服务的地址暴露出来。如果每一个微服务都直接暴露接口,会导致一些列的问题,比如调用过于复杂,涉及到账户、权限不能统一处理等。另外基于高内聚低耦合的设计准则来讲,我们也应该将内部系统和外部系统做切割。
在这样的场景下,就需要有一个独立的组件来处理外部的请求,这个组件就是服务网关。服务网关是为了简化前端的调用逻辑,通常情况下也会实现相关的认证逻辑,根据外部不同的请求响应不同的数据,从而简化内外部系统之间调用的复杂度。
网关的作用:
- 简化客户端调用的复杂度,统一处理外部请求。
- 数据的裁剪和聚合,根据不同的接口需求,对数据加工后对外提供接口。
- 多平台的支持,对不同的客户端提供不同的网关支持。
- 遗留系统的微服务化改造,可以作为新老系统的中转组件。
- 统一处理调用过程中的安全、权限问题。
下图为服务网关调用示意图:
服务网关负责服务请求路由、组合及协议转换。客户端的所有请求都首先经过服务网关,然后由它将请求路由到合适的微服务。服务网关经常会通过调用多个微服务并合并结果来处理一个请求,它可以在系统外部与内部响应之间友好的转换。
二、Spring Cloud 体系内的网关技术
Spring Cloud 体系内的网关技术主要有 Zuul 和 Spring Cloud Gateway。Zuul 是 Netflix 公司开源的产品,也被人称为第一代网关。也是 Spring Cloud 前几个版本默认使用的服务网关组件,但是随着 Netflix 公司一系列的停更事件,在最新的 Spring Cloud Greenwich 版本中已经不建议采用 Zuul 技术,官方建议使用 Spring Cloud Gateway 作为默认的网关技术。 Spring Cloud Gateway作为第二代网关技术,比Zull更强,官方会一直维护更新下去。
Zuul
Zuul 是 Netflix 开发的一款提供动态路由的网关服务。Zuul 接收所有设备或者网站请求,根据需求将请求转发到对应的后端系统。作为一个前置服务,Zuul 旨在实现动态路由,监控,弹性和安全性。
Zuul 提供了不同类型的 filter 用于处理请求,这些 filter 可以让我们实现以下功能:
- 权限控制和安全性:可以识别认证需要的信息和拒绝不满足条件的请求。
- 监控:与边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图。
- 动态路由:根据需要动态地路由请求到后台的不同集群。
- 压力测试:逐渐增加指向集群的流量,以了解性能。
- 负载均衡:为每一种负载类型分配对应容量,并弃用超出限定值的请求。
- 静态资源处理:直接在 zuul 处理静态资源的响应,从而避免其转发到内部集群。
Filter 是 Zuul 的核心,用来实现对外服务的控制。Filter 的生命周期有4个,分别是“PRE”、“ROUTING”、“POST”、“ERROR”,整个生命周期可以用下图来表示:
Zuul 大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。
- PRE: 这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul 还允许我们创建自定义的过滤器类型。例如,我们可以定制一种 STATIC 类型的过滤器,直接在 Zuul 中生成响应,而不将请求转发到后端的微服务。
在实际工作中可以根据业务的使用场景,来选择使用哪种类型的过滤器。 由于 Zuul2 不断推迟发布、以及 Netfilx 内部对开源软件支持的不稳定性,Spring Cloud 自行研发了另外一款服务网关产品 Spring Cloud GateWay,并且在最新版本中推荐使用 Spring Cloud GateWay。
Spring Cloud GateWay
Spring Cloud Gateway 是 Spring 体系内的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全、监控/指标和限流。
特性
- 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
- 动态路由
- Predicates 和 Filters 作用于特定路由
- 集成 Hystrix 断路器
- 集成 Spring Cloud DiscoveryClient
- 易于编写的 Predicates 和 Filters
- 限流
- 路径重写
Spring Cloud Gateway 中的相关概念:
- Route(路由):这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
- Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
- Filter(过滤器):这是`org.springframework.cloud.gateway.filter.GatewayFilter`的实例,我们可以使用它修改请求和响应。
工作原理
客户端向 Spring Cloud Gateway 发出请求。如果 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
虚线左边的是 pre 类型过滤器,虚线右边的是 post 类型过滤器,外部请求进来后先经过 pre 类型的过滤器,再发送到代理服务,代理服务响应请求,再次经过 post 过滤器链,最后传递给前端。
Spring Cloud Gateway vs Zull
- Zuul 是 Netflix 开源的产品,Spring Cloud Gateway 是 Spring 体系内的产品,和 Spring 体系融合更好。
- Zuul1 不支持长连接,比如 WebSockets;Spring Cloud Gateway 支持 WebSockets 等协议。
- 作为一个服务网关产品,Spring Cloud Gateway 考虑更全面一些,增加了 Predicate、限流等技术。
- Zuul1 是基于 Servlet 框架构建,采用的是阻塞和多线程方式,即一个线程处理一次连接请求,这种方式在内部延迟严重、设备故障较多情况下会引起存活的连接增多和线程增加的情况发生。
- Spring Cloud Gateway 基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 开发,采用了 Netty 实现了异步非阻塞模型,占用资源更小,在性能方面更有优势。
综合比较,Spring Cloud Gateway 在使用效率和性能方面更好一些,可以预期未来 Spring Cloud GateWay 的使用率会越来越高。
三、Spring之GateWay网关
GateWay 快速入门
添加核心依赖)
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies>
Spring Cloud GateWay 配置路由有两种方式,分别是注解和配置文件来进行配置。
编程式
在启动类上以@Bean的形式注入RouteLocator来配置路由
@Bean public RouteLocator redirectRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("path_router", r -> r.path("/get") .uri("http://httpbin.org")) .build(); }
上面这段配置的意思是,配置了一个 id 为 path_route 的路由规则,当访问地址 http://localhost:8080/get
时会自动转发到地址:http://httpbin.org/get
。配置完成启动项目(只需要gateway依赖)即可在浏览器访问进行测试,当我们访问地址http://localhost:8080/get
时页面展示如下信息:
配置文件方式
在resource 中使用.properties或者.yml,这里我用yml来演示,首先在application.yml中配置如下参数:
server: port: 8080 spring: cloud: gateway: routes: - id: path_route uri: http://httpbin.org predicates: - Path=/get
配置说明:
- id:我们自定义的路由 ID,保持唯一,代码中就是
route()
方法的第一个参数。 - uri:需要转发的目标服务地址,
r -> r.path("/get").uri("http://httpbin.org")
代码使用了函数时编程简化代码。 - predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
- filters:过滤规则,本示例暂时没用。
配置完成之后我们把上启动类中的中的RouteLocator 配置全部注销掉, 然后重新启动,访问http://localhost:8080/get
,返回信息和上述返回的结果一致,这说明了通过配置的方式也可以到达路由转发的功能。在实际项目中为了方便维护,推荐使用配置文件的方式来配置。
网关和注册中心
上面两种转发方式只适合在单一的项目中使用,如果在微服务架构中就会存在一个致命的问题,微服务架构中服务提供者是动态变化的,所以不能直接将目标服务器地址写在配置文件中。那么在微服务架构中如何解决这个问题呢,这个时候就需要和注册中心来配置使用,本文以 Eureka 和 Spring Cloud Gateway 为例给大家讲解。
当网关和注册中心结合起来使用时,把网关当作一个客户端注册到注册中心,然后网关从注册中心获取所有服务,并自动为这些服务提供路由转发功能。Spring Cloud Gateway 提供了此功能,在项目中只需要简单配置即可达到这样的效果,接下来进行演示。
首先我们创建一个父工程,有四个子工程:
- eureka-server :注册中心
- service-provider:服务提供者
- service-consumer:服务消费者
- eureka-gateway:服务网关
我们将eureka-gateway注册到eureka-server中,并且配置网关:
spring: application: name: eureka-gateway cloud: gateway: discovery: locator: enabled: true server: port: 8888 eureka: client: service-url: defaultZone: http://localhost:1111/eureka/ logging: level: org.springframework.cloud.gateway: debug
配置说明:
- spring.cloud.gateway.discovery.locator.enabled:是否开启通过注册中心进行路由转发的功能,通过 serviceId 转发到服务,默认为 false。
- logging.level.org.springframework.cloud.gateway:调整 gateway 包的 log 级别,以便排查问题。
service-provider、service-consumer、eureka-gateway都注册到eureka-server后,我们通过网关调用服务:http://localhost:8888/SERVICE-PROVIDER/getUser
Spring Cloud Gateway 支持和注册中心结合起来使用,只要将 Spring Cloud Gateway 注册到注册中心,即可自动代理注册中心中的所有服务,简化路由配置和使用方式。
注意访问的格式是这样的:http://网关地址:端口/服务中心注册 serviceId/具体的 url
GateWay的高级用法
Predicate 和 Filter 是 Spring Cloud Gateway 的核心,通过这两个功能点的灵活配置使用,Spring Cloud Gateway 的使用变得高效、简单。Predicate 的核心作用是路由选择,通过一些列的规则配置,让我们知道哪些请求可以被某个规则转发;Filter 是过滤器,在 Predicate 删选出某些请求需要转发时,Filter 负责在这些请求的执行前或者执行后做一些处理,比如安全校验、参数处理等。
Predicate是用来请求需要处理,而Filter就是对请求需要处理做一些变更。
Predicate
Predicate 是来自于JDK 8中对一个函数,Predicate接受一个输出参数,它返回一个布尔值,可以用于接口参数的校验,判断新老数据的变化需要进行更新操作。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。Spring Cloud Gateway 中内置了几种 Predicate 的实现,如下图:
通过时间匹配
Predicate 支持设置一个时间,在请求进行转发的时候,可以通过判断在这个时间之前或者之后进行转发。比如我们现在设置只有在2019年1月1日才会转发到我的网站,在这之前不进行转发,我就可以这样配置:
spring: cloud: gateway: routes: - id: time_route uri: https://blog.csdn.net/qwe86314 predicates: - After=2019-09-13T17:33:06+08:00[Asia/Shanghai]
Spring 是通过 ZonedDateTime 来对时间进行的对比,ZonedDateTime 是 Java 8 中日期时间功能里,用于表示带时区的日期与时间信息的类,ZonedDateTime 支持通过时区来设置时间,中国的时区是:Asia/Shanghai。
After Route Predicate 是指在这个时间之后的请求都转发到目标地址。上面的示例是指,请求时间在 2019年9月13日17点33分6秒之后的所有请求都转发到地https://blog.csdn.net/qwe86314
。+08:00 是指时间和 UTC 时间相差八个小时,时间地区为Asia/Shanghai
。
添加完路由规则之后,访问地址http://localhost:8080
会自动转发到https://blog.csdn.net/qwe86314
其它的两个时间判断和上面类似,只需要替换对应的 predicates 值即可。
predicates: - After=2019-09-13T17:33:06+08:00[Asia/Shanghai] - Before=2019-09-13T17:33:06+08:00[Asia/Shanghai] - Between=2019-09-13T17:33:06+08:00[Asia/Shanghai], 2019-09-13T17:34:06+08:00[Asia/Shanghai]
通过请求方式匹配
请求方式即使页面表单的请求类型,比如:POST、GET、PUT、DELETE ,Spring Cloud Gateway 内置了 Predicate 可根据不同的请求方式来选择路由。
我们来配置这个GET方式转发:
spring: cloud: gateway: routes: - id: method_route uri: https://blog.csdn.net/qwe86314 predicates: - Method=GET
配置完成后我们启动项目可以通过curl 命令 如: curl http://localhost:8080
当然你也可以通过这个浏览器输入地址去访问,但是特别注意的是我们这个当前的路由只能是GET,如果尝试用POST去访问,那么返回的结果就是404。
其它的请求方式只需要将Method里面的GET换成POST、PUT等其它方式就可以。
请求路径匹配
Path Route Predicate 接收一个匹配路径的参数来判断是否走路由。
我们来配置一下路由匹配:
spring: cloud: gateway: routes: - id: path_route uri: http://cxytiandi.com predicates: - Path=/course/{segment}
我们在浏览器地址输入http://localhost:8080/course/25
就会返回相应的页面信息,而我们输入http://localhost:8080/course/100
就会返回404的页面。
请求参数匹配
Query Route Predicate 支持传入两个参数,一个是属性名一个为属性值,属性值可以是正则表达式。
spring: cloud: gateway: routes: - id: query_route uri: http://cxytiandi.com predicates: - Query=course/vipType
我们去浏览器访问:http://localhost:8080/course/vipType?=1
还可以将 Query 的值以键值对的方式进行配置,这样在请求过来时会对属性值和正则进行匹配,匹配上才会走路由。
我们将 - Query=course/vipType 修改为 - Query=course/vipType,1
这样当请求中包含course/vipType并且属性值为1就会匹配到内容否则会出404,有些网站会出现跨域问题。建议在自己网站测试。
组合使用
在我们日常工作中,往往会使用多个 Predicate 来进行判断,Spring Cloud Gateway 支持同时配置多个 Predicate 条件,各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。
spring: cloud: gateway: routes: - id: mehtod_path_time uri: http://cxytiandi.com/course predicates: - Method=GET - Query=vipType,1 - After=2019-09-14T10:16:13+08:00[Asia/Shanghai]
需要注意的是:一个请求满足多个路由条件时,请求只会被首个成功匹配的路由转发
Filter
Spring Cloud Gateway 的 Filter 的生命周期不像 Zuul 的那么丰富,它只有两个:pre
和 post
。
- PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现权限管理、安全校验、记录调试信息等。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
Spring Cloud Gateway 的 Filter 分为两种:GatewayFilter 与 GlobalFilter。GlobalFilter 会应用到所有的路由上,而 GatewayFilter 将应用到单个路由或者一个分组的路由上。
GateWay Filter
Spring Cloud Gateway 内置了 25 种 GatewayFilter 和一个 Default Filters。按照不同作用方式我们将它划分为 12 大类。
Global Filter
Spring Cloud Gateway 内置了7种 GlobalFilter,比如 Netty Routing Filter、LoadBalancerClient Filter、Websocket Routing Filter 等,根据名字即可看出这些 Filter 的作用。
多个 Global Filter 可以通过 @Order 或者 getOrder() 方法指定每个 Global Filter 的执行顺序,order 值越小,Global Filter 执行的优先级越高。
利用 GatewayFilter 可以修改 Http 的请求或者响应,或者根据请求或者响应做一些特殊的限制,更多时候我们会利用 GatewayFilter 做一些具体的路由配置。
AddRequestHeader GatewayFilter
AddRequestHeader GatewayFilter 是匹配的请求中添加Header头相关的参数。
我们首先创建一个普通的maven项目,然后再依次的在这个父模块下创建子模块:eureka、provider、consumer、gateway模块。然后分别注册到eureka。
spring: application: name: gateway cloud: gateway: discovery: locator: enabled: true routes: - id: add_request_parameter_route uri: lb://consumer filters: - AddRequestHeader=Content-Type, application/json predicates: - Method=POST server: port: 8083 eureka: client: service-url: defaultZone: http://localhost:8888/eureka/ logging: level: org.springframework.cloud.gateway: debug
配置说明:
- spring.cloud.gateway.discovery.locator.enabled,是否与服务发现组件进行结合,通过 serviceId 转发到具体的服务实例。默认为 false,设为 true 便开启通过服务中心的自动根据 serviceId 创建路由的功能。
- spring.cloud.gateway.routes.uri=lb://consumer,配置路由转发到名为 provider 的服务消费者。
- spring.cloud.gateway.routes.filters ,配置需要执行 Filter 的具体实现。
- spring.cloud.gateway.routes.filters.AddRequestHeader配置请求需要的Header头
- spring.cloud.gateway.routes.predicates,请求的删选条件, Filter 需要和 Predicate 配合使用。
配置完成后我们分别启动,然后通过postman或者RestletClient Http请求工具然后访问http://localhost:8083/rest/SaveUser
然后加入Content-Type为application/json 然后添加参数然后发送,就返回了正确的结果。
注意这里默认使用了全局过滤器 LoadBalancerClient ,当路由配置中 uri 所用的协议为 lP 时(已uri: IP://provider
为例),gateway 将使用 LoadBalancerClient 把 consumer 通过 eureka 解析为实际的主机和端口,并进行负载均衡。