(无代码)微服务服务调用和Feign详解

文章目录

  • 微服务服务调用
    • 一,事件驱动(Event-Driven)方式
      • 事件通知(Event Notification)方式
      • 事件溯源(Event Sourcing)方式
    • 二,RPC方式:
    • 三,总结
  • Spring Cloud Feign概述与工作原理解读
    • 一,服务间调用的几种方式
    • 二,Feign 概述
    • 三,FeignClient注解剖析
      • @FeignClient 注解剖析
  • 参考资料

微服务服务调用

在微服务架构中,要调用很多服务才能完成一项功能。服务之间如何互相调用就变成微服务架构中的一个关键问题。服务调用有两种方式,一种是RPC方式,另一种是事件驱动(Event-driven)方式,也就是发消息方式。消息方式是松耦合方式,比紧耦合的RPC方式要优越,但RPC方式如果用在适合的场景也有它的一席之地.

一,事件驱动(Event-Driven)方式

Martin Fowler把事件驱动分成四种方式(What do you mean by “Event-Driven”),简化之后本质上只有两种方式。 一种就是我们熟悉的的事件通知(Event Notification),另一种是事件溯源(Event Sourcing)。事件通知就是微服务之间不直接调用,而是通过发消息来进行合作。事件溯源有点像记账,它把所有的事件都记录下来,作为永久存储层,再在它的基础之上构建应用程序。实际上从应用的角度来讲,它们并不应该分属一类,它们的用途完全不同。事件通知是微服务的调用(或集成)方式,应该和RPC分在一起。事件溯源是一种存储数据的方式,应该和数据库分在一起。

事件通知(Event Notification)方式

举一个购物的例子。用户选好商品之后进行“Checkout”,生成“Order”,然后需要“payment”,再从“Inventory”取货,最后由“Shipment”发货,它们每一个都是微服务。这个例子用RPC方式和事件通知方式都可以完成。当用RPC方式时,由“Order”服务调用其他几个服务来完成整个功能。用事件通知方式时,“Checkout”服务完成之后发送“Order Placed”消息,“Payment”服务收到消息,接收用户付款,发送“Payment received”消息。“Inventory”服务收到消息,从仓库里取货,并发送“Goods fetched”消息。“Shipment”服务得到消息,发送货物,并发送“Goods shipped”消息。

file

对这个例子来讲,使用事件通知是一个不错的选择,因为每个服务发消息之后它不需要任何反馈,这个消息由下一个模块接收来完成下一步动作,时间上的要求也比上一个要宽松。用事件通知的好处是降低了耦合度,坏处是你现在不能在程序里找到整个购物过程的步骤。如果一个业务逻辑有它自己相对固定的流程和步骤,那么使用RPC或业务流程管理(BPM)能够更方便地管理这些流程。在这种情况下选哪种方案呢?在我看来好处和坏处是大致相当的。从技术上来讲要选事件驱动,从业务上来讲要选RPC。

事件溯源(Event Sourcing)方式

这是一种具有颠覆性质的的设计,它把系统中所有的数据都以事件(Event)的方式记录下来,它的持久存储叫Event Store, 一般是建立在数据库或消息队列(例如Kafka)基础之上,并提供了对事件进行操作的接口,例如事件的读写和查询。事件溯源是由领域驱动设计(Domain-Driven Design)提出来的。DDD中有一个很重要的概念,有界上下文(Bounded Context),可以用有界上下文来划分微服务,每个有界上下文都可以是一个微服务。 下面是有界上下文的示例。下图中有两个服务“Sales”和“Support”。有界上下文的一个关键是如何处理共享成员, 在图中是“Customer”和“Product”。在不同的有界上下文中,共享成员的含义、用法以及他们的对象属性都会有些不同,DDD建议这些共享成员在各自的有界上下文中都分别建自己的类(包括数据库表),而不是共享。可以通过数据同步的手段来保持数据的一致性。

file

事件溯源是微服务的一种存储方式,它是微服务的内部实现细节。因此你可以决定哪些微服务采用事件溯源方式,哪些不采用,而不必所有的服务都变成事件溯源的。 通常整个应用程序只有一个Event Store, 不同的微服务都通过向Event Store发送和接受消息而互相通信。Event Store内部可以分成不同的stream(相当于消息队列中的Topic), 供不同的微服务中的领域实体(Domain Entity)使用。

