在微服务架构中,需要调用很多服务来完成一个功能。服务之间如何相互调用成为微服务架构中的一个关键问题。调用服务有两种方式,一种是RPC方式,另一种是事件驱动(Event-driven)方式,也就是发送消息的方式。message方法是一种松耦合的方法,优于紧耦合的RPC方法,但是如果在合适的场景下使用RPC方法也有它的用武之地。我们一直在谈论耦合,那么耦合到底是什么意思呢?耦合类型时间耦合:客户端和服务器必须同时在线才能工作。发送消息时,接收消息队列必须运行,但后台处理程序暂时不工作,不影响。容量耦合:客户端和服务器的处理能力必须匹配。发送消息时,如果后台处理能力不足也没关系,消息队列会起到缓冲的作用。接口耦合:RPC调用有函数标签,而消息队列只是一条消息。例如,购买产品后,您需要致电送货服务。如果要发消息,只需要发消息说明产品已购买即可。发送方式耦合:RPC是点对点的方式,需要知道对方是谁。它的优点是可以返回返回值。消息可以是点对点的,也可以是广播的,这样可以减少耦合,但也很难返回值。让我们一一分析这些耦合的影响。第一,时间耦合。对于大多数应用来说,你都希望立即得到答案,所以即使你使用了消息队列,后台也需要一直工作。关注公众号:码猿技术专栏,回复关键字:“1111”获取阿里巴巴内部Java性能调优手册二、容量耦合,如果回复有时间要求,那么消息队列的缓冲功能不是很有效,因为你想要一个及时的回应。真正需要的是自动缩放,它可以自动调整服务器端的处理能力来匹配请求的数量。第三和第四,接口耦合和发送模式耦合,这两个确实是RPC模式的软肋。事件驱动(Event-Driven)方法MartinFowler将事件驱动分为四种方法(Whatdoyoumeanby“Event-Driven”),简化后本质上只有两种方法。一种是大家熟悉的事件通知(EventNotification),另一种是EventSourcing。事件通知是指微服务不直接调用,而是通过发送消息进行协作。事件溯源有点像会计。它将所有事件记录为永久存储层,并在其上构建应用程序。关注公众号:码猿技术专栏,回复关键词:“1111”获取阿里内部Java性能调优手册其实从应用的角度来说,它们不应该属于同一类,它们的用途是完全不同的。事件通知是微服务的调用(或集成)方式,应该和RPC分开。事件溯源是一种存储数据的方式,应该与数据库分离。事件通知(EventNotification)让我们来看一个具体的例子。在下面的示例中,有三个微服务,“OrderService”、“CustomerService”和“ProductService”。先说读取数据。假设你要创建一个“Order”,在这个过程中需要读取“Customer”和“Product”的数据。如果使用事件通知方式,只能在“订单服务”本地创建只读的“Customer”和“Product”表,并以消息的形式同步数据。除了写入数据,如果在创建“订单”时需要新建“客户”或修改“客户”的信息,可以在界面跳转到用户创建页面,然后在“客服”中创建用户》再次发送消息“用户已创建”,“订单服务”收到消息,并更新本地“客户”表。这不是一个很好的使用事件驱动的例子,因为事件驱动的好处是不同的程序可以独立运行,不需要绑定。但是现在“OrderService”需要等待“CustomerService”创建完成后才能继续运行,完成创建“Order”的全部工作。主要原因是“Order”和“Customer”在逻辑上是紧耦合的。没有“客户”就无法创建“订单”。在这种紧耦合的情况下,也可以使用RPC。你可以构建一个更高层的管理程序来管理这些微服务之间的调用,这样“订单服务”就不必直接调用“客服”了。当然,它本质上并没有解耦,只是把耦合转移到了上层,但至少现在“订单服务”和“客服”可以不互相影响了。之所以无法根除这种紧耦合,是因为他们在业务上是紧耦合的。关注公众号:码猿技术专栏,回复关键词:“1111”获取阿里内部Java性能调优手册,再举个购物的例子。用户选择商品后,生成“Checkout”,生成“Order”,然后需要“payment”,然后从“Inventory”中取货,最后通过“Shipment”发货。他们每个人都是一个微服务。这个例子可以用RPC和事件通知来完成。当使用RPC方法时,“订单”服务会调用其他几个服务来完成整个功能。当使用事件通知方式时,“Checkout”服务在服务完成后发送“OrderPlaced”消息,“Payment”服务接收消息,接收用户付款,发送“Paymentreceived”消息.“Inventory”服务接收消息,从仓库中取出货物,并发送“Goodsfetched”消息。“Shipment”服务获取消息、运送货物并发送“Goodsshipped”消息。对于这个例子,使用事件驱动是一个不错的选择,因为每个服务发送消息后,不需要任何反馈。这条消息被下一个模块接收到,完成下一步的动作,时间要求也比上一个短。松动的。使用事件驱动的优点是降低了耦合度,缺点是你现在无法在程序中找到整个购物流程的步骤。如果一个业务逻辑有自己相对固定的流程和步骤,那么使用RPC或者业务流程管理(BPM)可以更方便的管理这些流程。在这种情况下选择哪个选项?在我看来,优缺点大致相当。从技术角度,选择事件驱动,从业务角度,选择RPC。不过,现在越来越多的人将事件通知作为微服务的集成方式,似乎已经成为微服务之间的标准调用方式。事件溯源(EventSourcing)这是一种颠覆性的设计,将系统中的所有数据以事件(Event)的形式记录下来。它的持久化存储称为EventStore,一般建立在数据库或消息队列(如Kafka)的基础上,提供对事件进行操作的接口,如读取、写入和查询事件。事件溯源是由领域驱动设计提出的。DDD中有一个很重要的概念,限界上下文(BoundedContext),可以用来划分微服务,每一个限界上下文都可以是一个微服务。下面是一个有界上下文的例子。在下图中有两个服务“销售”和“支持”。有界上下文的关键是如何处理共享成员,在本例中为“客户”和“产品”。在不同的限界上下文中,共享成员的含义、用法和对象属性都会有所不同。DDD建议这些共享成员在各自的限界上下文中构建自己的类(包括数据库表),而不是共享。可以通过数据同步的方式来保持数据的一致性。下面将详细说明。事件溯源是微服务的一种存储方式,是微服务内部的实现细节。因此,您可以决定哪些微服务使用事件源方法,哪些不使用,而无需使所有服务都成为事件源。通常整个应用只有一个EventStore,不同的微服务通过向EventStore发送和接收消息来相互通信。EventStore可以分为不同的流(相当于消息队列中的Topics),供不同微服务中的领域实体(DomainEntities)使用。事件溯源的一个缺点是数据查询,可以通过两种方式解决。第一种是直接查询流,只适用于流比较小,查询比较简单的情况。如果查询比较复杂,则必须采用第二种方法,即建立只读数据库,将需要的数据放入数据库中进行查询。通过监听EventStore中的相关事件来更新数据库中的数据。关注公众号:码猿技术专栏,回复关键词:“1111”获取阿里巴巴内部Java性能调优手册数据库存储方式只能保存当前状态,而事件溯源存储所有历史状态,所以可以玩根据需要返回到历史上任何一点的状态都有很大的优势。但这并非没有问题。首先它的程序比较复杂,因为事件是一等公民,你必须按照事件的方式梳理业务逻辑,然后用事件来驱动程序。第二,如果要修改事件或者事件的格式,会很麻烦,因为旧的事件已经存储在EventStore中(事件就像一个日志,是只读的),并且有没有办法改变它。由于事件溯源和事件通知从表面上看很相似,所以很多人不知道它们之间的区别。事件通知只是微服务的一种集成方式。程序内部没有使用事件追踪,内部实现仍然是传统的数据库方式。仅在与其他微服务集成时才发送消息。在事件溯源中,事件是一等公民,不需要数据库。所有数据都以事件的形式存储。虽然事件溯源的从业者众说纷纭,但很多人认为事件溯源并不是一种微服务的集成方式,而是微服务内部的一种实现方式。因此,在一个系统中,一些微服务可以使用事件追踪,另一些微服务可以使用数据库。当你想集成这些微服务时,你可以使用事件通知。请注意,需要区分两个不同的事件。一种是微服务的内部事件,比较细粒度。这种事件只发送到微服务的流中,仅供事件跟踪使用。另一个是其他微服务也有关注,粒度比较粗。这种事件会放在另一个流或者几个流中,被多个微服务使用,用于服务之间的集成。这样做的好处是限制了事件的范围,减少了无关事件对程序的干扰。有关详细信息,请参阅“领域事件与事件溯源”。事件可追溯性由来已久。虽然人气一直在上升(尤其是近两年),但总体来说很慢。有很多人在谈论它,但在生产环境中使用它的人并不多。原因是它对现有架构的颠覆太大,需要改变数据存储结构和程序的工作方式,还是有一定风险的。另外,微服务已经形成了一个完整的体系,从程序部署、服务发现和注册,到监控、服务弹性(ServiceResilience),它们基本上都是针对RPC的,虽然也支持消息,但是成熟度要低很多,所以,一个很多工作还是要自己完成。有趣的是,Kafka一直将其作为一个事件驱动的工具来推广,并且也取得了很大的成功。但在事件溯源圈内并未得到认可。大多数事件溯源使用一个开源的事件存储,称为evenstore,或者基于某个数据库的事件存储。只有少数人将Kafka用作事件存储。但是如果你用kafka来实现事件通知,就完全没有问题。总的来说,事件溯源对大多数公司来说都是一个挑战,应用时需要找到合适的场景。如果你想尝试,你可以先用微服务试水。虽然现在事件驱动还是有点生涩,但是从长远来看,还是很看好它的。与其他全新的技术一样,事件追踪也需要大规模的应用场景来推广。比如,容器技术因为微服务的流行和推广而成为主流。之前事件溯源的适用场景仅限于会计和源码库,局限性比较大。区块链可能会成为它的下一个机会,因为它也使用了事件源技术。另外,未来AI会渗透到具体的程序中,让程序具备学习的功能。RPC模式注定是没有适配功能的。事件驱动本身具有响应事件的能力,这是自学习的基础。因此,这项技术从长远来看肯定会大放异彩,但短期内(3-5年)很可能不会成为主流。它的缺点是以后新客户来的时候,看到两个相似的功能就会一头雾水,不知道该用哪个。而且时间越长,就越严重。你的服务器可能附加的功能不多,但是类似的功能越来越多,无法选择。它的解决方案是使用支持向后兼容的RPC协议。现在最好的是ProtobufgRPC,尤其是在向后兼容性方面。它为每一个服务定义了一个接口,这是一个中性的接口,与编程语言无关,然后你可以使用工具生成各种语言的实现代码,以供不同语言使用。函数定义的变量是有编号的,变量可以是可选的,更好的解决了函数兼容的问题。使用上面的例子,当你想添加一个可选参数时,你定义一个新的可选变量。由于是可选的,原来的客户端不需要提供这个参数,所以不需要修改程序。新客户端可以提供此参数。您只需要能够在服务器端同时处理这两种情况。这样,服务器并没有增加新的功能,但是满足了用户的新需求,而且还是向下兼容的。微服务数量有上限吗?一般来说,微服务的数量不宜过多,否则会造成运维负担过重。有一点需要明确的是,微服务的流行并不是因为技术创新,而是为了满足管理需求。单体程序增长后,各个模块的部署时间要求不同,服务器优化要求也不同,团队人数众多,难以协调和管理。将程序拆分成微服务后,每个团队负责几个服务,便于管理,每个团队可以按照自己的节奏进行创新,但是给运维带来了巨大的麻烦。所以微服务刚出来的时候,我总觉得是一种倒退,弊大于利。但是,由于管理上的问题,没有别的办法,只好硬着头皮上了。值得庆幸的是,微服务的麻烦都是可以解决的。直到后来,微服务建立了一套完整的自动化体系,从程序集成到部署,从全链路跟踪到日志,以及服务检测、服务发现和注册,从而降低了微服务的工作量。微服务虽然在技术上没什么用,但它的流行极大地推动了容器技术、服务网格(ServiceMesh)、全链路追踪等新技术的发展。但是,它在技术本身上仍然没有发现任何优势。直到有一天,我才意识到,其实单个程序的性能调试是非常困难的(很难隔离瓶颈点),而微服务配置了全链路跟踪后,可以很快找到问题的症结所在。看来微服务在技术上不全是劣势,其实也有优势。但是微服务的粒度不能太细,否则工作量还是太大。一般规模的公司十几、几十个微服务可以承受,但是如果有几百个甚至上千个,那肯定不是一般公司能管得了的。即使现有的工具已经很成熟,整个微服务相关的流程已经基本实现了自动化,但仍然增加了很多工作量。MartinFowler几年前建议从单个程序开始(详见MonolithFirst),然后逐渐将功能拆分成单独的微服务。不过后来有人反对这个建议,他这才稍稍松了口气。如果整体程序不是太大,这是一个好主意。程序的大小可以用数据库表的数量来衡量。我见过一个大的单体程序,有几百张表,太多了,不好管理。一般情况下,一个微服务可以有两三张表到五六张表,一般不会超过十张表。但是如果你想减少微服务的数量,可以把这个标准放宽到不超过二十个表。使用它作为创建微程序的粗略指标。如果使用一段时间后还是觉得太大,那就逐渐拆分。当然,根据这个标准构建的服务更像是服务组合,而不是单个微服务。不过,它会为您节省很多工作。只要不影响业务部门的创新进度,这是一个很好的解决方案。你应该选择微服务吗?如果单体无法管理,那么您别无选择。如果没有管理上的问题,那么微服务只会给你带来问题和麻烦。事实上,大多数公司除了采用微服务之外别无选择,但你可以选择构建更少的微服务。如果还是拿不定主意,还有一个折中方案,“内部微服务设计”。内部微服务设计这种设计表面上看起来是一个单体程序,只有一个源代码存储仓库,一个数据库,一个部署,但是可以按照程序内部微服务的思想来设计。可以分为多个模块,每个模块是一个微服务,可以由不同的团队管理。以这张图片为例。这个图中每个圆角的正方形大致就是一个微服务,但是我们可以把它设计成一个单独的程序,里面有五个微服务。每个模块都有自己的数据库表,都在一个数据库中,但是模块不能跨数据库访问(不要在模块之间建立数据库表的外键)。“User”(在会议管理模块中)是一个共享类,但在不同的模块中有不同的名称、不同的含义和用法、不同的成员(例如在“客户服务”中称为“Customer”)。DDD(Domain-DrivenDesign)建议不要共享这个类,而是在每个限界上下文(模块)中新建一个类,并起一个新的名字。虽然他们数据库中的数据应该是大致相同的,但是DDD建议在每个限界上下文中新建一个表,并在它们之间进行数据同步。这种所谓的“内部微服务设计”其实就是DDD,只是那个时候还没有微服务,所以外表看起来是一个单体程序,但内部设计已经是微服务了。其书于2003年出版,当时名气很大。但是它更侧重于业务逻辑的设计,实践起来难度更大,所以大家说的多,实际用的少。十年后,微服务出来后,人们发现它内部其实也是微服务,微服务的设计需要以其思想为指导,于是再次焕发青春,而这一次更是来势汹汹,达到了每一个任何人谈论微服务都必须谈论DDD的重点。但是,一本软件书籍在十年后仍然可以指导新技术的设计,这是非常令人敬佩的。这样设计的好处是它是一个单一的程序,省去了多个微服务带来的部署和运维的麻烦。但是它内部是作为微服务设计的,以后拆分成微服务会更容易。至于什么时候拆分不是技术问题。如果负责这个单一程序的各个团队无法就部署进度、服务器优化等达成一致,那么就需要拆分。当然,你还要处理随之而来的各种运维麻烦。内部微服务设计是一种折衷,如果你想尝试微服务但又不想冒太大风险,这是一个不错的选择。结束语微服务之间的调用有两种方式,RPC和事件驱动。事件驱动是一种更好的方式,因为它是松耦合的。但如果业务逻辑是紧耦合的,RPC方式也是可行的(它的优点是代码更简单),你也可以通过选择合适的协议(ProtobufgRPC)来减少这种紧耦合带来的危害。很多人因为相似点而混淆了事件溯源和事件通知,但实际上它们是完全不同的东西。微服务数量不宜过多,可以先创建比较大的微服务(更像是服务组合)。如果你还不确定是否采用微服务架构,可以从“内部微服务设计”入手,然后逐步拆分。
