SpringCloud系列(51)--SpringCloud Stream之使用分组解决消息重复消费问题

前言:在上一节中我们实现了SpringCloud Stream消息驱动里的消费者,实现了消息的消费者后,在日常运行中我们可能会遇到重复消费的问题,比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况,这时我们就可以使用Stream中的消息分组来解决

1、再次创建一个消费者模块,命为cloud-stream-rabbitmq-consumer8803
(1)在父工程下新建模块
(2)选择模块的项目类型为Maven并选择模块要使用的JDK版本

(3)填写子模块的名称,然后点完成即可完成创建

效果图:

(4)修改cloud-stream-rabbitmq-consumer8802子模块的pom.xml文件,然后reolad一下,下载依赖

例:

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud01</artifactId>
        <groupId>com.ken.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-stream-rabbitmq-consumer8803</artifactId>

    <dependencies>
        <!--以下依赖都没写版本号,没写版本号的情况下会引用父项目的版本-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--Eureka Clinet-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!--lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
(5)为子模块添加名为application的yml配置文件

效果图:

(6)修改application.yml文件配置(注:因为当前实现的是消费者,所以spring.cloud.stream.bindings下要写成input,表示当前是消费者)
bash 复制代码
server:
  port: 8803

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      #在此处配置要绑定的rabbitmq的服务信息
      binders:
        #表示定义的名称,用于于binding整合
        defaultRabbit:
          #消息组件类型,表示用到的组件是rabbit
          type: rabbit
          #设置rabbitmq的相关的环境配置
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      #服务的整合处理
      bindings:
        #这个名字是一个通道的名称
        input:
          #表示要使用的Exchange名称定义
          destination: studyExchange
          #设置消息类型,本次为json,文本则设置"text/plain"
          content-type: application/json
          #设置要绑定的消息服务的具体设置
          binder: defaultRabbit

eureka:
  #客户端进行Eureka注册的配置
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    #设置心跳的时间间隔(默认是30秒)
    lease-renewal-interval-in-seconds: 2
    #如果现在超过了5秒的间隔(默认是90秒)
    lease-expiration-duration-in-seconds: 5
    #在信息列表时显示主机名称
    instance-id: receive-8002.com
    #访问的路径变为IP地址
    prefer-ip-address: true
(7)为cloud-stream-rabbitmq-consumer8803子模块新建一个主启动类,类名输入com.ken.springcloud.StreamMQMain8803,然后创建即可

效果图:

(8)编写StreamMQMain8803主启动类
java 复制代码
package com.ken.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StreamMQMain8803 {
    public static void main(String[] args) {
        SpringApplication.run(StreamMQMain8803.class, args);
    }
}
2、在com.ken.springcloud包下新建一个名为controller的包

效果图:

3、在controller包下新建一个名为ReceiveMessageListencerController的控制类

效果图:

4、编写ReceiveMessageListencerController类
java 复制代码
package com.ken.springcloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListencerController {

    @Value("${server.port}")
    private String serverPort;

    @StreamListener(Sink.INPUT)
    public void input(Message<String> message) {
        System.out.println("消费者1号--->接受到的消息:" + message.getPayload() + "\t port:" + serverPort);
    }

}
5、分别启动eureka-server7001、RabbitMQ、cloud-stream-rabbitmq-provider8801、cloud-stream-rabbitmq-consumer8802、cloud-stream-rabbitmq-consumer8803
(1)分别启动eureka-server7001、cloud-stream-rabbitmq-provider8801、cloud-stream-rabbitmq-consumer8802、cloud-stream-rabbitmq-consumer8803

效果图:

(2)启动RabbitMQ

效果图:

6、查看RabbitMQ是否正常运行

在浏览器输入http://localhost:15672/进入RabbitMQ的可视化界面,然后登录进去(默认账号密码guest/guest)

​​

效果图:

​​

7、在浏览器地址栏输入 http://localhost:8801/sendMessage接口,然后按回车发起请求

效果图:

