Spring Cloud第十四篇 | Api网关Zuul

本文是Spring Cloud专栏的第十四篇文章,了解前十三篇文章内容有助于更好的理解本文:html

  1. Spring Cloud第一篇 | Spring Cloud前言及其经常使用组件介绍概览git

  2. Spring Cloud第二篇 | 使用并认识Eureka注册中心github

  3. Spring Cloud第三篇 | 搭建高可用Eureka注册中心web

  4. Spring Cloud第四篇 | 客户端负载均衡Ribbon正则表达式

  5. Spring Cloud第五篇 | 服务熔断Hystrixspring

  6. Spring Cloud第六篇 | Hystrix仪表盘监控Hystrix Dashboardbootstrap

  7. Spring Cloud第七篇 | 声明式服务调用Feignapi

  8. Spring Cloud第八篇 | Hystrix集群监控Turbin缓存

  9. Spring Cloud第九篇 | 分布式服务跟踪Sleuth安全

  10. Spring Cloud第十篇 | 分布式配置中心Config

  11. Spring Cloud第十一篇 | 分布式配置中心高可用

  12. Spring Cloud第十二篇 | 消息总线Bus

  13. Spring Cloud第十三篇 | Spring Boot Admin服务监控

1、网关分类

开放Api

    开放api(openApi) 企业须要将自身数据、能力等做为开发平台向外开放,一般会以rest的方式向外提供,最好的例子就是淘宝开放平台、腾讯公司的QQ开发平台、微信开放平台。 Open API开放平台必然涉及到客户应用的接入、API权限的管理、调用次数管理等,必然会有一个统一的入口进行管理,这正是API网关能够发挥做用的时候。

微服务网关

    微服务的概念最先在2012年提出,在Martin Fowler的大力推广下,微服务在2014年后获得了大力发展。 在微服务架构中,有一个组件能够说是必不可少的,那就是微服务网关,微服务网关处理了负载均衡,缓存,路由,访问控制,服务代理,监控,日志等。API网关在微服务架构中正是以微服务网关的身份存在。

API服务管理平台

    上述的微服务架构对企业来讲有可能实施上是困难的,企业有不少遗留系统,要所有抽取为微服务器改动太大,对企业来讲成本过高。可是因为不一样系统间存在大量的API服务互相调用,所以须要对系统间服务调用进行管理,清晰地看到各系统调用关系,对系统间调用进行监控等。 API网关能够解决这些问题,咱们能够认为若是没有大规模的实施微服务架构,那么对企业来讲微服务网关就是企业的API服务管理平台。

2、网关设计

开放API接口

    一、对于OpenAPI使用的API网关来讲,通常合做伙伴要以应用的形式接入到OpenAPI平台,合做伙伴须要到 OpenAPI平台申请应用。所以在OpenAPI网关以外,须要有一个面向合做伙伴的使用的平台用于合做伙伴,这就要求OpenAPI网关须要提供API给这个用户平台进行访问。以下架构:

    固然若是是在简单的场景下,可能并不须要提供一个面向合做伙伴的门户,只须要由公司的运营人员直接添加合做伙伴应用id/密钥等,这种状况下也就不须要合做伙伴门户子系统。

内网API接口

    二、对于内网的API网关,在起到的做用上来讲能够认为是微服务网关,也能够认为是内网的API服务治理平台。当企业将全部的应用使用微服务的架构管理起来,那么API网关就起到了微服务网关的做用。而当企业只是将系统与系统之间的调用使用rest api的方式进行访问时使用API网关对调用进行管理,那么API网关起到的就是API服务治理的做用。架构参考以下:

    三、对于公司内部公网应用(如APP、公司的网站),若是管理上比较细致,在架构上是可能由独立的API网关来处理这部份内部公网应用,若是想比较简单的处理,也能够是使用面向合做伙伴的API网关。若是使用独立的API网关,有如下的好处:

面向合做伙伴和面向公司主体业务的优先级不同,不一样的API网关能够作到业务影响的隔离。

    内部API使用的管理流程和面向合做伙伴的管理流程可能不同。

    内部的API在功能扩展等方面的需求通常会大于OpenAPI对于功能的要求。

    基于以上的分析,若是公司有能力,那么仍是建议分开使用合做伙伴OPEN API网关和内部公网应用网关。

