微服务之ZuulFilter
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微服务之ZuulFilter相关的知识,希望对你有一定的参考价值。
参考技术A 我们在写微服务的时候每一个请求都是走的微服务的网关,再由网关找到对应的服务,然后传递参数请求数据返回数据,我们通常会在网关对于我们的请求做一些操作,包括请求前以及响应前,这里主要说一下微服务中常用的ZuulFilter过滤器我们手动去继承ZuulFilter这个类,然后需要我们重写几个方法
这个方法的作用是用来标识这个过滤器在哪个阶段发挥作用,在ZuulFilter源码中是这样描述的
我们这里常用的就是pre,route,post,error,当然还有"static"还有一些自定义的,
这是最开始生效的,这是服务器接收到请求,还没有到达网关的时候生效,我们常用于过滤请求,身份验证什么的,在集群中选择具体的微服务,记录日志之类的信息。
当我们把fifter type设置为route的时候就表示我们的过滤器的生效时间是在我们网关接收到请求但是还没有转发到具体的微服务的时候过滤器生效,用来构建新的请求,比如我们将一些数值信息从token中解析出来存放到我们的请求中,再Apache HttpClient或Netfilx Ribbon请求具体问服务。
这是我们的具体微服务将请求的数据存返回的时候经过的过滤器,比如我们将响应的参数进行加密。
当我们的服务器在接收到请求报错的情况下
这个方法主要是我们在注册过滤器的时候形成过滤链的时候排序使用,这是设置的过滤器的使用顺序
这个方法的功能是用来过滤请求是否需要走下面的具体的run方法,主要是为了区分不同过滤器针对不同的url
这是我们的过滤器的方法主题在这里写我们主要的方法,(解密,token验证,权限验证,重构请求或者响应,解密)
以上就是我们的ZuulFilter的主要的几个方法的讲解,当然别忘了一定要将过滤器放入到spring容器中。
RequestContext context = RequestContext.getCurrentContext():使用这个获取上下文内容主体,
context.setSendZuulResponse(false):用这个方法是设置不在走接下来的过滤器和微服务,直接返回
Chris Richardson微服务翻译:构建微服务之微服务架构的进程通讯
Chris Richardson 微服务系列翻译全7篇链接:
- 微服务介绍
- 构建微服务之使用API网关
- 构建微服务之微服务架构的进程通讯(本文)
- 微服务架构中的服务发现
- 微服务之事件驱动的数据管理
- 微服务部署
- 重构单体应用为微服务
原文链接:Building Microservices: Inter-Process Communication in a Microservices Architecture
简介
在单体应用中,模块间使用编程语言级别的方法或函数彼此调用。而基于微服务架构的本质是是运行在多台机器上的分布式应用,每个服务都是一个进程。如下图所示,微服务之间必须使用进程间通信(IPC)的机制实现交互:
稍后我们将讨论 IPC 技术,先看下设计相关的问题。
交互模式
当为某个服务选择 IPC 机制时,首先要考虑服务间如何交互。client 和 server 端有很多交互的方式,可以按两个维度分类:
第一个维度是一对一还是一对多:
- 一对一:每个 client 请求只会被一个 server 处理
- 一对多:每个 client 请求会被多个 server 处理
第二个维度是交互是同步还是异步:
- 同步模式:client 期望来自 server 的及时响应,甚至可能由于等待而阻塞
- 异步模式:client 等待响应时不会阻塞,不需要及时响应
下面表格展示了两种方式的不同:
一对一 | 一对多 | |
同步 | 请求/响应 | |
异步异步 | 通知 | 发布/订阅 |
请求/异步响应 | 发布/异步响应 |
下面有几种一对一的交互模式:
- 请求/响应:client 向 server 发送请求并等待响应,client 期望响应能及时到达。在一个基于线程的应用中,请求的线程可能在等待时阻塞线程的执行。
- 通知(单向请求):client 往 server 发送请求,但不期望响应。
- 请求/异步响应:client 往 server 发送请求,server 异步响应。client 不会阻塞,因为设计时就默认请求不会立即返回。
下面有几种一对多的交互模式:
- 发布/订阅模式:client 发布一个通知消息,消息会被 0 或多个感兴趣的服务消费。
- 发布/异步响应模式:client 发布一个请求消息,在一定时间内等待感兴趣服务的响应。
每个服务都是以上几种模式的组合,对某些服务来说,一个 IPC 机制就能满足了,另外一些服务可能需要多个 IPC 机制的组合。下图展示了用户叫车应用中,用户请求行程时,服务是如何交互的:
上图服务使用了通知、请求/响应、发布/订阅的方式。例如:乘客在移动端向『行程管理服务』发送接送需求的通知;『行程管理服务』使用 请求/响应 模式 调用『乘客服务』来验证乘客账号是否有效;然后『行程管理服务』创建行程并使用 发布/订阅 模式来通知其他服务(定位可用司机的『调度服务』等)。
我们讨论了交互风格,下面看下如何定义 API。
定义API
API 是服务端和客户端的契约。无论选择选择哪种 IPC 机制,都需要使用接口定义语言(IDL)来定义 服务的API。开发服务前,先定义服务接口,并与 client端开发者一起 review,后续再对 API 进行迭代。这样设计能帮助你构建更符合客户需求的服务。
文章后半段你会发现,API 的定义依赖选择的 IPC 机制。如果使用消息机制,API 则由消息频道和消息类型组成。如果使用 HTTP, API 则是由 URL 和 request/response 格式组成。后面我们将讨论 IDL 的细节。
API进化
服务的 API 不可避免的随着时间进化。单体应用中,可以直接修改 API 并更新所有的调用者。但在微服务应用中,即时 API 的所有调用者都在一个应用中,去更新其他服务也是很困难的,通常不能强制让所有 client 升级来保持和 server 端一致。此外,你可能还会增加部署新的服务版本,与老版本同时运行。了解处理这些问题的策略是非常重要的。
如何根据更改的大小来处理 API 呢?有的变化很小,通常可以与旧版本做到向后兼容,例如:为请求或响应添加了一个属性。对此,设计服务时考虑鲁棒性是很有必要的:使用旧版本 API 的 client 在新版本的 API 下能正常工作;server 为缺失的属性提供默认值;client 忽略响应中额外添加的属性。
有时候 API 不得不做一些大的、不兼容的变动,此时又不能强制让所有 client 立即升级,因此,旧版本 API 还需要运行一段时间。如果使用的是基于 HTTP 的 IPC,可以在 URL 里嵌入服务版本,每个服务实例可以同时处理多个版本。另一种方式也可以选择为每个版本单独部署。
处理局部故障
分布式系统普遍存在局部失败的问题,由于 client 和 server 是运行在独立的进程中,server 可能因为挂了或维护而暂时不可用,不能及时响应 client 的请求,或者因为过载而导致响应很慢。
以上篇文章提到的商品详情页场景为例,假设推荐服务没有响应,client 可能无限期的等待服务响应而导致阻塞,这不仅导致用户体验很糟糕,而且会占用线程等宝贵资源,就像下图所示,运行时线程耗尽,而无法响应任何请求:
为解决此类问题,设计时需要考虑局部故障的问题:
Netfilix 提供了较好的解决方案:
- 网络超时:等待响应时不设置无期限阻塞,而采用超时策略,保证资源不会无限被占用。
- 限制请求数量:为 client 对某个服务的请求设置访问上限,如果请求达到上限,则不再处理任何请求,做到快速失败。
- 熔断器模式:记录成功和失败的请求数量,如果失败率超过一个阀值,触发熔断器使得后面的请求立刻失败。如果大量请求失败,那这个服务可认为不可用,继续请求也没有意义。一段时间后,client 可以再次重试,如果成功,则关闭熔断器。
- 提供 fallback 机制:请求失败时提供 fallback,例如:返回缓存或一个默认值
Netflix Hystrix 是一个实现相关模式的开源库。如果使用 JVM,那么推荐使用 Hystrix。如果使用的非 JVM 环境,也可以使用类似的库。
IPC 技术
现在有不同的 IPC 技术可选择:基于 请求/响应 的同步通信模式,例如基于 HTTP 的 Rest 或 Thrift;也可以选择异步的、基于消息的通信模式,例如AMQP、STOMP。这些通信有着不同的消息格式,服务可以选择基于文本、方便阅读的 JSON 或 XML格式,或者效率更高的二进制格式(例如 Avro、Protocol Buffers)。
异步,基于消息的通信
使用消息模式时,进程间通过异步消息的方式来通信,client 发送消息来请求 server,如果期望 server 响应,则 server 会发送另外一条消息给 client。由于通信是异步的,client 不会因为等待响应而阻塞,同时 client 编程时也以服务不会立即响应来处理。
消息由消息头(元数据和发送者)和消息体组成,消息通过频道进行交换,任意数量的生产者都可以往频道里发送消息,同样,任意数量的消费者都可以从频道里消费消息。频道分为点对点、订阅/发布两种:
- 点对点模式:频道中的消息只会被交付给某个消费者,这种适用于前面提到的一对一的交互方式
- 订阅/发布模式:频道中的消息会被交付到所有感兴趣的消费者,这种适用于一对多的交互方式
下图展示了打车软件中如何使用 发布/订阅 模式:
行程管理服务向『订阅-发布』频道写入『创建行程』的消息,通知调度服务有新的行程请求。调度服务查找空闲的司机,并通过『发布-订阅』频道写入『推荐司机』的消息,通知其他服务。
有多种消息系统供我们选择,当然我们尽可能选择支持多种编程语言的。一些消息系统支持 AMQP和 STOMP 这样的标准协议,有的则支持专有的协议。开源的消息系统例如:RabbitMQ、Apacha Kafka、Apache ActiveMQ 和 NSQ。统一来看,他们都支持一些消息和频道,都致力于高可用、高性能和高可扩展性。
使用消息系统有很多优点:
- client 和 server 解耦,client 只需要将消息发送到合适的频道,完全不需要感知 server 的存在,因此不需要再去使用服务发现机制来确定服务实例的位置。
- 消息缓冲:在 HTTP 这样的请求/响应协议下,client 和 server 交互期间需要保证双方的可用性。然而在消息模式中,消息组件会将消息按照队列方式进行管理,直到消息被消费者消费。例如:即使订单系统很慢或不可用,在线商店仍旧可以接受客户的下单请求,只需要将下单消息放入队列即可。
- 灵活的 client-server 交互方式:消息支持前面提到的所有交互风格。
- 清晰的进程间通信:基于 RPC 的通信机制视图使调用远程服务像调用本地服务一样,然而,由于局部故障的可能,他们大不相同。消息机制使这些差异直观明显,开发者不会产生安全错觉。
当然,消息系统也有缺点:
- 额外的运维复杂度:消息系统组件的安装、部署、运维等工作,消息系统的高可用保障,否则会影响到系统的可用性。
- 实现 请求/响应 交互模式的复杂度:每条请求消息需要包含一个 回复渠道ID 和 关联ID,server 发送包含关联ID的响应消息到渠道中,client 使用关联ID 去匹配对应的响应。这种情况下,使用支持请求/响应的 IPC 机制会更容易些。
同步,请求/响应 IPC
使用同步、请求/响应的 IPC 时,client 请求 server 时有可能由于等待 server 响应而被阻塞。另外一些client 会使用异步、事件驱动的代码,例如封装好的 Future 或者 Rx Observable。这个模式最常见的协议是 Rest 和Thrift。
Rest
当前流行开发 RESTful 风格的 API。 Rest 是基于 HTTP 的 IPC 机制,其核心概念是使用 URL 来表示资源(用户或产品的一组业务对象)。例如:GET 请求会返回一个资源的信息,可能是 XML 文档 或 JSON 对象格式;POST 请求会创建新的资源;PUT 请求会更新资源。REST 之父 Roy Fielding 曾经说过:
REST provides a set of architectural constraints that, when applied as a whole, emphasizes scalability of component interactions, generality of interfaces, independent deployment of components, and intermediary components to reduce interaction latency, enforce security, and encapsulate legacy systems.
Rest 提供了一些列架构系统参数作为整体使用,强调组件交互的扩展性、接口的通用性、组件的独立部署、减少交互延迟的中间件,他强化安全,也能封装遗留系统。
下面展示打车软件使用 Rest 的场景:
Leonard Richardson 为 REST 定义了一个成熟度模型,分为如下四个层次:
- Level 0:web 服务使用 HTTP 作为传输方式,调用固定的 URL,每次请求指定方法和参数
- Level 1:引入了资源的概念,要执行对资源的操作,请求通过 POST,指定要执行的操作和参数
- Level 2:使用 HTTP 的语法来执行操作,例如:GET 表示获取,POST 表示创建,PUT 表示更新
- Level 3:API 定义按照 HATEOAS(Hypertext As The Engine Of Application State)设计原则,基本思想 GET 请求返回资源的一些对资源允许操作的链接。例如:client 使用 GET 订单资源中包含的链接取消某一订单。HATEOAS 的一个优点就是无需在 client 代码中写入硬链接的 URL。此外,返回的资源信息中包含了对资源允许操作的链接,client 无需再猜测当前资源下所能做哪些操作了
基于 HTTP 协议的优点:
- 简单,为大家所熟悉
- 可使用浏览器、postman,curl 之类的命令行测试 API
- 支持 请求/响应 模式的通信
- 不需要中间代理,减价系统架构
HTTP 不足之处:
- 只支持 请求/响应的交互
- client 和 server 之间没有消息缓冲机制,要求交互时双方必须同时运行
- client 需要知道每个 server实例 的url
Thrift
Apache Thrift 是 REST 的一个有趣的替代品,实现了跨语言的客户端和服务端RPC通信的框架,Thrift 提供了 C 语言风格的接口定义语言来定义 API,可以通过编译生成客户端Stub 和 服务端的骨架,可以生成多种语言的代码(包括 C++、Java、Python、PHP、Ruby、Erlang、Node.js)。
Thrift 接口通常包含一个或多个服务,服务定义与 Java 接口类似,是一组强类型方法的集合。Thrift 能返回值,也可以定义为单向通信。如果需要返回值就需要实现 请求/响应风格的交互,客户端等待响应时可以抛出异常;单向通信就是通知模式,服务端不需要返回响应。
Thrift 支持 JSON、二进制、压缩二进制等不同的消息格式。二进制解码比 JSON 更快,更为高效;压缩二进制比 JSON 空间利用率更高; JSON 则更易读。Thrift 也支持不同的通信协议:TCP 或 HTTP,TCP 比 HTTP 更加高效,而 HTTP 对防火墙、人及浏览器更加友好。
消息格式
选择一种支持多语言的消息格式非常重要,哪怕你只用一种语言实现微服务,谁又能保证以后不会使用新的语言呢?
目前有文本和二进制两种格式。文本格式包括 JSON 和 XML。这种格式优点不仅可读,而且是自描述的。JSON中,对象的属性是键值对的集合;XML中,属性表示为命名的元素和值。消费者能选择感兴趣的值而忽略其他部分,对格式的修改也能容易的向后兼容。
XML文档的结构是 XML Schema 定义的,随着时间的发展,开发者意识到 JSON 也需要一个类似的机制,方法一是使用 JSON Schema,要么独立使用,要么作为 Swagger 这类 IDL的一部分使用。
文本格式的一大缺点是消息会变的冗长,尤其是 XML:因为消息是自描述的,每条消息除了值之外还包括属性的名称。另一大缺点是解析文本的开销略大,此时可以考虑二进制格式。
二进制格式也很多,如果使用 Thrift,那么可以用二进制Thrift;如果使用其他消息格式,常用的还包括 Protocol Buffers 和 Apache Avro,两者都提供了 IDL 来定义消息结构。差异之处在于 Protocol Buffers 使用标记字段,而 Avro 消费者需要了解 Schema 来解析消息,使用 Protocol Buffers 时,API进化比 Avro 更容易。Martin Kleppmann 的 博客文章 对Thrift、Protocol Buffers 和 Avor 进行了详细的比较。
总结
微服务需要使用进程间消息通信机制来交互,设计服务的通信模式时,需要考虑一下几个问题:服务如何交互、如何定义 API、如何升级 API,如何处理局部故障。微服务架构有两种 IPC 机制可用:异步消息机制和同步请求/响应机制。下篇文章中,我们会讨论微服务架构中的服务发现问题。
以上是关于微服务之ZuulFilter的主要内容,如果未能解决你的问题,请参考以下文章