分别查看cloud-stream-rabbitmq-provider8801、cloud-stream-rabbitmq-consumer8802、cloud-stream-rabbitmq-consumer8803的控制台输出,发现8801生产者只发送了一条消息,但这条消息分别被8802和8803消费了,出现了我们在前言里说的重复消费问题

cloud-stream-rabbitmq-provider8801:

cloud-stream-rabbitmq-consumer8802:

cloud-stream-rabbitmq-consumer8803:

8、造成重复消费的原因

不同组是可以重复消费的,因为cloud-stream-rabbitmq-consumer8802和cloud-stream-rabbitmq-consumer8803是处于不同的组,所以可以重复消费

9、分别查看cloud-stream-rabbitmq-consumer8802和cloud-stream-rabbitmq-consumer8803所属的组是什么
(1)登录RabbitMQ Management页面,点进Exchange,然后点进studyExchange

(2)点进第一个队列

(3)可以看到这个队列所属的组是djyBMHL-QTyaDDmJp-TBvQ

(4)点进第一个队列
(5)可以看到这个队列所属的组是sC3WGnxXRFGQZ8V94jUQog

综上所示,cloud-stream-rabbitmq-consumer8802和cloud-stream-rabbitmq-consumer8803属于不同的组,所以可以重复消费

10、使用分组解决重复消费问题原理

在Stream中处于同一个group中的多个消费者是竞争关系把微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。

11、设置自定义配置分组
(1)修改cloud-stream-rabbitmq-consumer8802的applidation.yml配置文件,给cloud-stream-rabbitmq-consumer8802分配组名为KenA的组
复制代码
server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      #在此处配置要绑定的rabbitmq的服务信息
      binders:
        #表示定义的名称,用于于binding整合
        defaultRabbit:
          #消息组件类型,表示用到的组件是rabbit
          type: rabbit
          #设置rabbitmq的相关的环境配置
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      #服务的整合处理
      bindings:
        #这个名字是一个通道的名称
        input:
          #表示要使用的Exchange名称定义
          destination: studyExchange
          #设置消息类型,本次为json,文本则设置"text/plain"
          content-type: application/json
          #设置要绑定的消息服务的具体设置
          binder: defaultRabbit
          #分配给组名为kenA的组
          group: KenA

eureka:
  #客户端进行Eureka注册的配置
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    #设置心跳的时间间隔(默认是30秒)
    lease-renewal-interval-in-seconds: 2
    #如果现在超过了5秒的间隔(默认是90秒)
    lease-expiration-duration-in-seconds: 5
    #在信息列表时显示主机名称
    instance-id: receive-8802.com
    #访问的路径变为IP地址
    prefer-ip-address: true
(2)修改cloud-stream-rabbitmq-consumer8803的applidation.yml配置文件,给cloud-stream-rabbitmq-consumer8803分配组名为KenB的组
复制代码
server:
  port: 8803

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      #在此处配置要绑定的rabbitmq的服务信息
      binders:
        #表示定义的名称,用于于binding整合
        defaultRabbit:
          #消息组件类型,表示用到的组件是rabbit
          type: rabbit
          #设置rabbitmq的相关的环境配置
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      #服务的整合处理
      bindings:
        #这个名字是一个通道的名称
        input:
          #表示要使用的Exchange名称定义
          destination: studyExchange
          #设置消息类型,本次为json,文本则设置"text/plain"
          content-type: application/json
          #设置要绑定的消息服务的具体设置
          binder: defaultRabbit
          #分配给组名为kenB的组
          group: KenB

eureka:
  #客户端进行Eureka注册的配置
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    #设置心跳的时间间隔(默认是30秒)
    lease-renewal-interval-in-seconds: 2
    #如果现在超过了5秒的间隔(默认是90秒)
    lease-expiration-duration-in-seconds: 5
    #在信息列表时显示主机名称
    instance-id: receive-8803.com
    #访问的路径变为IP地址
    prefer-ip-address: true
(3)重启cloud-stream-rabbitmq-consumer8802和cloud-stream-rabbitmq-consumer8803服务

效果图:

(4)重新点进Exchange,然后点进studyExchange