事件溯源的一个短板是数据查询,它有两种方式来解决。第一种是直接对stream进行查询,这只适合stream比较小并且查询比较简单的情况。查询复杂的话,就要采用第二种方式,那就是建立一个只读数据库,把需要的数据放在库中进行查询。数据库中的数据通过监听Event Store中相关的事件来更新。

数据库存储方式只能保存当前状态,而事件溯源则存储了所有的历史状态,因而能根据需要回放到历史上任何一点的状态,具有很大优势。但它也不是一点问题都没有。第一,它的程序比较复杂,因为事件是一等公民,你必须把业务逻辑按照事件的方式整理出来,然后用事件来驱动程序。第二,如果你要想修改事件或事件的格式就比较麻烦,因为旧的事件已经存储在Event Store里了(事件就像日志,是只读的),没有办法再改。

由于事件溯源和事件通知表面上看起来很像,不少人都搞不清楚它们的区别。事件通知只是微服务的集成方式,程序内部是不使用事件溯源的,内部实现仍然是传统的数据库方式。只有当要与其他微服务集成时才会发消息。而在事件溯源中,事件是一等公民,可以不要数据库,全部数据都是按照事件的方式存储的。

虽然事件溯源的践行者有不同的意见,但有不少人都认为事件溯源不是微服务的集成方式,而是微服务的一种内部实现方式。因此,在一个系统中,可以某些微服务用事件溯源,另外一些微服务用数据库。当你要集成这些微服务时,你可以用事件通知的方式。注意现在有两种不同的事件需要区分开,一种是微服务的内部事件,是颗粒度比较细的,这种事件只发送到这个微服务的stream中,只被事件溯源使用。另一种是其他微服务也关心的,是颗粒度比较粗的,这种事件会放到另外一个或几个stream中,被多个微服务使用,是用来做服务之间集成的。这样做的好处是限制了事件的作用范围,减少了不相关事件对程序的干扰。详见"Domain Events vs. Event Sourcing".

事件溯源出现已经很长时间了,虽然热度一直在上升(尤其是这两年),但总的来说非常缓慢,谈论的人不少,但生产环境使用的不多。究其原因就是应为它对现在的体系结构颠覆太大,需要更改数据存储结构和程序的工作方式,还是有一定风险的。另外,微服务已经形成了一整套体系,从程序部署,服务发现与注册,到监控,服务韧性(Service Resilience),它们基本上都是针对RPC的,虽然也支持消息,但成熟度就差多了,因此有不少工作还是要自己来做。有意思的是Kafka一直在推动它作为事件驱动的工具,也取得了很大的成功。但它却没有得到事件溯源圈内的认可(详见这里)。
多数事件溯源都使用一个叫evenstore的开源Event Store,或是基于某个数据库的Event Store,只有比较少的人用Kafka做Event Store。 但如果用Kafka实现事件通知就一点问题都没有。总的来说,对大多数公司来讲事件溯源是有一定挑战的,应用时需要找到合适的场景。如果你要尝试的话,可以先拿一个微服务试水。

虽然现在事件驱动还有些生涩,但从长远来讲,还是很看好它的。像其他全新的技术一样,事件溯源需要大规模的适用场景来推动。例如容器技术就是因为微服务的流行和推动,才走向主流。事件溯源以前的适用场景只限于记账和源代码库,局限性较大。区块链可能会成为它的下一个机遇,因为它用的也是事件溯源技术。另外AI今后会渗入到具体程序中,使程序具有学习功能。而RPC模式注定没有自适应功能。事件驱动本身就具有对事件进行反应的能力,这是自我学习的基础。因此,这项技术长远来讲定会大放异彩,但短期内(3-5年)大概不会成为主流。

二,RPC方式:

RPC的方式就是远程函数调用,像RESTFul,gRPC, DUBBO 都是这种方式 。它一般是同步的,可以马上得到结果。在实际中,大多数应用都要求立刻得到结果,这时同步方式更有优势,代码也更简单。RPC允许调用远程方法时能像调用本地方法一样。在调用本地方法时,程序处于同一个进程,同样的内存空间,而远程调用时则不一样,为此,RPC框架则需要解决以下几个问题:

  • 寻址:服务提供者与服务消费者通过在服务注册中心注册服务与订阅服务;
  • 序列化与反序列化:网络传输时数据为二进制流;
  • 网络通信:Java提供的IO流操作即可完成网络通信,但较为复杂,Netty是一个IO通信框架,可基于此来实现RPC。

调用流程

(无代码)微服务服务调用和Feign详解_第1张图片#

三,总结

