8.3.2 在许可证服务中编写消息消费者

 
    到目前为止,我们已经修改了组织服务,以便在组织服务更改组织数据时向Kafka发布消息。任何对组织数据感兴趣的服务,都可以在不需要由组织服务显式调用的情况下作出反应。这还意味着开发人员可以轻松地添加新的功能,可以让它们监听消息队列中的消息来对组织服务中的更改作出反应。现在让我们换一个角度,看看服务如何使用Spring Cloud Stream来消费消息。
    对于本示例,我们将使用许可证服务消费组织服务发布的消息。图8-5展示了将许可证服务融入图8-3所示的Spring Cloud Stream架构中的什么地方。
 
 
图8-5 当一条消息进入Kafka的orgChangeTopic时,许可证服务将作出响应
    首先,还是需要将Spring Cloud Stream依赖项添加到许可证服务的pom.xml文件中。该pom.xml文件可以在本书源代码的licensing-service目录中找到。与之前看到的organization-service pom.xml文件类似,需要添加以下两个依赖项。
<dependency>   
<groupId>org.springframework.cloud</groupId>   
<artifactId>spring-cloud-stream</artifactId> 
</dependency> 
<dependency>   
<groupId>org.springframework.cloud</groupId>   
<artifactId>spring-cloud-starter-stream-kafka</artifactId> 
</dependency>
 
接下来,需要告诉许可证服务,它需要使用Spring Cloud Stream绑定到消息代理。像组织服务一样,我们将使用@EnableBinding注解来标注许可证服务引导类Application(在licensing-service/src/main/java/com/thoughtmechanix/licenses/Application.java中)。许可证服务和组织服务之间的区别在于传递给@EnableBinding注解的值,如代码清单8-5所示。
 
代码清单8-5 使用Spring Cloud Stream消费消息
package com.thoughtmechanix.licenses; 
    // 为了简洁,省略了import语句 