3、网关框架

  • Tyk:Tyk是一个开放源码的API网关,它是快速、可扩展和现代的。Tyk提供了一个API管理平台,其中包括API网关、API分析、开发人员门户和API管理面板。Try 是一个基于Go实现的网关服务。https://tyk.io

  • Kong:Kong是一个可扩展的开放源码API Layer(也称为API网关或API中间件)。Kong 在任何RESTful API的前面运行,经过插件扩展,它提供了超越核心平台的额外功能和服务,是基于Nginx+Lua进行二次开发的方案。https://konghq.com

  • Orange:Orange和Kong相似也是基于OpenResty的一个API网关程序,是由国人开发的。 http://orange.sumory.com

  • Netflix Zuul:Zuul是Netflix公司的开源项目,提供动态路由、监视、弹性、安全性等功能的边缘服务。Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器,Spring Cloud在Netflix项目中也已经集成了Zuul。https://github.com/Netflix/zuul

  • GateWay:GateWay是Spring Cloud的一个子项目,构建于Spring5+,基于Spring Boot 2.x 响应式的、非阻塞式的 API。https://spring.io/projects/spring-cloud-gateway

4、网关做用

    网关的做用,能够实现负载均衡、路由转发、日志、权限控制、监控等。

5、网关与过滤器区别

    网关是拦截全部服务器请求进行控制

    过滤器拦截某单个服务器请求进行控制

6、Nginx与Zuul区别

    Nginx是采用服务器负载均衡进行转发

    Zuul依赖Ribbon和Eureka实现本地负载均衡转发
    相对来讲Nginx功能比Zuul功能更增强大,可以整合其余语言好比Lua脚本实现强大的功能,同时Nginx能够更好的抗高并发,Zuul网关适用于请求过滤和拦截等。

7、网关

Zuul是Spring Cloud推荐的一个组件:https://github.com/Netflix/zuul

一、使用Zuul实现反向代理

1-一、在springcloud-zuul模块中添加依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

1-二、application.yml配置文件内容以下:

spring: application: name: springcloud-zuul server: port: 9999 eureka: client: service-url: defaultZone: http://localhost:8700/eureka #客户端每隔30秒从Eureka服务上更新一次服务信息 registry-fetch-interval-seconds: 30 #须要将个人服务注册到eureka上 register-with-eureka: true #须要检索服务 fetch-registry: true #心跳检测检测与续约时间 instance: #告诉服务端,若是我10s以内没有给你发心跳,就表明我故障了,将我剔除掉,默认90s #Eureka服务端在收到最后一次心跳以后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待本身) lease-expiration-duration-in-seconds: 10 #每隔2s向服务端发送一次心跳,证实自已依然活着,默认30s #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端本身会按照该规则) lease-renewal-interval-in-seconds: 2 #以/api-a/ 开头的请求都转发给springcloud-service-consumer服务 #以/api-b/开头的请求都转发给springcloud-service-feign服务 zuul: routes: api-a: path: /api-a/** serviceId: springcloud-service-consumer api-b: path: /api-b/** serviceId: springcloud-service-feign

1-三、在主类上添加注解

@EnableZuulProxy //开启Zuul的支持
@EnableEurekaClient //开启Eureka客户端支持

完成上面的操做以后,咱们能够启动相应的服务,启动相应服务以下:

    而后访问springcloud-service-consumer服务中的接口:http://localhost:9999/api-a/consumer/hello,同理springcloud-service-feign的服务中接口也是这样 http://localhost:9999/api-b/feign/hello路径中的api-a,和api-b分别被路由到响应的服务上去,你也能够配置忽略api-a,api-b等等其余配置

二、Zuul对Ribbon和Hystrix的支持

    从依赖上能够看出来,Zuul自身会依赖Ribbon和Hystrix的依赖,因此Zuul自己就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能,可是仅限于咱们path和serviceId的组合使用

zuul.routes.<route>.path zuul.routes.<route>.serviceId

不支持path和url的组合使用

zuul.routes.<route>.path zuul.routes.<route>.url

三、使用Zuul过滤器

    微服务数量多的状况下,咱们为每一个服务都加上安全校验和权限控制,是很是麻烦的,这样的作法并不可取,它会增长后期系统的维护难度,由于每个系统中的各类校验逻辑不少状况下大体相同或者相似,而后这些非业务的逻辑代码分散到各个服务中,产生的冗余代码是咱们不想看到的,因此一般的作法是经过网关服务来完成这些非业务性质的校验。

