SpringCloud整合RocketMQ

3,062 阅读5分钟

现在多大企业都使用到Spring Cloud的框架,本文介绍Sping Cloud接入RocketMQ消息队列中间件的示例

1、Spring Cloud Stream 介绍

Spring Cloud Stream 是一个用于构建基于消息的微服务应用框架,使用 Spring Integration 与 Broker 进行连接。

一般来说,消息队列中间件都有一个 Broker Server(代理服务器),消息中转角色,负责存储消息、转发消息。

例如说在 RocketMQ 中,Broker 负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。另外,Broker 也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。

Spring Cloud Stream 提供了消息中间件的统一抽象,推出了 publish-subscribe、consumer groups、partition 这些统一的概念。

Spring Cloud Stream 内部有两个概念:BinderBinding

Binder,跟消息中间件集成的组件,用来创建对应的 Binding。各消息中间件都有自己的 Binder 具体实现。RocketMQ的具体实现为 RocketMQMessageChannelBinder

Binding,包括 Input Binding 和 Output Binding。Binding 在消息中间件与应用程序提供的 Provider 和 Consumer 之间提供了一个桥梁,实现了开发者只需使用应用程序的 Provider 或 Consumer 生产或消费数据即可,屏蔽了开发者与底层消息中间件的接触。

2、项目demo

前提:准备好一个可运行的spingboot框架的web项目

导入关键依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <groupId>org.apache.rocketmq</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>${rocketmq.starter.version}</version>
</dependency>

项目结构

配置文件


 #防止注册成同一个服务,影响dev,本地自测时,在name后面加上自己的名字,如demo-service-local,修改后的名字请勿提交

spring.application.name: demo-service-local

 #不启用外部化配置

spring.cloud.nacos.config.enabled: false



spring:

  cloud:

    stream:

      #在有多个binder的情况下,所有binder要使用的binder的名称

      default-binder: rocketmq

      rocketmq:

        binder:

          #RocketMQ NameServer 地址

          name-server: 127.0.0.1:9876

          # 阿里云access-key,购买阿里云RocketMQ时配置

 #access-key:

          # 阿里云secret-key,购买阿里云RocketMQ时配置

 #secret-key:

          # 消息轨迹开启后存储的 Topic 名称

 customized-trace-topic: rmq_TRACE_DATA_cn-publictest

        bindings:

          # 输出通道,对应下面自定义枚举 COMMON_OUTPUT

          common_output:

            producer:

              # 可用来查询生产者组

              group: GID_demo_qa

              # 是否同步发送消息

              sync: true

          # 输入通道,对应下面自定义枚举 DEMO_TEST, 记住这个通道 

          demo_test:

            consumer:

              enabled: true

              orderly: false 

              # 订阅哪个tag

              tags: demo_test_tag

      # 注意间隔,这里的bindings是和rocketmq同级的,绑定哪个topic,格式是什么

      bindings:

       # 输出通道,对应下面自定义枚举 COMMON_OUTPUT

        common_output:

        # 发送消息格式,这里使用JSON

          content-type: application/json

          # 目标,也就是topic

          destination: demo

        # 输入通道,对应下面自定义枚举 DEMO_TEST

        demo_test:

          # 接收消息格式,这里使用JSON

          content-type: application/json

           # 目标,也就是topic

          destination: demo 

          # 对应哪个Group,因为一个topic下面可有多个Group

          group: GID_demo_qa
消息发送和接收的POJO(Json格式传输)
@Data

public class DemoTestBody implements Serializable {



    private static final long serialVersionUID = -761662493627579882L;



    /**

 * 名称

 */

 private String name;

}

demo案例,只定义好了一个简单的POJO,实际可根据自己业务需要定义

消费者
@Slf4j

@Service

public class DemoTestConsumer {



    @Autowired

    private DemoService demoService;

    

    

    @StreamListener(StreamConstant.DEMO_TEST)

    public void onConsumer(@Payload DemoTestBody body, @Headers Map headers) {

        long time = System.currentTimeMillis();

        String msgId = headers.get(StreamConstant.ROCKET_MQ_MESSAGE_ID) == null ? "" : headers.get(StreamConstant.ROCKET_MQ_MESSAGE_ID).toString();

        log.info("DemoTestConsumer 消费消息,msgId:{},body:{}", msgId, JSONObject.toJSONString(body));

        try {

            // 业务处理

            demoService.consumerHandle(body);

            log.info("DemoTestConsumer 消费消息成功,msgId:{},消费时长:{},msg:{}", msgId, System.currentTimeMillis() - time, JSONObject.toJSONString(body));

        } catch (Exception e) {

            log.error("DemoTestConsumer 消费失败,正在重试,错误信息:{},{}, info:{}", e, e.getStackTrace(), JSONObject.toJSONString(body));

            throw new BusinessException(ServerCode.SERVER_ERROR, "消费失败:" + e.getMessage());

        }

    }

}

