博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Cloud学习笔记18——API 网关(Zuul)
阅读量:3941 次
发布时间:2019-05-24

本文共 9376 字,大约阅读时间需要 31 分钟。

目前的架构(使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过RibbonFeign实现服务的消费以及均衡负载;通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。)还存在一些不足

  • 首先,破坏了服务无状态特点。为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。
  • 其次,无法直接复用既有接口。当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。

为了解决上面这些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器,它就是服务网关。

API网关是一个更为智能的应用服务器,它的定义类似于面向对象设计模式中的Facade模式,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由负载均衡校验过滤等功能之外,还需要更多能力,比如与服务治理框架的结合请求转发时的熔断机制服务的聚合等一系列高级功能。

API 网关是一套主要用于统一API入口的应用组件,可以管理所有的API,形成一个API的入口。

API网关的意义

  • 集合多个API
  • 统一API入口

在这里插入图片描述

API网关服务对微服务架构的重要性

  • 它作为系统的统一入口,屏蔽了系统内部各个微服务的细节。
  • 它可以与服务治理框架结合,实现自动化的服务实例维护以及负载均衡的路由转发。
  • 它可以实现接口权限校验与微服务业务逻辑的解耦。
  • 通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。

API网关所带来的好处

  • 避免将内部信息泄露给外部
  • 为微服务添加额外的安全层
  • 支持混合通信协议
  • 降低构建微服务的复杂性
  • 微服务模拟与虚拟化

API网关的弊端

  • 在架构上需要额外考虑更多编排与管理
  • 路由逻辑配置要进行统一的管理
  • 可能引发单点故障

常见API网关的实现方式

NGINX

NGINX作为API网关:

在这里插入图片描述

Spring Cloud Zuul

Spring Cloud中提供了基于Netflix Zuul实现的API网关组件——Spring Cloud Zuul

Spring Cloud Zuul提供了认证、鉴权、限流、动态路由、监控、弹性、安全、负载均衡、协助单点压测、静态响应等边缘服务的框架。

首先,对于路由规则与服务实例的维护问题。Spring Cloud Zuul通过与Spring Cloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。这样的设计非常巧妙地将服务治理体系中维护的实例信息利用起来,使得维护服务实例的工作交给了服务治理框架自动完成,不再需要人工介入。而对于路由规则的维护,Zuul默认会将通过以服务名作为ContextPath的方式来创建路由映射,大部分情况下,这样的默认设置已经可以实现我们大部分的路由需求,除了一些特殊情况(比如兼容一些老的URL)还需要做一些特别的配置。但是相比于之前架构下的运维工作量,通过引入Spring Cloud Zuul实现API网关后,已经能够大大减少了。

其次,对于类似签名校验、登录校验在微服务架构中的冗余问题。理论上来说,这些校验逻辑在本质上与微服务应用自身的业务并没有多大的关系,所以它们完全可以独立成一个单独的服务存在,只是它们被剥离和独立出来之后,并不是给各个微服务调用,而是在API网关服务上进行统一调用来对微服务接口做前置过滤,以实现对微服务接口的拦截和校验。Spring Cloud Zuul提供了一套过滤器机制,它可以很好地支持这样的任务。开发者可以通过使用Zuul来创建各种校验过滤器,然后指定哪些规则的请求需要执行校验逻辑,只有通过校验的才会被路由到具体的微服务接口,不然就返回错误提示。通过这样的改造,各个业务层的微服务应用就不再需要非业务性质的校验逻辑了,这使得我们的微服务应用可以更专注于业务逻辑的开发,同时微服务的自动化测试也变得更容易实现。

微服务架构虽然可以将我们的开发单元拆分得更为细致,有效降低了开发难度,但是它所引出的各种问题如果处理不当会成为实施过程中的不稳定因素,甚至掩盖掉原本实施微服务带来的优势。所以,在微服务架构的实施方案中,API网关服务的使用几乎成为了必然的选择。

路由详解

传统路由配置

所谓的传统路由配置方式就是在不依赖于服务发现机制的情况下,通过在配置文件中具体指定每个路由表达式与服务实例的映射关系来实现API网关对外部请求的路由。

没有Eureka等服务治理框架的帮助,我们需要根据服务实例的数量采用不同方式的配置来实现路由规则。

  • 单实例配置:通过zuul.routes.<route>.pathzuul.routes.<route>.url参数对的方式进行配置,比如:
