第8章-使用Spring Cloud Stream的事件驱动架构

    本章主要内容
    了解事件驱动的架构处理以及它与微服务的相关性
    使用Spring Cloud Stream简化微服务中的事件处理
    配置Spring Cloud Stream
    使用Spring Cloud Stream和Kafka发布消息
    使用Spring Cloud Stream和Kafka消费消息
 
使用Spring Cloud Stream、Kafka和Redis实现分布式缓存
    还记得最后一次和别人坐下来聊天是什么时候吗?回想一下你是如何与那个人进行互动的。你完全专注于信息交换(就是在你说完之后,在等待对方完全回复之前什么都没有做)吗?当你说话的时候,你完全专注于谈话,而不让外界的东西分散自己的注意力吗?如果这场谈话中有两位以上的参与者,你重复了你对每位对话参与者所说的话,然后依次等待他们的回应吗?如果你对上述问题的回答都是“是”,那就说明你已经得道开悟,超越了我等凡人,那么你应该停止你正在做的事情,因为你现在可以回答这个古老的问题:“一只手鼓掌的声音是什么?”另外,我猜你没有孩子。
    事实上,人类总是处于一种运动状态,与周围的环境相互作用,同时发送信息给周围的事物并接收信息。在我家里,一个典型的对话可能是这样的:在和老婆说话的时候我正忙着洗碗,我正在向她描述我的一天,此时,她正玩着她的手机,并聆听着、处理着我说的话,然后偶尔给予回应。当我在洗碗的时候,我听到隔壁房间里有一阵骚动。我停下手头的事情,冲进隔壁房间去看看出了什么问题,然后我就看到我们那只9个月大的小狗维德咬住了我3岁大的儿子的鞋,像拿着战利品般在客厅里到处跑,而我3岁的儿子对此情此景感到不满。我满屋子追狗,直到把鞋子拿回来。然后我回去洗碗,继续和我的老婆聊天。
    我跟大家说这件事并不是想告诉大家我生活中普通的一天,而是想要指出我们与世界的互动不是同步的、线性的,不能狭义地定义为一个请求-响应模型。它是消息驱动的,在这里,我们不断地发送和接收消息。当我们收到消息时,我们会对这些消息作出反应,同时经常打断我们正在处理的主要任务。
    本章将介绍如何设计和实现基于Spring的微服务,以便与其他使用异步消息的微服务进行通信。使用异步消息在应用程序之间进行通信并不新鲜,新鲜的是使用消息实现事件通信的概念,这些事件代表了状态的变化。这个概念称为事件驱动架构(Event Driven Architecture,EDA),也被称为消息驱动架构(Message Driven Architecture,MDA)。基于EDA的方法允许开发人员构建高度解耦的系统,它可以对变更作出反应,而不需要与特定的库或服务紧密耦合。当与微服务结合后,EDA通过仅让服务监听由应用程序发出的事件流(消息)的方式,允许开发人员迅速地向应用程序中添加新功能。
    Spring Cloud项目通过Spring Cloud Stream子项目使构建基于消息传递的解决方案变得轻而易举。Spring Cloud Stream允许开发人员轻松实现消息发布和消费,同时屏蔽与底层消息传递平台相关的实现细节。
 
8.1 为什么使用消息传递、EDA和微服务
    
为什么消息传递在构建基于微服务的应用程序中很重要?为了回答这个问题,让我们从一个例子开始。本章将使用贯穿全书的两项服务:许可证服务和组织服务。让我们想象一下,将这些服务部署到生产环境之后,我们会发现,从组织服务中查找组织信息时,许可证服务调用花费了非常长的时间。在查看组织数据的使用模式时,我们会发现组织数据很少会更改,并且组织服务中读取的大多数数据都是按照组织记录的主键完成的。如果可以为组织数据缓存读操作从而节省访问数据库的成本,那么就可以极大地改善许可证服务调用的响应时间。
    在实施缓存解决方案时,我们会意识到有以下3个核心要求。
    (1)缓存的数据需要在许可证服务的所有实例之间保持一致——这意味着不能在许可证服务本地中缓存数据,因为要保证无论服务实例如何都能读取相同的组织数据。
    (2)不能将组织数据缓存在托管许可证服务的容器的内存中——托管服务的运行时容器通常受到大小限制,并且可以使用不同的访问模式来对数据进行访问。本地缓存可能会带来复杂性,因为必须保证本地缓存与集群中的所有其他服务同步。