微服务之间的调用有两种方式,RPC和事件驱动,其中事件驱动分为事件通知和事件溯源。事件驱动是更好的方式,因为它是松耦合的。但如果业务逻辑是紧耦合的,RPC方式也是可行的(它的好处是代码更简单),而且你还可以通过选取合适的协议(Protobuf gRPC)来降低这种紧耦合带来的危害。

Spring Cloud Feign概述与工作原理解读

一,服务间调用的几种方式

使用Spring Cloud开发微服务时,在服务消费者调用服务提供者时,底层通过HTTP Client 的方式访问。但实际上在服务调用时,有主要以下来实现:

  • 使用JDK原生的URLConnection;

  • Apache提供的HTTP Client;

  • Netty提供的异步HTTP Client;

  • Spring提供的RestTemplate。

  • Spring Cloud的Spring Cloud Open Feign相对是最方便与最优雅的,使Feign支持Spring MVC注解的同时并整合了Ribbon。

二,Feign 概述

Feign 是一个声明式的 Web Service 客户端。它的出现使开发 Web Service 客户端变得很简单。使用 Feign 只需要创建一个接口加上对应的注解,比如:@FeignClient 注解。 Feign 有可插拔的注解,包括 Feign 注解和 AX-RS 注解。Feign 也支持编码器和解码器,Spring Cloud Open Feign 对 Feign 进行增强支持 Spring Mvc 注解,可以像 Spring Web 一样使用 HttpMessageConverters 等。

Feign 是一种声明式、模板化的 HTTP 客户端。在 Spring Cloud 中使用 Feign,可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问 HTTP 请求。接下来介绍一下 Feign 的特性,具体如下:

  • 可插拔的注解支持,包括 Feign 注解和AX-RS注解。
  • 支持可插拔的 HTTP 编码器和解码器。
  • 支持 Hystrix 和它的 Fallback。
  • 支持 Ribbon 的负载均衡。
  • 支持 HTTP 请求和响应的压缩。Feign 是一个声明式的 WebService 客户端,它的目的就是让 Web Service 调用更加简单。它整合了 Ribbon 和 Hystrix,从而不需要开发者针对 Feign 对其进行整合。Feign 还提供了 HTTP 请求的模板,通过编写简单的接口和注解,就可以定义好 HTTP 请求的参数、格式、地址等信息。Feign 会完全代理 HTTP 的请求,在使用过程中我们只需要依赖注入 Bean,然后调用对应的方法传递参数即可。

(三)Feign 工作原理

  1. 在开发微服务应用时,我们会在主程序入口添加 @EnableFeignClients 注解开启对 Feign Client 扫描加载处理。根据 Feign Client 的开发规范,定义接口并加 @FeignClients 注解。
  2. 当程序启动时,会进行包扫描,扫描所有 @FeignClients 的注解的类,并将这些信息注入 Spring IOC 容器中。当定义的 Feign 接口中的方法被调用时,通过JDK的代理的方式,来生成具体的 RequestTemplate。当生成代理时,Feign 会为每个接口方法创建一个 RequetTemplate 对象,该对象封装了 HTTP 请求需要的全部信息,如请求参数名、请求方法等信息都是在这个过程中确定的。
  3. 然后由 RequestTemplate 生成 Request,然后把 Request 交给 Client 去处理,这里指的 Client 可以是 JDK 原生的 URLConnection、Apache 的 Http Client 也可以是 Okhttp。最后 Client 被封装到 LoadBalanceclient 类,这个类结合 Ribbon 负载均衡发起服务之间的调用。

三,FeignClient注解剖析

@FeignClient 注解剖析

FeignClient注解被@Target(ElementType.TYPE)修饰,表示FeignClient注解的作用目标在接口上。

声明接口之后,在代码中通过@Resource注入之后即可使用。@FeignClient标签的常用属性如下:

  • name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
  • url: url一般用于调试,可以手动指定@FeignClient调用的地址
  • decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
  • configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
  • fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
  • fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
  • path: 定义当前FeignClient的统一前缀.

参考资料

微服务之间的最佳调用方式

https://blog.csdn.net/weixin_38748858/article/details/101062272

微服务架构-实现技术之具体实现工具与框架5:Spring Cloud Feign与Ribbon原理与注意事项

https://blog.csdn.net/xiaofeng10330111/article/details/87272248

微服务架构-实现技术之具体实现工具与框架5:Spring Cloud Feign与Ribbon原理与注意事项

https://blog.csdn.net/xiaofeng10330111/article/details/87272248

你可能感兴趣的:(微服务,spring,java)