←@EnableBinding注解告诉服务使用Sink接口中定义的通道来监听传入的消息。
@EnableBinding(Sink.class)  ⇽---
public class Application {     
// 为了简洁,移除剩余代码 
@StreamListener(Sink.INPUT)  ⇽--- 每次收到来自input通道的消息时,Spring Cloud Stream将执行此方法     
public void loggerSink(OrganizationChangeModel orgChange) {             
logger.debug("Received an event for organization id {}" , orgChange.getOrganizationId());     
}
    
因为许可证服务是消息的消费者,所以将会把值Sink.class传递给@EnableBinding注解。这告诉Spring Cloud Stream使用默认的Spring Sink接口。与8.3.1节中描述的Spring Cloud Steam Source接口类似,Spring Cloud Stream在Sink接口上公开了一个默认的通道,名为input,它用于监听通道上的传入消息。
    定义了想要通过@EnableBinding注解来监听消息之后,就可以编写代码来处理来自input通道的消息。为此,要使用Spring Cloud Stream的@StreamListener注解。
    @StreamListener注解告诉Spring Cloud Stream,每次从input通道接收消息,就会执行loggerSink()方法。Spring Cloud Stream将自动把从通道中传出的消息反序列化为一个名为OrganizationChangeModel的Java POJO。
    同样,消息代理的主题到input通道的实际映射是在许可证服务的配置中完成的。对于许可证服务,其配置如代码清单8-6所示,可以在许可证服务的licensing-service/src/main/resources/application.yml文件中找到。
    
代码清单8-6 将许可证服务映射到Kafka中的消息主题
spring:
  application:
    name: licensingservice
  cloud:
    stream:
      bindings:
        input: ⇽--- spring.cloud.stream.bindings.input属性将input通道映射到orgChangeTopic队列
          destination: orgChangeTopic
          content-type: application/json
          group: licensingGroup ⇽--- 该group属性用于保证服务只处理一次
        binder:
          zkNodes: localhost
          brokers: localhost
 
代码清单8-6中的配置类似于组织服务的配置。然而,上述配置有两个关键的不同之处。首先,现在有一个名为input的通道定义在spring.cloud.stream.bindings属性下。这个值映射到代码清单8-5中代码里定义的Sink.INPUT通道,它的属性将input通道映射到orgChangeTopic。其次,我们看到这里引入了一个名为spring.cloud.stream.bindings.input.group的新属性。group属性定义将要消费消息的消费者组的名称。消费者组的概念是这样的:开发人员可能拥有多个服务,每个服务都有多个实例侦听同一个消息队列,但是只需要服务实例组中的一个服务实例来消费和处理消息。group属性标识服务所属的消费者组。只要服务实例具有相同的组名,Spring Cloud Stream和底层消息代理将保证,只有消息的一个副本会被属于该组的服务实例所使用。对于许可证服务,group属性值将会是licensingGroup。
    图8-6阐述了如何使用消费者组来强制跨多个服务消费的消息只被消费一次。
 
 
图8-6 消费者组保证消息只会被一组服务实例处理一次
    
8.3.3 在实际操作中查看消息服务
    现在,每当添加、更新或删除记录时,组织服务就将向orgChangeTopic发布消息,并且许可证服务从同一主题接收消息。通过更新组织服务记录并观察控制台,可以看到来自许可证服务的相应日志消息,以此来查看这段代码的实际操作。
    要更新组织服务记录,我们将在组织服务上发送PUT请求来更新组织的联系电话号码。将要用来执行更新的端点是http://localhost:5555/api/organization/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a,要发送到端点的PUT调用的请求体是:
 {     
"contactEmail": "mark.balster@custcrmco.com",    
 "contactName": "Mark Balster",    
 "contactPhone": "823-555-2222",    
 "id": "e254f8c-c442-4ebe-a82a-e2fc1d1ff78a",     
"name": "customer-crm-co" 
}
    
图8-7展示了这个PUT调用返回的输出。
图8-7 使用组织服务更新联系电话号码
    一旦组织服务调用完成,就应该在运行服务的控制台窗口中看到图8-8所示的输出结果。
 
 
图8-8 控制台将显示组织服务发送的消息,以及接下来被许可证服务接收的消息
    现在已经有两个通过消息传递相互通信的服务。Spring Cloud Stream充当了这些服务的中间人。从消息传递的角度来看,这些服务对彼此一无所知。它们使用消息传递代理来作为中介,并使用Spring Cloud Stream作为消息传递代理的抽象层进行通信。
 
在本地使用postman访问的时候,其界面如下:
 
body为:
{     
"contactEmail": "mark.balster@custcrmco66.com",    
"contactName": "Mark Balster",    
"contactPhone": "823-555-2222",    
"id": "e254f8c-c442-4ebe-a82a-e2fc1d1ff78a",     
"name": "customer-crm-co"
注意Application.java下面截图中的代码需要将注释去掉:、
application.yml如下:
    
8.4 Spring Cloud Stream用例:分布式缓存
    到目前为止,我们拥有两个使用消息传递进行通信的服务,但是我们并没有真正处理消息。现在我们将要构建在本章前面讨论过的分布式缓存示例。我们将让许可证服务始终检查分布式的Redis缓存以获取与特定许可证相关联的组织数据。如果组织数据在缓存中存在,那么将从缓存中返回数据。否则,将调用组织服务,并将调用的结果缓存在一个Redis散列中。
    在组织服务中更新数据时,组织服务将向Kafka发出一条消息。许可证服务将接收消息,并对Redis发出删除指令,以清除缓存。
    云缓存与消息传递
    使用Redis作为分布式缓存与云中的微服务开发密切相关。以我目前的雇主来为例,我们使用亚马逊Web服务(AWS)构建我们的解决方案,并且是亚马逊的DynamoDB的重度使用者。我们还使用亚马逊的ElastiCache(Redis)增强如下功能。
 
提高查找常用数据的性能——通过使用缓存,我们显著提高了几个关键服务的性能。我们销售的产品中的所有表都是多租户的(在单个表中保存多个客户记录),这意味着它们可以非常大。由于缓存倾向于留住“大量”使用的数据,所以我们使用Redis和缓存来避免读取DynamoDB,从而显著提高了性能。
    
减少持有数据的DynamoDB表上的负载(和成本)——在DynamoDB中访问数据可能是一项昂贵的提议。应用程序发出的每一次读取都是一次收费事件。使用Redis服务器通过主键读取要比DynamoDB读取便宜得多。
    
增加弹性,以便在主数据存储(DynamoDB)存在性能问题时,服务能够优雅地降级——如果AWS DynamoDB出现问题(这确实偶尔发生),使用诸如Redis这样的缓存可以帮助服务优雅地降级。根据在缓存中保存的数据量,缓存解决方案可以帮助减少从访问数据存储中获取的错误的数量。
    Redis远远不止是一个缓存解决方案,但是如果开发人员需要一个分布式缓存,它可以充当这个角色。

 

posted @ 2019-12-03 10:55  mongotea  阅读(123)  评论(0编辑  收藏  举报