@StreamListener 它主要定义在方法上, 作用是将被修饰的方法注册为消息中间件上数据流的事件监听器,配置文件中提到注意输入通道, 注解中的属性值对应了监听的消息通道名, 在上面的例子中,我们通过 @StreamListener(StreamConstant.DEMO_TEST) 注解将onConsumer方法注册为input消息通道的监听处理器,所以当我们向RocketMQ的中发布消息的时候,该方法会做出对应的响应动作。

@Payload : 进行反序列化成 POJO 对象

@Headers : 拿到消息头的信息

生产者
@Slf4j

@Service

public class RocketMessageQueueProducer {



    @Autowired

    private Source source;



    /**

 * 发送消息

 *

 *  @param body    : 消息体

 *  @param tagEnum : tag枚举

 */

 public <T> void sendMessage(T body, MessageQueueTagEnum tagEnum) {

        String msgJson = JSON.toJSONString(body);

        log.info("sendMessage,tag:{},msgBody:{}", tagEnum.getTag(), msgJson);

        try {

            // 构建消息体

            Message<T> message = MessageBuilder.withPayload(body)

                    .setHeader(MessageConst.PROPERTY_TAGS, tagEnum.getTag())

                    .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)

                    .build();

            // 发送

            source.commonOutput().send(message);

            log.info("sendMessage succeed. tag:{},msgBody:{}", tagEnum.getTag(), msgJson);

        } catch (Exception e) {

            log.info("sendMessage failure. tag:{},msgBody:{},errorMsg:{}", tagEnum.getTag(), msgJson, e);

        }

    }

}

sendMessage(T body, MessageQueueTagEnum tagEnum),定义泛型参数,及标签参数,根据需求传入不同的消息及tag

setHeader(),设置消息头内容,可自定义内容,KV结构

自定义枚举


public class StreamConstant {



    /**

 * rocketmq消息id

 */

 public static final String ROCKET_MQ_MESSAGE_ID = "rocketmq_MESSAGE_ID";



    /**

 * demo测试消息

 */

 public static final String DEMO_TEST = "demo_test";



    /**

 * 公共输出通道

 */

 public static final String COMMON_OUTPUT = "common_output";



}
标签枚举
@Getter

@AllArgsConstructor

public enum MessageQueueTagEnum {



    /**

 * 测试tag

 */

 DEMO_TEST_TAG("demo_test_tag", "测试tag");



    private final String tag;

    private final String describe;

}
输入通道定义


public interface Sink {



    /**

     * demo项目输入通道

 *  @return channel

 */

 @Input(StreamConstant.DEMO_TEST)

    SubscribableChannel spiderServerReleaseTicket();

}

那么,我们是否要实现 Sink 接口呢?答案是不需要,还是全部交给 Spring Cloud Stream 的 BindableProxyFactory 来解决。BindableProxyFactory 会通过动态代理,自动实现 Sink 接口。 而 @Input 注解的方法的返回值,BindableProxyFactory 会扫描带有 @Input 注解的方法,自动进行创建。

输出通道定义


public interface Source {



    /**

 * demo项目输出通道

 *  @return channel

 */

 @Output(StreamConstant.COMMON_OUTPUT)

    MessageChannel commonOutput();

}

那么,我们是否要实现 Source 接口呢?答案是也不需要,全部交给 Spring Cloud Stream 的 BindableProxyFactory 来解决。BindableProxyFactory 会通过动态代理,自动实现 Source 接口。 而 @Output 注解的方法的返回值,BindableProxyFactory 会扫描带有 @Output 注解的方法,自动进行创建。

我们可以看到,Sink和Source中分别通过@Input和@Output注解定义了 输入通道和输出通道,另外, @Input和@Output 注解都还有一 个value属性,该属性可以用来设置消息通道的名称,这里Sink和Source中指定的消息通道名称分别为 input和output。如果我们直接使用这两个注解而没有指定具体的 value值,将默认使用方法名作为消息通道的名称 。需要 注意 一 点,当我们定义输出通道的时候,需要返回MessageChannel接口对象, 该接口定义了向消息通道发送消息的方法; 而定义输入通道时,需要返回SubscribableChannel接口对象,该接口 继承自MessageChannel接口,它定义了维护消息通道订阅者的方法。

成功

3、更多配置

RocketMQ Binder Properties

spring.cloud.stream.rocketmq.binder 为前缀

RocketMQ Consumer Properties

RocketMQ Provider Properties

如果觉得有收获,请点个赞吧