1. 微服务网关与用户身份识别
- 在微服务分布式架构下,客户端(如浏览器)直接访问Provider服务提供者会存在以下问题:
- 客户端需要进行负载均衡,从多个Provider中挑选最合适的微服务提供者。
- 存在跨域请求时,服务端需要进行额外处理。
- 每个服务需要进行独立的用户认证。
- 解决以上问题的手段就是使用微服务网关。
- 微服务网关是微服务架构中不可或缺的部分,它统一解决Provider路由、均衡负载、权限控制等功能。
- 微服务网关的功能如图所示
- 微服务网关的实现框架有多种,Spring Cloud全家桶中比较常用的有Zuul和Spring Cloud Gateway两大框架。
- 虽然Spring Cloud官方推荐自家的Spring Cloud Gateway框架,但是,由于Zuul使用非常广泛且文档更加丰富,因此推荐使用Zuul作为生产场景的微服务网关,在高并发的使用场景中则推荐使用Spring Cloud Gateway框架作为网关。
2. Zuul 简介
- Zuul是Netflix公司的开源网关产品,可以和Eureka、Ribbon、Hystrix等组件配合使用。
- Zuul的规则引擎和过滤器基本上可以用任何JVM语言编写,内置支持Java和Groovy。
- 在Spring Cloud框架中,Zuul的角色是网关,负责接收所有的REST请求(如网页端、App端等),然后进行内部转发,是微服务提供者集群的流量入口。
- 将Zuul称为内部网关,以便和Nginx外部网关相区分。
- Spring Cloud对Zuul进行了整合与增强。
- Zuul作为网关层,自身也是一个微服务,跟其他服务提供者一样都注册在Eureka Server上,可以相互发现。
- Zuul能感知到哪些Provider实例在线,同时通过配置路由规则可以将REST请求自动转发到指定的后端微服务提供者。
- Zuul的功能大致有:
- 路由:将不同REST请求转发至不同的微服务提供者,其作用类似于Nginx的反向代理。同时,也起到了统一端口的作用,将很多微服务提供者的不同端口统一到了Zuul的服务端口。
- 认证:网关直接暴露在公网上时,终端要调用某个服务,通常会把登录后的token(令牌)传过来,网关层对token进行有效性验证。如果token无效(或没有token),就不允许访问REST服务。可以结合Spring Security中的认证机制完成Zuul网关的安全认证。
- 限流:高并发场景下瞬时流量不可预估,为了保证服务对外的稳定性,限流成为每个应用必备的一道安全防火墙。如果没有这道安全防火墙,那么请求的流量超过服务的负载能力时很容易造成整个服务的瘫痪。
- 负载均衡:在多个微服务提供者之间按照多种策略实现负载均衡。
3. 创建Zuul网关服务
- 添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
2.在启动类中添加注解@EnableZuulProxy,声明这是一个网关服务提供者
package com.crazymaker.springcloud.cloud.center.zuul; ... @EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class}) @SpringBootApplication(scanBasePackages = {"com.crazymaker.springcloud.cloud.center.zuul", "com.crazymaker.springcloud.standard", "com.crazymaker.springcloud.user.info.contract" }) @EnableScheduling @EnableHystrix @EnableDiscoveryClient //开启网关服务 @EnableZuulProxy @EnableCircuitBreaker public class ZuulServerApplication { public static void main(String[] args) { SpringApplication.run(ZuulServerApplication.class, args); } }
3.1. Zuul路由配置规则
- 作为反向代理,Zuul需要通过路由规则将REST请求转发到上游的微服务Provider。
- crazy-springcloud脚手架中Zuul网关的路由规则配置示例:
#服务网关配置 zuul: ribbonIsolationStrategy: THREAD host: connect-timeout-millis: 600000 socket-timeout-millis: 600000 #路由规则 routes: seckill-provider: path: /seckill-provider/** serviceId: seckill-provider strip-prefix: false message-provider: path: /message-provider/** serviceId: message-provider strip-prefix: false user-provider: path: /user-provider/** serviceId: user-provider strip-prefix: false backend-provider: path: /backend-provider/** serviceId: backend-provider strip-prefix: false generate-provider: path: /generate-provider/** serviceId: generate-provider strip-prefix: false sensitiveHeaders: Cookie,Set-Cookie,token,backend,Authorization demo-provider: path: /demo-provider/** serviceId: demo-provider strip-prefix: false urlDemo: path: /blog/** url: https://www.cnblogs.com sensitiveHeaders: Cookie,Set-Cookie,token,backend,Authorization
3.以上示例中,有两种方式的路由规则配置:(1)路由到直接URL;(2)路由到微服务提供者。
4.先看第一种方式的路由规则配置:路由到直接URL。在上述示例中,有一条名为urlDemo的路由规则,该规则匹配到格式为/blog/**的所有URL请求,直接转发到https://www.cnblogs.com的地址上;
5.比如,通过网关访问如下URL:
http://127.0.0.1:7799/blog/crazymakercircle/p/9904544.html
此URL满足/blog/**的匹配规则,将被Zuul直接转发到上游的URL地址:
https://www.cnblogs.com/crazymakercircle/p/9904544.html
- 再看第二种方式的路由规则配置:路由到微服务提供者。比如在上述代码中,有一条名为user-provider的路由规则,该规则将匹配/user-provider/**的所有URL请求,直接路由到名为user-provider的某个微服务提供者。
- 两种方式的区别如下:
- 第一种方式使用url属性来指定直接的上游URL的前缀;第二种方式使用serviceId属性来指定上游服务提供者的名称。
2.第二种方式需要结合Eureka Client客户端来实现动态的路由转发功能,启动类需要加上注解@EnableDiscoveryClient,只能用于Spring Cloud架构中。其实该注解也可以不加,因为网关注解@EnableZuulProxy已经默认进行了导入。
3.2. 过滤敏感请求头部
- 在同一个系统中,在不同Provider之间共享请求头是可行的,但是,如果Zuul需要将请求转发到外部,可能不希望敏感的请求头泄露到外部的其他服务器。
- 防止请求头泄露的方式之一是,在Zuul的路由配置中指定要忽略的请求头列表,并且多个敏感头部之间可以用逗号隔开。下面是一个简单的实例:
spring: application: name: cloud-zuul zuul: sensitiveHeaders: Cookie,Set-Cookie,token,backend,Authorization
3.Cookie经常用于在流量中缓存用户的会话、用户凭证等信息,对于外部系统而言是需要保密的,所以应该设置为敏感标题,不应该带往系统外部。
4.默认情况下,Zuul转发请求时会把header清空,如果在微服务集群内部转发请求,上游Provider就会收不到任何头部。
5.也可对单个路由规则进行局部配置,比如crazy-springcloud脚手架中专门对外部的转发规则urlDemo进行了请求头的屏蔽,它的配置如下:
#服务网关路由规则 zuul: routes: urlDemo: path: /blog/** url: https://www.cnblogs.com sensitiveHeaders: Cookie,Set-Cookie,token,backend,Authorization
- 单个路由规则的局部配置会覆盖全局的设置。
3.3. 路径前缀的处理
- 如果不进行任何配置,默认情况下Zuul会去掉路由的路径前缀。
- 如果上游的微服务提供者没有配置路径前缀,Zuul的这种默认处理和转发就不会有问题。
- 如果上游提供者配置了统一的路径前缀,而前缀被去掉,上游服务提供者就会报出404的错误,也就是找不到URL对应的资源。
- 在Zuul进行路由处理时,如何保留请求URL中的路径前缀呢?可以设置配置项stripPrefix的值为false,确保路径前缀不会截取掉。stripPrefix的值默认为true。
4. Zuul过滤器
- Spring Cloud Zuul除了可以实现请求的路由功能外,还有一个重要的功能就是过滤器。
- Zuul可以通过定义过滤器来实现请求的拦截和过滤,而它本身的大部分功能也是通过过滤器实现的。
4.1. Zuul网关的过滤器类型
- pre类型的过滤器:此类型为请求路由之前调用的过滤器,可利用此类过滤器来实现身份验证、记录调试信息等。
- route类型的过滤器:此类型为发送请求到上游服务的过滤器,比如使用Apache HttpClient或Netflix Ribbon请求上游服务。
- post类型的过滤器:此类型为上游服务返回之后调用的过滤器,可用来为响应添加HTTP响应头、收集统计信息和指标、将响应回复给客户端。
- error类型的过滤器:此类型为在其他阶段发生错误时执行的过滤器。
- 除了默认的过滤器类型外,Zuul还允许我们创建自定义的过滤器类型,例如可以定制一种echo类型的过滤器,直接在Zuul中生成响应,而不将请求转发到上游的服务。
- Zuul的请求处理流程如下:
- 当外部请求到达Zuul网关时,首先会进入pre处理阶段,在这个阶段请求将被pre类型的过滤器处理,以完成再请求路由的前置过滤处理,比如请求的校验等。在完成pre类型的过滤处理之后,请求进入第二个阶段:route路由请求转发阶段。
- 在route路由请求转发阶段,请求将被route类型的过滤器处理,route类型的过滤器将外部请求转发到上游的服务。当服务实例的结果返回之后,route阶段完成,请求进入第三个阶段:post处理阶段。
- 在post处理阶段,请求将被post类型的过滤器处理,post类型的过滤器在处理的时候不仅可以获取请求信息,还能获取服务实例的返回信息,所以post阶段可以对处理结果进行一些加工或转换等。
- 还有一个特殊的阶段error,在该阶段请求将被error类型的过滤器处理,在上述3个阶段发生异常时才会触发,但是error过滤器也能将最终结果返回给请求客户端。
7.Zuul的请求处理流程如图所示。
- Zuul提供了一个动态读取、编译和运行过滤器的框架。过滤器不直接相互通信,而是通过RequestContext共享状态,RequestContext(请求上下文)实例对每个请求都是唯一的。
5. Hystrix和Ribbon支持
- spring-cloud-starter-zuul 依赖本身就包含了spring-cloud-starter-hystrix 和 spring-cloud-starter-ribbon 模块的依赖,所以Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。
- 但是需要注意,当使用path与url的映射关系来配置路由规则的时候,对于路由转发的请求不会采用 HystrixCommand 来包装,所以这类路由请求没有线程隔离和断路器的保护,并且也不会有负载均衡的能力。因此,我们在使用 Zuul 的时候尽量使用 path 和serviceId 的组合来进行配置,这样不仅可以保证 API 网关的健壮和稳定,也能用到 Ribbon 的客户端负载均衡功能。