3-一、Filter的生命周期

    Filter的生命周期有4个,分别是 “PRE”、“ROUTING”、“POST” 和“ERROR”,整个生命周期能够用下图来表示

Zuul大部分功能都是经过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。

  • PRE:这种过滤器在请求被路由以前调用。咱们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

  • ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务。

  • POST:这种过滤器在路由到微服务之后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

  • ERROR:在其余阶段发生错误时执行该过滤器。

在Zuul网关中,咱们须要自定义一个类来继承ZuulFilter抽象类并实现4个相应的抽象方法便可。

简单实例,验证请求有没有userToken参数:

@Component public class TokenFilter  extends ZuulFilter { // 过滤器类型 pre 表示在 请求以前进行拦截
 @Override public String filterType() { return "pre"; } ​ // 过滤器的执行顺序。当请求在一个阶段的时候存在多个多个过滤器时,须要根据该方法的返回值依次执行
 @Override public int filterOrder() { return 0; } ​ // 判断过滤器是否生效
 @Override public boolean shouldFilter() { return true; } ​ @Override public Object run() throws ZuulException { // 获取上下文
        RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); String userToken = request.getParameter("userToken"); if (StringUtils.isEmpty(userToken)) { //setSendZuulResponse(false)令zuul过滤该请求,不进行路由
            currentContext.setSendZuulResponse(false); //设置返回的错误码
            currentContext.setResponseStatusCode(401); currentContext.setResponseBody("userToken is null"); return null; } // 不然正常执行业务逻辑.....
        return null; } }

3-二、重启springcloud-zuul服务,访问:

    http://localhost:9999/api-b/feign/hello返回:userToken is null

    http://localhost:9999/api-b/feign/hello?userToken=""返回:spring cloud provider-01 hello world

在上面实现的过滤器代码中,咱们经过继承ZuulFilter抽象类并重写了下面的四个方法来实现自定义的过滤器。这四个方法分别定义了:

  • filterType():过滤器的类型,它决定过滤器在请求的哪一个生命周期中执行。这里定义为pre,表明会在请求被路由以前执行。

  • filterOrder():过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,须要根据该方法返回的值来依次执行。经过数字指定,数字越大,优先级越低。

  • shouldFilter():判断该过滤器是否须要被执行。这里咱们直接返回了true,所以该过滤器对全部请求都会生效。实际运用中咱们能够利用该函数来指定过滤器的有效范围。

  • run():过滤器的具体逻辑。这里咱们经过currentContext.setSendZuulResponse(false)令 Zuul 过滤该请求,不对其进行路由,而后经过currentContext.setResponseStatusCode(401)设置了其返回的错误码,固然咱们也能够进一步优化咱们的返回,好比,经过currentContext.setResponseBody(body)对返回 body 内容进行编辑等。

四、Zuul路由规则

4-一、默认路由规则

    因为Zuul引入Eureka网关的时候,会为每个服务创键一个默认的路由规则,默认状况下实例名做为请求的前缀,这样不对外开放的服务也会被外界访问到,咱们能够控制一下为哪些服务创键路由规则。

zuul.ignored-services: *

*表示不为全部的服务建立默认的路由规则,则须要咱们本身配置路由规则。