(3)在更新或删除一个组织记录时,开发人员希望许可证服务能够识别出组织服务中出现了状态更改——许可证服务应该使该组织的所有缓存数据失效,并将它从缓存中删除。
    我们来看看实现这些要求的两种方法。第一种方法将使用同步请求-响应模型来实现上述要求。在组织状态发生变化时,许可证服务和组织服务通过它们的REST端点进行通信。第二种方法是组织服务发出异步事件(消息),该事件将通报组织服务数据已经发生了变化。使用第二种方法,组织服务将发布一条组织记录已被更新或删除的消息到队列。许可证服务将监听中介,了解到一个组织事件已发生,并清除其缓存中的组织数据。
 
8.1.1 使用同步请求-响应方式来传达状态变化
    对于组织数据缓存,我们将使用分布式的键值存储数据库Redis。图8-1提供了一个高层次概览,讲述如何使用传统的同步请求-响应编程模型构建高速缓存解决方案。
 
 
图8-1 在同步请求-响应模型中,紧密耦合的服务带来复杂性和脆弱性
    在图8-1中,当用户调用许可证服务时,许可证服务同样需要查找组织数据。
 
许可证服务首先会检查通过组织ID从Redis集群中检索的所需的组织数据。如果许可证服务找不到组织数据,它将使用基于REST的端点调用组织服务,然后在将组织数据返回给用户之前,将返回的数据存储在Redis中。现在,如果有人使用组织服务的REST端点来更新或删除组织记录,组织服务将需要调用在许可证服务上公开的端点,以通知许可证服务使它缓存中的组织数据无效。在图8-1中,如果查看组织服务调用许可证服务以使Redis缓存失效的地方,那么至少可以看到以下3个问题。
    (1)组织服务和许可证服务紧密耦合。
 
    (2)耦合带来了服务之间的脆弱性。如果用于使缓存无效的许可证服务端点发生了更改,则组织服务必须要进行更改。
    (3)这种方法是不灵活的,因为如果想要为组织服务添加新的消费者,我们必须修改组织服务的代码,才能让它知道需要调用其他的服务以通知数据变更。
    1.服务之间的紧密耦合
    在图8-1中,我们可以看到许可证服务和组织服务之间存在紧密耦合。许可证服务始终依赖于组织服务来检索数据。然而,通过让组织服务在组织记录被更新或删除时直接与许可证服务进行通信,就已经将耦合从组织服务引入许可证服务了。
 
为了使Redis缓存中的数据失效,组织服务需要许可证服务公开的端点,该端点可以被调用以使许可证服务的Redis缓存无效,或者组织服务必须直接与许可证服务所拥有的Redis服务器进行通信以清除其中的数据。
    让组织服务与Redis进行通信有其自身的问题,因为开发人员正直接与另一个服务拥有的数据存储进行通信。在微服务环境中,这是一个很大的禁忌。虽然可以认为组织数据理所当然地属于组织服务,但是许可证服务在特定的上下文中使用这些数据,并且可能潜在地转换数据,或者围绕这些数据构建业务规则。让组织服务直接与Redis服务进行通信,可能会意外地破坏拥有许可证服务的团队所实现的规则。
    2.服务之间的脆弱性
    许可证服务与组织服务之间的紧密耦合也带来了这两种服务之间的脆弱性。如果许可证服务关闭或运行缓慢,那么组织服务可能会受到影响,因为组织服务正在与许可证服务进行直接通信。同样,如果组织服务直接与许可证服务的Redis数据存储进行对话,那么就会在组织服务和Redis之间创建一个依赖关系。在这种情况下,共享Redis服务器出现任何问题都有可能拖垮这两个服务。
    3.在修改组织服务以增加新的消费者方面是不灵活的
 
这种架构的最后一个问题是,它是不灵活的。使用图8-1中的模型,如果有其他服务对组织数据发生的变化感兴趣,则需要添加另一个从组织服务到该其他服务的调用。这意味着需要更改代码并重新部署组织服务。如果使用同步的请求-响应模型来通知状态更改,则会在应用程序中的核心服务和其他服务之间出现网状的依赖关系模式。这些网络的中心会成为应用程序中的主要故障点。
    
另一种耦合
    虽然消息传递在服务之间增加了一个间接层,但是使用消息传递仍然会在两个服务之间引入紧密耦合。在本章的后面,读者将在组织服务和许可证服务之间发送消息。这些消息将使用JSON作为消息的传输协议,序列化以及反序列化为Java对象。如果两个服务不能优雅地处理同一消息类型的不同版本,则在转换为Java对象时,对JSON消息的结构的变更会造成问题。JSON本身不支持版本控制,但如果读者需要版本控制,那么可以使用Apache Avro。Avro是一个二进制协议,它内置了版本控制。Spring Cloud Stream支持Apache Avro作为消息传递协议。使用Avro不在本书的讨论范围之内,但是本书确实希望让读者意识到,如果真的担心消息版本控制的话,Avro确实会有帮助。
 
 
 
 
posted @ 2019-12-03 10:54  mongotea  阅读(664)  评论(0编辑  收藏  举报