(5)可以看到cloud-stream-rabbitmq-consumer8802和cloud-stream-rabbitmq-consumer8803被分别分配到KenA组和KenB组,这说明我们成功设置自定义配置分组

注:如果分组没生效可以尝试使用Maven的compile命令来重新编译项目,然后重启启动项目即可


12、使用自定义分组解决重复消费问题

在上述步骤11中我们演示了如何去设置自定义配置分组,而不同的组是可以重复消费的,同一个组内会发生竞争关系,只有其中一个可以消费,所以我们可以通过自定义分组把cloud-stream-rabbitmq-consumer8802和cloud-stream-rabbitmq-consumer8803共同分为KenA组,使其只有一个消费者能消费消息

(1)修改cloud-stream-rabbitmq-consumer8803的applidation.yml配置文件,给cloud-stream-rabbitmq-consumer8803分配组名为KenA的组(因为在上述步骤11中我们给8802设置了KenA组,所以这里只用修改8803的分组即可)
复制代码
server:
  port: 8803

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      #在此处配置要绑定的rabbitmq的服务信息
      binders:
        #表示定义的名称,用于于binding整合
        defaultRabbit:
          #消息组件类型,表示用到的组件是rabbit
          type: rabbit
          #设置rabbitmq的相关的环境配置
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      #服务的整合处理
      bindings:
        #这个名字是一个通道的名称
        input:
          #表示要使用的Exchange名称定义
          destination: studyExchange
          #设置消息类型,本次为json,文本则设置"text/plain"
          content-type: application/json
          #设置要绑定的消息服务的具体设置
          binder: defaultRabbit
          #分配给组名为kenB的组
          group: KenA

eureka:
  #客户端进行Eureka注册的配置
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    #设置心跳的时间间隔(默认是30秒)
    lease-renewal-interval-in-seconds: 2
    #如果现在超过了5秒的间隔(默认是90秒)
    lease-expiration-duration-in-seconds: 5
    #在信息列表时显示主机名称
    instance-id: receive-8803.com
    #访问的路径变为IP地址
    prefer-ip-address: true
(2)重启cloud-stream-rabbitmq-consumer8803服务

效果图:

(3)在浏览器地址栏输入 http://localhost:8801/sendMessage接口,然后按2次回车发起2次请求

效果图:

分别查看cloud-stream-rabbitmq-provider8801、cloud-stream-rabbitmq-consumer8802、cloud-stream-rabbitmq-consumer8803的控制台输出,发现8801生产者发送了2条消息的情况下,8802和8803分别消费了1条消息,一开始说到的重复消费问题不再出现

cloud-stream-rabbitmq-provider8801:

cloud-stream-rabbitmq-consumer8802:

cloud-stream-rabbitmq-consumer8803:

相关推荐
wno7045 小时前
Spring Cloud Hystrix Dashboard仪表盘
spring·spring cloud·hystrix
BUG指挥官8 小时前
Redis已接入AI
数据库·人工智能·spring boot·redis·spring cloud
wno70420 小时前
Spring Cloud Hystrix服务容错
spring·spring cloud·hystrix
苏渡苇1 天前
Spring Insight 里上报 Span 为什么用 JDK HttpClient 而不是 RestTemplate
java·后端·spring·spring cloud·springboot·性能监控
YOU OU2 天前
Spring Cloud Consul
spring cloud·consul·java-consul
重生之我是Java开发战士2 天前
【Spring Cloud】Eureka服务注册发现与LoadBalancer负载均衡
spring cloud·eureka·负载均衡
谢亮_vipxieliang2 天前
ValidX vs Google Guava Preconditions:验证 vs 断言
java·spring boot·后端·spring cloud·hibernate·guava
夏天拐跑了西瓜3 天前
Spring Cloud 微服务实战(八):Hystrix熔断降级——从服务雪崩到断路器模式
spring cloud·hystrix·微服务
重生之我是Java开发战士4 天前
【Spring Cloud】认识微服务与工程搭建
spring·spring cloud·微服务
墨着染霜华5 天前
SpringCloud Gateway线上Nginx代理后自定义下划线请求头丢失(app_name/app_version获取不到)完美解决
nginx·spring cloud·gateway