4-二、自定义路由规则

    为了兼容客户端不一样版本,有时候须要咱们为一组互相配合的微服务定义一个版本标识来方便管理,它们的版本关系,根据这个标识咱们很容易的知道这些服务须要一块儿启动并配合使用,好比咱们的服务都采用以版本这样的命名方式,例如:consumer-v1,consumer-v2分版本访问服务的话,咱们可使用自定义路由规则,注入PatternServiceRouteMapper对象便可自动的构建相似 /v1/consumer/** 的路由规则

@Bean public PatternServiceRouteMapper patternServiceRouteMapper(){ return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }

    PatternServiceRouteMapper对象能够经过正则表达式来自定义服务与路由映射的生成关系。其中构造函数的

第一个参数: 用来匹配服务名称是否符合该自定义规则的正则表达式,

第二个参数用来定义根据服务名中定义的内容转换出的路径表达式规则。

    当开发者在API网关中定义了PatternServiceRouteMapper实现以后,只要符合第一个参数定义规则的服务名,都会优先使用该实现构建出的路径表达式,若是没有匹配上的服务则仍是会使用默认的路由映射规则,即采用完整服务名做为前缀的路径表达式。

五、路径匹配

在上面案例上咱们看到了使用通配符做为匹配路径,一共有三种通配符,以下:

通配符 通配符含义
? 匹配单个字符,如:/consumer/a,/consumer/b
* 匹配任意数量的字符,如:/consumer/abc,/consumer/def
** 匹配任意数量的字符,支持多级路径,如:/consumer/abc/def,/consumer/ghi/gkl

    咱们在使用的时候,好比咱们的consumer服务路由路径为:/consumer/**,因为发展还须要再次拆分出另一个consumer-ext服务,路由规则为/consumer/ext/**,因为**匹配多级目录这时候咱们须要区别这些服务路径,properties配置没法保证配置的加载顺序,但在YML配置文件中咱们可使用/consumer/ext/**配置在/consumer/**前面则能够保证consumer-ext服务的正常路由

六、忽略表达式

zuul.ignored-patterns: /**/hello/**

该配置表示忽略路由路径包含hello的路径

七、路由前缀

zuul.prefix: /api zuul.strip-prefix: true

    prefix:前缀,当请求匹配前缀时会进行代理

    strip-prefix:代理前缀默认(true)会从请求路径中移除,能够设置为false关闭移除代理前缀动做,也能够经过zuul.routes.<route>.strip-prefix=false来对指定路由关闭移除代理前缀动做。

    可是在《Spring Cloud微服务实战》中指出Brixton.SR7和Camden.SR3中有Bug,该案例版本为Finchley.SR4未发现Bug

八、Zuul安全与Header

    敏感的Header设置,通常来讲同一个系统中的服务之间共享Header,不过Zuul防止一些敏感的Header外泄,防止它们被传递到下游服务器,若是咱们须要传递Cookie,Set-Cookie,Authorization 这些信息,咱们能够这样作

作法一全局设置:将 zuul.sensitive-headers 的值设置为空
作法二指定路由设置: zuul.routes.<route>.sensitiveHeaders: '' zuul.routes.<route>.custom-sensitive-headers: true

九、忽略 Header

    可用 zuul.ignoredHeaders 属性丢弃一些 Header,这样设置后 Cookie 将不会传播到其它微服务中

zuul.ignored-headers: Cookie

十、禁用指定的 Filter

zuul.<SimpleClassName>.<filterType>.disable=true

具体详细配置参考Spring官网:https://cloud.spring.io/spring-cloud-static/Finchley.SR4/single/spring-cloud.html#_router_and_filter_zuul

8、Zuul的动态路由

    Zuul做为服务的统一入口,传统方式将路由规则配置在配置文件中,若是路由规则发生了改变,须要重启服务器,这就会对外界中止服务。这时候咱们结合SpringCloud Config分布式配置中心《Spring Cloud第十篇 | 分布式配置中心Config》实现动态路由规则,此处再也不演示消息总线Bus《Spring Cloud第十二篇 | 消息总线Bus》的使用。

一、配置中心服务端

1-一、为了保证之前配置中心服务端模块(springcloud-config-server)的整洁性,此处新建一个配置中心服务端模块命名为(springcloud-zuul-config-server)
1-二、添加依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

1-三、配置application.yml文件

spring: application: name: springcloud-zuul-config-server cloud: config: server: git: #配置git仓库地址 uri: https://gitee.com/coding-farmer/config-center #配置仓库路径 search-paths: "{profile}" #访问git仓库的用户名 username: #访问git仓库的密码 password: #配置中心经过git从远程git库,有时本地的拷贝被污染, #这时配置中心没法从远程库更新本地配置,设置force-pull=true,则强制从远程库中更新本地库 force-pull: true #默认从git仓库克隆下载的在C:/Users/<当前用户>/AppData/Local/Temp #basedir: server: port: 8888 eureka: client: service-url: defaultZone: http://localhost:8700/eureka #客户端每隔30秒从Eureka服务上更新一次服务信息 registry-fetch-interval-seconds: 30 #须要将个人服务注册到eureka上 register-with-eureka: true #须要检索服务 fetch-registry: true #心跳检测检测与续约时间 instance: #告诉服务端,若是我10s以内没有给你发心跳,就表明我故障了,将我剔除掉,默认90s #Eureka服务端在收到最后一次心跳以后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待本身) lease-expiration-duration-in-seconds: 10 #每隔2s向服务端发送一次心跳,证实自已依然活着,默认30s #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端本身会按照该规则) lease-renewal-interval-in-seconds: 2 # 启用ip配置 这样在注册中心列表中看见的是以ip+端口呈现的 prefer-ip-address: true # 实例名称 最后呈现地址:ip:2002 instance-id: ${spring.cloud.client.ip-address}:${server.port}

1-四、在启动类上添加注解

@EnableConfigServer @EnableEurekaClient

到此配置中心服务端搭建完成

二、修改zuul服务模块

2-一、添加配置中心客户端依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-client</artifactId>
</dependency>

2-二、将application.yml文件更名为application-bak.yml(为了application.yml文件),配置bootstrap.yml文件

server: port: 9999 spring: application: name: springcloud-zuul cloud: config: #uri则表示配置中心的地址 #uri: http://localhost:8888 #注:config 客户端在没有 spring.cloud.config.name属性的时候,服务端{application} 获取的是客户端 #spring.application.name的值,不然,获取的是 spring.cloud.config.name的值。 #1)、当没有spring.cloud.config.name时,客户端获取的是spring.application.name 所对应的git库中的文件,而且只能 #获取一个文件, #2)、当一个项目中有需求要获取多个文件时,就须要用到spring.cloud.config.name这个属性,以逗号分割 name: configzuul profile: dev #label对应了label部分 label: master # username: # password: discovery: #表示开启经过服务名来访问config-server enabled: true #则表示config-server的服务名 service-id: springcloud-zuul-config-server #失败快速响应 fail-fast: true retry: #配置重试次数,默认为6 max-attempts: 6 #初始重试间隔时间,默认1000ms initial-interval: 1000 #间隔乘数,默认1.1 multiplier: 1.1 #最大间隔时间,默认2000ms max-interval: 2000 eureka: client: service-url: defaultZone: http://localhost:8700/eureka #客户端每隔30秒从Eureka服务上更新一次服务信息 registry-fetch-interval-seconds: 30 #须要将个人服务注册到eureka上 register-with-eureka: true #须要检索服务 fetch-registry: true #心跳检测检测与续约时间 instance: #告诉服务端,若是我10s以内没有给你发心跳,就表明我故障了,将我剔除掉,默认90s #Eureka服务端在收到最后一次心跳以后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待本身) lease-expiration-duration-in-seconds: 10 #每隔2s向服务端发送一次心跳,证实自已依然活着,默认30s #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端本身会按照该规则) lease-renewal-interval-in-seconds: 2 # 启用ip配置 这样在注册中心列表中看见的是以ip+端口呈现的 prefer-ip-address: true # 实例名称 最后呈现地址:ip:2002 instance-id: ${spring.cloud.client.ip-address}:${server.port} management: endpoints: web: exposure: include: ["info","health","refresh"]

2-三、配置刷新类

@Configuration public class Config { ​ @RefreshScope @ConfigurationProperties("zuul") public ZuulProperties zuulProperties() { return new ZuulProperties(); } }

三、在代码仓库添加配configzuul-dev.yml置文件

四、启动相关服务

    Eureka服务(springcloud-eureka-server),zuul的配置中心服务(springcloud-zuul-config-server)、提供者服务(springcloud-service-provider)、消费者服务(springcloud-service-consumer)、zuul服务(springcloud-zuul)

    访问消费者服务接口:http://localhost:9999/api-a/consumer/hello?userToken=""

    访问配置中心服务端:http://localhost:8888/configzuul-dev.yml,查看配置结果如图:

    修改仓库config-center的configzuul-dev.yml配置为api-c接着在访问配置中心仓库配置结果,http://localhost:8888/configzuul-dev.yml,注意api-a的key不要修改

    而后发送post请求zuul的refresh端点进行配置刷新http://localhost:9999/actuator/refresh

    而后你会发现http://localhost:9999/api-a/consumer/hello?userToken=’‘路径访问不通了,访问http://localhost:9999/api-c/consumer/hello?userToken=’'结果如图,显示页面为

    到此zuul的动态刷新完成,此处动态刷新就是使用了配置中心的功能,不了解的能够参考《Spring Cloud第十篇 | 分布式配置中心Config

 

详细参考案例源码:https://gitee.com/coding-farmer/springcloud-learn

 

原文出处:https://www.cnblogs.com/coding-farmer/p/12329115.html

相关文章
相关标签/搜索