• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
Y-wee
博客园    首页    新随笔    联系   管理     

Spring Cloud Stream基本使用

Spring Cloud Stream基本使用

Spring Cloud Stream是一个构建消息驱动微服务的框架,可以屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型

应用程序通过inputs或者outputs来与Spring Cloud Stream中binder对象交互,通过我们配置来binding,而Spring Cloud Stream的binder对象负责与消息中间件交互,所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式,通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动

比方说我们用到了RabbitMQ和Kafka,这些中间件的差异性给我们实际项目开发造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我们想往另外一种消息队列进行迁移,这时候无疑是一个灾难性的,一大堆东西都要推倒重新做,因为它跟我们的系统耦合了,这时候Spring Cloud Stream给我们提供了一种解耦方式

在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互时,由于各消息中间件实现细节上有较大的差异性,通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离,通过向应用程序暴露统一的Channel通道,使用应用程序不需要再考虑各种不同的消息中间件实现(通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离)

Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费者、分区三个核心概念,目前仅支持RabbitMQ、Kafka

代码实战

生产者

导入依赖

<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>
    <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>
    <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>

配置文件

server:
  port: 8801

spring:
  application:
    name: stream-rabbitmq-provider
  cloud:
    stream:
      binders:
        # 配置要绑定的rabbitmq的服务信息
        defaultRabbit:
          # 消息组件类型
          type: rabbit
          # 配置rabbitmq的相关环境
          environment:
            spring:
              rabbitmq:
                host: 192.168.84.136
                port: 5672
                username: admin
                password: 123
      # 服务的整合处理
      bindings:
        # 这个名字是一个通道的名称
        output:
          # 要使用的Exchange名称
          destination: streamExchange
          # 设置消息类型为json,文本则设置“text/plain”
          content-type: application/json
          # 设置要绑定的消息服务的具体设置
          defaultbinder: defaultRabbit

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2
    lease-expiration-duration-in-seconds: 5
    instance-id: stream-rabbit-provider-8801
    prefer-ip-address: true

发送消息接口

package com.yl.stream.rabbitmq.provider.service;

/**
 * 消息提供者
 *
 * @auther Y-wee
 */
public interface IMessageProvider {
    /**
     * 发送消息
     *
     * @return
     */
    String send();
}

发送消息接口实现类

package com.yl.stream.rabbitmq.provider.service.impl;

import com.yl.stream.rabbitmq.provider.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.support.MessageBuilder;

import javax.annotation.Resource;

import org.springframework.cloud.stream.messaging.Source;

import java.util.UUID;

/**
 * 消息提供者
 *
 * @auther Y-wee
 */
// 定义消息的推送管道,将信道和交换机绑定
@EnableBinding(Source.class)
public class MessageProviderImpl implements IMessageProvider {
    /**
     * 消息发送管道
     */
    @Resource
    private MessageChannel output;

    /**
     * 发送消息
     *
     * @return
     */
    @Override
    public String send() {
        String serial = UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload(serial).build());
        System.out.println("serial: " + serial);

        return serial;
    }
}

controller

package com.yl.stream.rabbitmq.provider.controller;

import com.yl.stream.rabbitmq.provider.service.IMessageProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * 消息提供者
 *
 * @auther Y-wee
 */
@RestController
public class SendMessageController {
    @Resource
    private IMessageProvider messageProvider;

    /**
     * 发送消息
     *
     * @return 消息
     */
    @GetMapping(value = "/sendMessage")
    public String sendMessage() {
        return messageProvider.send();
    }

}

启动生产者,发送请求通过stream操作rabbitmq发送消息:http://localhost:8801/sendMessage

注意:启动可能会报拒绝连接异常,但是服务正常,依然可以发送消息,如果不想看到此报错,需要在配置文件单独配置rabbitmq:

spring:
  rabbitmq:
    host: 192.168.84.136
    port: 5672
    username: admin
    password: 123

消费者

导入依赖:所需依赖跟生产者一样

配置文件

server:
  port: 8802

spring:
  application:
    name: stream-rabbitmq-consumer
  cloud:
    stream:
      binders:
        defaultRabbit:
          type: rabbit
          environment:
            spring:
              rabbitmq:
                host: 192.168.84.136
                port: 5672
                username: admin
                password: 123
      bindings:
        input:
          destination: streamExchange
          content-type: application/json
          defaultbinder: defaultRabbit

eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2
    lease-expiration-duration-in-seconds: 5
    instance-id: stream-rabbitmq-consumer-8802
    prefer-ip-address: true

消费消息

package com.yl.stream.rabbitmq.consumer.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;

import javax.annotation.Resource;

/**
 * 消息消费接口
 *
 * @auther Y-wee
 */
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
    @Value("${server.port}")
    private String serverPort;

    /**
     * 消费消息
     * 注解@StreamListener:监听队列,用于消费者队列的消息接收
     *
     * @param message 消息
     */
    @StreamListener(Sink.INPUT)
    public void input(Message<String> message) {
        System.out.println("消费者1号,接受到的消息:" + message.getPayload() + "\tport:" + serverPort);
    }
}

消息重复消费问题

上面配置了一个消费者和一个生产者,如果在此基础上再增加一个一样的消费者来消费消息,则会产生一个问题:生产者发送一个消息被两个消费者消费了,这种情况在实际开发中是不被允许的

重复消费原因:在spring cloud stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次,不同组是可以重复消费的(上面两个消费者默认是在两个不同组)

其实如果仔细查看rabbitmq的交换机,就会发现该交换机类型是topic,路由是#,此配置相当于是发布订阅模式(广播)了,所以消息会被重复消费

解决方法:把两个消费者配置在同一个组,组名可以自定义,组配置如下:

spring:
  cloud:
    stream:
      bindings:
        input:
          group: groupA

配置分组后队列持久化了,如果没有配置分组则队列不是持久化的,消费者服务关闭后队列丢失,会造成消息也丢失

记得快乐
posted @ 2022-03-20 00:07  Y-wee  阅读(280)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3