zuul.routes.user-service.path=/user-service/**zuul.routes.user-service.url=http://localhost:8080/

该配置实现了对符合/user-service/**规则的请求路径转发到http://localhost:8080/地址的路由规则。比如,当有一个请求http://localhost:5555/user-service/hello被发送到API网关上,由于/user-service/hello能够被上述配置的path规则匹配,所以API网关转发请求到http://localhost:8080/hello地址。

  • 多实例配置:通过zuul.routes.<route>.pathzuul.routes.<route>.serviceId参数对的方式进行配置,比如:
zuul.routes.user-service.path=/user-service/**zuul.routes.user-service.serviceId=user-service# 由于zuul.routes.
.serviceId指定的是服务名称,默认情况下Ribbon会根据服务发现机制来获取配置服务名对应的实例清单。# 但是,该示例并没有整合类似Eureka之类的服务治理框架,所以需要将该参数设置为false,否则配置的serviceId获取不到对应实例的清单ribbon.eureka.enabled=false# 该参数内容与zuul.routes.
.serviceId的配置相对应,开头的user-service对应了serviceId的值,# 这两个参数的配置相当于在该应用内部手工维护了服务与实例的对应关系user-service.ribbon.listOfServers=http://localhost:8080/,http://localhost:8081/

该配置实现了对符合/user-service/**规则的请求路径转发到http://localhost:8080/http://localhost:8081/两个实例地址的路由规则。它的配置方式与服务路由的配置方式一样,都采用了zuul.routes.<route>.pathzuul.routes.<route>.serviceId参数对的映射方式,只是这里的serviceId是由用户手工命名的服务名称,配合service.ribbon.listOfServers参数实现服务与实例的维护。由于存在多个实例,API网关在进行路由转发时需要实现负载均衡策略,于是这里还需要Spring Cloud Ribbon的配合。由于在Spring Cloud Zuul中自带了对Ribbon的依赖,所以我们只需做一些配置即可,比如上面示例中关于Ribbon的各个配置。

不论是单实例还是多实例的配置方式,我们都需要为每一对映射关系指定一个名称,也就是上面配置中的<route>,每一个<route>对应了一条路由规则。每条路由规则都需要通过path属性来定义一个用来匹配客户端请求的路径表达式,并通过urlserviceId属性来指定请求表达式映射具体实例地址或服务名。

服务路由配置

Spring Cloud Zuul通过与Spring Cloud Eureka的整合,实现了对服务实例的自动化维护,所以在使用服务路由配置的时候,我们不需要像传统路由配置方式那样为serviceId指定具体的服务实例地址,只需要通过zuul.routes.<route>.pathzuul.routes.<route>.serviceId参数对的方式进行配置即可。

比如下面的示例,它实现了对符合/user-service/**规则的请求路径转发到名为user-service的服务实例上去的路由规则。其中<route>可以指定为任意的路由名称。

zuul.routes.user-service.path=/user-service/**zuul.routes.user-service.serviceId=user-service

对于面向服务的路由配置,除了使用pathserviceId映射的配置方式之外,还有一种更简洁的配置方式:zuul.routes.<serviceId>=<path>,其中<serviceId>用来指定路由的具体服务名,<path>用来配置匹配的请求表达式。比如下面的例子,它的路由规则等价于上面通过pathserviceId组合使用的配置方式。

zuul.routes.user-service=/user-service/**

传统路由的映射方式比较直观且容易理解,API网关直接根据请求的URL路径找到最匹配的path表达式,直接转发给该表达式对应的url或对应serviceId下配置的实例地址,以实现外部请求的路由。

Zuul巧妙地整合了Eureka来实现面向服务的路由。实际上,我们可以直接将API网关也看作Eureka服务治理下的一个普通微服务应用。它除了会将自己注册到Eureka服务注册中心上之外,也会从注册中心获取所有服务以及它们的实例清单。所以,在Eureka的帮助下,API网关服务本身就已经维护了系统中所有serviceId与实例地址的映射关系。当有外部请求到达API网关的时候,根据请求的URL路径找到最佳匹配的path规则,API网关就可以知道要将该请求路由到哪个具体的serviceId上去。由于在API网关中已经知道serviceId对应服务实例的地址清单,那么只需要通过Ribbon的负载均衡策略,直接在这些清单中选择一个具体的实例进行转发就能完成路由工作了。

spring-cloud-starter-zuul依赖自身就包含了对spring-cloud-starter-hystrixspring-cloud-starter-ribbon模块的依赖,所以Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。但是需要注意,当使用pathurl的映射关系来配置路由规则的时候,对于路由转发的请求不会采用HystrixCommand来包装,所以这类路由请求没有线程隔离和断路器的保护,并且也不会有负载均衡的能力。因此,我们在使用Zuul的时候尽量使用pathserviceId的组合来进行配置,这样不仅可以保证API网关的健壮和稳定,也能用到Ribbon的客户端负载均衡功能。

服务路由的默认规则

当我们为Spring Cloud Zuul构建的API网关服务引入Spring Cloud Eureka之后,它为Eureka中的每个服务都自动创建一个默认路由规则,这些默认规则的path会使用serviceId配置的服务名作为请求前缀。

由于默认情况下所有Eureka上的服务都会被Zuul自动地创建映射关系来进行路由,这会使得一些我们不希望对外开放的服务也可能被外部访问到。这个时候,我们可以使用zuul.ignored-services参数来设置一个服务名匹配表达式来定义不自动创建路由的规则。Zuul在自动创建服务路由的时候会根据该表达式来进行判断,如果服务名匹配表达式,那么Zuul将跳过该服务,不为其创建路由规则。比如,设置为zuul.ignored-services=*的时候,Zuul将对所有的服务都不自动创建路由规则。在这种情况下,我们就要在配置文件中逐个为需要路由的服务添加映射规则(可以使用pathserviceId组合的配置方式,也可使用更简洁的zuul.routes.<serviceId>=<path>配置方式),只有在配置文件中出现的映射规则会被创建路由,而从Eureka中获取的其他服务,Zuul将不会再为它们创建路由规则。

路径匹配

不论是使用传统路由的配置方式还是服务路由的配置方式,我们都需要为每个路由规则定义匹配表达式,也就是path参数。在Zuul中,路由匹配的路径表达式采用了Ant风格定义。

Ant风格的路径表达式使用起来非常简单,它一共有下面这三种通配符:

通配符 说明
? 匹配任意单个字符
* 匹配任意数量的字符
** 匹配任意数量的字符,支持多级目录

当存在多个匹配的路由规则时,匹配结果完全取决于路由规则的保存顺序。为了保证路由的优先顺序,我们需要使用YAML文件来配置,以实现有序的路由规则。

为了更细粒度和更为灵活地配置路由规则,Zuul还提供了一个忽略表达式参数zuul.ignored-patterns。该参数可以用来设置不希望被API网关进行路由的URL表达式。该参数在使用时需要注意它的范围并不是对某个路由,而是对所有路由。所以在设置的时候需要全面考虑URL规则,防止忽略了不该被忽略的URL路径。

为了方便全局地为路由规则增加前缀信息,Zuul提供了zuul.prefix参数来进行设置。比如,希望为网关上的路由规则都增加/api前缀,那么我们可以在配置文件中增加配置:zuul.prefix=/api。另外,对于代理前缀会默认从路径中移除,我们可以通过设置zuul.stripPrefix=false来关闭该移除代理前缀的动作,也可以通过zuul.routes.<route>.strip-prefix=true来对指定路由关闭移除代理前缀的动作。

Zuul实现的API网关路由功能中,还支持forward形式的服务端跳转配置。实现方式非常简单,只需通过使用pathurl的配置方式就能完成,通过url中使用forward来指定需要跳转的的服务器资源路径。

如下api-a路由使用了本地跳转,它实现了将符合/api-a/**规则的请求转发到API网关中以/local为前缀的请求上,由API网关进行本地处理。比如,当API网关接收到请求/api-a/hello,它符合/api-a/**的路由规则,所以该请求会被API网关转发到网关的/local/hello请求上进行本地处理。

zuul.routes.api-a.path=/api-a/**zuul.routes.api-a.url=forward:/local

要注意的是由于需要在API网关上实现本地跳转,所以相应的我们也需要为本地跳转实现对应的请求接口。按照上面的例子,在API网关上还需要增加一个/local/hello的接口实现才能让api-a路由规则生效,比如下面的实现。否则Zuul在进行forward转发的时候会因为找不到该请求而返回404错误。

@RestControllerpublic class HelloController{
@RequestMapping("/local/hello") public String hello(){
return "Hello World Local"; } }

Hystrix和Ribbon配置

我们在使用Zuul搭建API网关的时候,可以通过HystrixRibbon的参数来调整路由请求的各种超时时间等配置,比如下面这些参数的设置。

参数 说明
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 可以用来设置API网关中路由转发请求的HystrixCommand执行超时时间,单位为毫秒。当路由转发请求的命令执行时间超过该配置值之后,Hystrix会将该执行命令标记为TIMEOUT并抛出异常,Zuul会对该异常进行处理并返回JSON信息给外部调用方。
ribbon.ConnectTimeout 用来设置路由转发请求的时候,创建请求连接的超时时间。当ribbon.ConnectTimeout的配置值小于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds配置值的时候,若出现路由请求出现连接超时,会自动进行重试路由请求,如果重试依然失败,会返回JSON信息给外部调用方。如果ribbon.ConnectTimeout的配置值大于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds配置值的时候,当出现路由请求连接超时时,由于此时对于路由转发的请求命令已经超时,所以不会进行重试路由请求,而是直接按请求命令超时处理,返回TIMEOUT的错误信息。
ribbon.ReadTimeout 用来设置路由转发请求的超时时间。它的处理与ribbon.ConnectTimeout类似,只是它的超时是对请求连接建立之后的处理时间。当ribbon.ReadTimeout的配置值小于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds配置值的时候,若路由请求的处理时间超过该配置值且依赖服务的请求还未响应的时候,会自动进行重试路由请求。如果重试后依然没有获得请求响应,Zuul会返回NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED错误。如果ribbon.ReadTimeout的配置值大于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的配置值,若路由请求的处理时间超过该配置值且依赖服务的请求还未响应时,不会进行重试路由请求,而是直接按请求命令超时处理,返回TIMEOUT的错误信息。

在使用Zuul的服务路由时,如果路由转发请求发生超时(连接超时或处理超时),只要超时时间的设置小于Hystrix的命令超时时间,那么他就会自动发起重试。但是在有些情况下,我们可能需要关闭该重试机制,那么可以通过下面的两个参数来进行设置:

# 全局关闭重试机制zuul.retryable=false# 指定路由关闭重试机制zuul,routes.
.retryable=false

过滤器

路由功能在真正运行时,它的路由映射和请求转发都是由几个不同的过滤器完成的。其中,路由映射主要通过pre类型的过滤器完成,它将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址;而请求转发的部分则是由route类型的过滤器来完成,对pre类型过滤器获得的路由地址进行转发。所以,过滤器可以说是Zuul实现API网关功能最为核心的部件,每一个进入ZuulHTTP请求都会进过一系列的过滤器处理链得到请求响应并返回给客户端。

Spring Cloud Zuul中实现的过滤器必须包含4个基本特征:过滤类型、执行顺序、执行条件、具体操作,实际上就是ZuulFilter接口中定义的4个抽象方法:

//需要返回一个字符串来代表过滤器的类型,而这个类型就是在HTTP请求过程中定义的各个阶段//Zuul中默认定义了4种不同生命周期的过滤器类型,如下所示://1. pre:可以在请求被路由之前调用//2. routing:在路由请求时被调用//3. post:在routing和error过滤器之后被调用//4. error:处理请求时发生错误时被调用String filterType();//通过int值来定义过滤器的执行顺序,数值越小优先级越高int filterOrder();//返回一个boolean值来判断该过滤器是否要执行//我们可以通过此方法来指定过滤器的有效范围boolean shouldFilter();//过滤器的具体逻辑//在该函数中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,//或是在请求路由返回结果之后,对处理结果做一些加工等Object run();

Zuul核心过滤器的顺序、名称、功能、类型如下:

在这里插入图片描述在这里插入图片描述

不论是核心过滤器还是自定义过滤器,只要在API网关应用中为它们创建了实例,那么默认情况下,它们都是启用状态的。如果有些过滤器我们不想使用了,可以禁用过滤器。

Zuul中特别提供了一个参数来禁用指定的过滤器,该参数的配置格式如下:

# 
代表过滤器的类名#
代表过滤器类型zuul.
.
.disable=true

该参数除了可以对自定义的过滤器进行禁用配置之外,很多时候可以用它来禁用Spring Cloud Zuul中默认定义的核心过滤器。这样我们就可以抛开Spring Cloud Zuul自带的那套核心过滤器,实现一套更符合我们实际需求的处理机制。

Kong

专注于提供微服务API网关的管理平台,底层实现技术基于NGINX,在NGINX基础之上提供了一些更加简单的配置方式,也提供了很多插件,如:验证、日志、调用频率限制。

在这里插入图片描述

转载地址:http://rgiwi.baihongyu.com/

你可能感兴趣的文章
Embedded Software Design Engineer
查看>>
MTK网络通信例程
查看>>
手机设计与制造全过程
查看>>
对MTK按键事件的简单分析
查看>>
DM模板使用心得
查看>>
进入和退出屏幕模板程序
查看>>
FileManager模块分析
查看>>
嵌入式及手机开发
查看>>
C语言嵌入式系统编程修炼之道
查看>>
MTK定时器消息机制分析
查看>>
MTK芯片
查看>>
SIM卡与USIM
查看>>
拍照手机名词术语小常识
查看>>
手机设计与制造全过程
查看>>
手机研发流程
查看>>
android studio drawable目录的分辨率
查看>>
j2me MIDP2.0已移植到 MT6225 GEMINI 0812 版本上
查看>>
MIDP2.1规范的新特性
查看>>
J2ME开发
查看>>
Java ME平台
查看>>