Spring Boot整合Rocketmq

RocketMQ介绍

   rocketmq是阿里巴巴开源的一款分布式的消息中间件,他源于jms规范但是不遵守jms规范。对于分布式只一点,如果你了用过其他mq并且了解过rocketmq,就知道rocketmq天生就是分布式的,可以说是brokerproviderconsumer等各种分布式。

RocketMQ优点:

1 rmq去除对zk的依赖

2 rmq支持异步和同步两种方式刷磁盘

3 rmq单机支持的队列或者topic数量是5w

4 rmq支持消息重试

5 rmq支持严格按照一定的顺序发送消息

6 rmq支持定时发送消息

7 rmq支持根据消息ID来进行查询

8 rmq支持根据某个时间点进行消息的回溯

9 rmq支持对消息服务端的过滤

10 rmq消费并行度:顺序消费 取决于queue数量,乱序消费 取决于consumer数量

RocketMQ的安装(windows)

首先去官网下载编译之后的版本,然后解压到本地目录,比如如下截图所示

Spring Boot整合Rocketmq_第1张图片

然后按照如下步骤来操作:

1 配置ROCKETMQ_HOME到系统环境变量中,因为启动脚本会读取这个变量

2 进入bin目录,用编辑器打开红色标注的脚本

Spring Boot整合Rocketmq_第2张图片

3 查看内容,发现每个脚本会调用另外一个脚本,最终要修改如下的脚本

打开之后找到这一行,修改成红色标注的一样

Spring Boot整合Rocketmq_第3张图片

其实就是把2g改为1g,防止内存设置过大而导致的其他问题

4 分别进入bin目录下 启动如下脚本:

& 启动namesrv

& 启动brokerserver

看到如下就代表两个服务都成功启动了,接下来通过代码来模拟发送消息和消费消息

RocketMQ发送消息和消费消息

Spring Boot整合Rocketmq_第4张图片

看一下pom.xml的文件内容



    4.0.0

    com.suning.mq
    rocketmq
    1.0-SNAPSHOT

    
        
            org.apache.rocketmq
            rocketmq-client
            4.2.0
        

        
        
            com.alibaba
            fastjson
            1.2.44
        


    


先定义一个消息保存的载体:

package producer;

import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;

import java.io.Serializable;

/**
* @Author 18011618
* @Date 10:41 2018/7/17
* @Function 消息生产者
*/
public class Producer {
    public static void main(String[] args) throws MQClientException {
        DefaultMQProducer producer = new DefaultMQProducer("test-group");
        producer.setNamesrvAddr("localhost:9876");
        producer.setInstanceName("rmq-instance");
        producer.start();
        try {
            for (int i=0;i<100;i++){
                User user = new User();
                user.setLoginName("abc"+i);
                user.setPwd(String.valueOf(i));
                Message message = new Message("log-topic", "user-tag",JSON.toJSONString(user).getBytes());
                System.out.println("生产者发送消息:"+JSON.toJSONString(user));
                producer.send(message);
            }


        } catch (Exception e) {
            e.printStackTrace();
        }
        producer.shutdown();
    }

    /**
     * 发送用户消息
     */
    static  class User implements Serializable{
        private String loginName;
        private String pwd;

        public String getLoginName() {
            return loginName;
        }

        public void setLoginName(String loginName) {
            this.loginName = loginName;
        }

        public String getPwd() {
            return pwd;
        }

        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    }

定义消息的发送者:

package com.springboot.rocketmq.one.producer;

import com.alibaba.fastjson.JSON;
import com.springboot.rocketmq.content.UserContent;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
* @Author 18011618
* @Date 10:31 2018/7/18
* @Function 模拟用户消息发送
*/
@Component
public class UserProducer {
    /**
     * 生产者的组名
     */
    @Value("${suning.rocketmq.producerGroup}")
    private String producerGroup;

    /**
     * NameServer 地址
     */
    @Value("${suning.rocketmq.namesrvaddr}")
    private String namesrvAddr;

    @PostConstruct
    public void produder() {
         DefaultMQProducer producer = new DefaultMQProducer(producerGroup);
         producer.setNamesrvAddr(namesrvAddr);
        try {
            producer.start();
            for (int i = 0; i < 100; i++) {
                UserContent userContent = new UserContent(String.valueOf(i),"abc"+i);
                String jsonstr = JSON.toJSONString(userContent);
                System.out.println("发送消息:"+jsonstr);
                Message message = new Message("user-topic", "user-tag", jsonstr.getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult result = producer.send(message);
                System.err.println("发送响应:MsgId:" + result.getMsgId() + ",发送状态:" + result.getSendStatus());
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            producer.shutdown();
        }
    }
}

定义消息的消费者:

package consumer;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class Consumer {
    public static void main(String[] args) throws MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("test-group");

        consumer.setNamesrvAddr("localhost:9876");
        consumer.setInstanceName("rmq-instance");
        consumer.subscribe("log-topic", "user-tag");
        
        consumer.registerMessageListener(new MessageListenerConcurrently() {

            public ConsumeConcurrentlyStatus consumeMessage(
                    List msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.println("消费者消费数据:"+new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
     }
}

接下来先启动消费者,然后再启动生产者,看一下效果

生产者控制台发送消息:

Spring Boot整合Rocketmq_第5张图片

消费者控制台消费消息

Spring Boot整合Rocketmq_第6张图片

且能发现消费消息的机制 默认是乱序的

五 RocketMQ Web启动

上面虽然成功了发送了消息和消费了消息,可惜都是通过控制台查看的,其实rocketmq有扩展的组件,其中有一个组件就是支持web界面查看消息相关的功能,下面就简单介绍一下如何使用这个功能:访问 https://github.com/apache/rocketmq-externals/ 可以看到如下界面,选择红色标注的模块,下载到本地:

Spring Boot整合Rocketmq_第7张图片

然后进行部署,它是一个完整的springboot项目,所以可以用IDEA打开

Spring Boot整合Rocketmq_第8张图片

然后需要修改application.properties的相关内容

server.contextPath=
server.port=8080
#spring.application.index=true
spring.application.name=rocketmq-console
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
logging.config=classpath:logback.xml
#if this value is empty,use env value rocketmq.config.namesrvAddr  NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
rocketmq.config.namesrvAddr=localhost:9876
#if you use rocketmq version < 3.5.8, rocketmq.config.isVIPChannel should be false.default true
rocketmq.config.isVIPChannel=
#rocketmq-console's data path:dashboard/monitor
rocketmq.config.dataPath=/tmp/rocketmq-console/data
#set it false if you don't want use dashboard.default true
rocketmq.config.enableDashBoardCollect=true

其实主要就是这个地址,然后启动App这个类,这个时候在浏览器端访问 http://localhost:8080/ 会出现如下界面

Spring Boot整合Rocketmq_第9张图片

点击Dashboard可以查看相关消息主题的信息

Spring Boot整合Rocketmq_第10张图片

还可以查看具体的发送消息和消费消息:点击Message

Spring Boot整合Rocketmq_第11张图片

点击Detail 能看查看消息具体的消息

Spring Boot整合Rocketmq_第12张图片

这个控制台的功能还是很强大的,都可以去摸索一下如何使用.

SpringBootRocketMQ整合

整合方式有很多种,但是可以总结为两大类,一类是基于高度封装(使用起来非常简单,通用性高),一类是基于原始开发(没啥可通用性),在这里把这两个类别都讲解一下.

& 先讲一种,基于原始API开发,不要啥封装,只需要引入一个配置文件即可

看一下整体项目结构,主要实现的部分是红色标注的,其它类是第二种封装使用的,

Spring Boot整合Rocketmq_第13张图片

看一下pom.xml的文件内容



    
        spring_boot
        com.suning.springboot
        1.0-SNAPSHOT
    
    4.0.0

    spring_boot_rocketmq
    

    
       

        
        
            org.projectlombok
            lombok
            provided
        


        
            org.apache.rocketmq
            rocketmq-client
            4.1.0-incubating
        

    


在这里为了简化javabean的操作,引入了lombok组件.定义一个javabean用来承载消息

package com.springboot.rocketmq.content;

import lombok.*;
import lombok.experimental.Accessors;

/**
* @Author 18011618
* @Date 19:28 2018/7/17
* @Function 发送消息体
*/
@ToString
@AllArgsConstructor
@EqualsAndHashCode
@Accessors(chain = true)
@Getter
@Setter
public class UserContent {
    private String username;
    private String pwd;

}

关于这里的注解参数可以具体参考:lombok的常用注解含义

定义生产者:

package com.springboot.rocketmq.producer;

import com.alibaba.fastjson.JSON;
import com.springboot.rocketmq.content.UserContent;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import javax.annotation.PostConstruct;

/**
* @Author 18011618
* @Date 10:31 2018/7/18
* @Function 模拟用户消息发送
*/
@Component
public class UserProducer {
    /**
     * 生产者的组名
     */
    @Value("${suning.rocketmq.producerGroup}")
    private String producerGroup;

    /**
     * NameServer 地址
     */
    @Value("${suning.rocketmq.namesrvaddr}")
    private String namesrvAddr;

    @PostConstruct
    public void produder() {
         DefaultMQProducer producer = new DefaultMQProducer(producerGroup);
         producer.setNamesrvAddr(namesrvAddr);
        try {
            producer.start();
            for (int i = 0; i < 100; i++) {
                UserContent userContent = new UserContent(String.valueOf(i),"abc"+i);
                String jsonstr = JSON.toJSONString(userContent);
                System.out.println("发送消息:"+jsonstr);
                Message message = new Message("user-topic", "user-tag", jsonstr.getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult result = producer.send(message);
                System.err.println("发送响应:MsgId:" + result.getMsgId() + ",发送状态:" + result.getSendStatus());
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            producer.shutdown();
        }
    }
}

这里使用了两个新注解:

@PostConstruct:它的作用是相当于servlet的Init功能,在对象的构造器执行完了,就会立马调用该方法
@Value:注入classpath下面的配置文件中的值到变量中

定义消费者:

package com.springboot.rocketmq.consumer;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
* @Author 18011618
* @Date 10:31 2018/7/18
* @Function 模拟用户消息消费
*/
@Component
public class UserConsumer {
    /**
     * 消费者的组名
     */
    @Value("${suning.rocketmq.conumerGroup}")
    private String consumerGroup;

    /**
     * NameServer 地址
     */
    @Value("${suning.rocketmq.namesrvaddr}")
    private String namesrvAddr;

    @PostConstruct
    public void consumer() {
        System.err.println("init defaultMQPushConsumer");
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup);
        consumer.setNamesrvAddr(namesrvAddr);
        try {
            consumer.subscribe("user-topic", "user-tag");
            consumer.registerMessageListener((MessageListenerConcurrently) (list, context) -> {
                try {
                    for (MessageExt messageExt : list) {

                        System.err.println("消费消息: " + new String(messageExt.getBody()));//输出消息内容
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER; //稍后再试
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //消费成功
            });
            consumer.start();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

最后再看一下配置文件:

server.port=8080

# 生产者的组名
suning.rocketmq.producerGroup=user-group
# 消费者的组名
suning.rocketmq.conumerGroup=user-group
# NameServer地址
suning.rocketmq.namesrvaddr=localhost:9876

写一个启动类

package com.springboot.rocketmq;

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

/**
 * @Author 18011618
 * @Description
 * @Date 11:02 2018/7/17
 * @Modify By
 */
@SpringBootApplication
public class RocketmqApplication {
    public static void main(String[] args) {
        SpringApplication.run(RocketmqApplication.class,args);
    }
}

这个时候直接启动该应用就能看到消息的发送和消费的情况:

Spring Boot整合Rocketmq_第14张图片

其实这样就实现了和springboot的整合,其实也很简单,但是有的人会感觉代码有点啰嗦,而且没啥通用性,如果业务中有多个消息队列的应用,那么就会出现很多重复代码,针对这种情况,可以按照下面这种方式来进行简化,如果不想自己动手的话,可以去找开源的代码,看有没有人实现了类似功能,拿过来直接用,还有就是在熟悉原理之后,也可以自己进行二次封装.

& 使用第三方组件整合springboot

首先看一下整体项目结构:以红色标注

Spring Boot整合Rocketmq_第15张图片

接下来具体分析要封装的步骤:

1 定义rocketmq相关参数的配置文件

参数可以自己根据实际业务进行选择,因为本身rocketmq中很多的参数都是有默认值的,所以可以不需要配置所有的参数,按需即可,存储位置,网上很多的例子都是配置在默认的application.yaml中(不建议这样做,因为这个是全局的,建议发在一个非全局的文件,然后动态注入进来,达到隔离)

2 定义保存配置文件的实体和对应的配置文件加载解析类

& 保存配置文件的类

package com.springboot.rocketmq.two.config;

import java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
* @Author 18011618
* @Date 19:31 2018/7/18
* @Function 读取配置文件信息
*/
@PropertySource("classpath:config/rocketmq.properties")
@ConfigurationProperties(prefix = "suning.rocketmq")
@Configuration
@Setter
@Getter
@ToString
@Accessors(chain = true)
public class RocketMQProperties {
   private String namesrvAddr;
   private String producerGroupName;
   private String transactionProducerGroupName;
   private String consumerGroupName;
   private String producerInstanceName;
   private String consumerInstanceName;
   private String producerTranInstanceName;
   private int consumerBatchMaxSize;
   private boolean consumerBroadcasting;
   private boolean enableHistoryConsumer;
   private boolean enableOrderConsumer;
   private List subscribe = new ArrayList();

}
@PropertySource:指定要加载的配置文件路径,其实默认是可以不写的,如果是加载classpath下面的配置文件,因为它会自己去寻找,但是有时候不同的版本,不写的话又会出现错误,所以为了不出现错误,通常建议配置一下,这样肯定不会有错的
@ConfigurationProperties:指定读取配置文件的规则,比如前缀是什么,不存在的字段是否可以忽略等
@Configuration:相当于xml的配置标签,这样

& 加载配置文件的参数,实例化相关的bean

package com.springboot.rocketmq.two.config;

import javax.annotation.PostConstruct;

import com.springboot.rocketmq.two.bean.MessageEvent;
import lombok.extern.slf4j.Slf4j;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.stream.Collectors;
/**
* @Author 18011618
* @Date 19:36 2018/7/18
* @Function 通过使用指定的文件读取类 来加载配置文件到字段中
*/
@Configuration
@EnableConfigurationProperties(RocketMQProperties.class)
@Slf4j
public class RocketMQConfiguration {

   @Autowired
   private RocketMQProperties rocketMQProperties;

   //事件监听
   @Autowired
   private ApplicationEventPublisher publisher = null;

   private static boolean isFirstSub = true;

   private static long startTime = System.currentTimeMillis();

    /**
     * 容器初始化的时候 打印参数
     */
   @PostConstruct
   public void init() {
      System.err.println(rocketMQProperties.getNamesrvAddr());
      System.err.println(rocketMQProperties.getProducerGroupName());
      System.err.println(rocketMQProperties.getConsumerBatchMaxSize());
      System.err.println(rocketMQProperties.getConsumerGroupName());
      System.err.println(rocketMQProperties.getConsumerInstanceName());
      System.err.println(rocketMQProperties.getProducerInstanceName());
      System.err.println(rocketMQProperties.getProducerTranInstanceName());
      System.err.println(rocketMQProperties.getTransactionProducerGroupName());
      System.err.println(rocketMQProperties.isConsumerBroadcasting());
      System.err.println(rocketMQProperties.isEnableHistoryConsumer());
      System.err.println(rocketMQProperties.isEnableOrderConsumer());
      System.out.println(rocketMQProperties.getSubscribe().get(0));
   }

   /**
    * 创建普通消息发送者实例
    * @return
    * @throws MQClientException
    */
   @Bean
   public DefaultMQProducer defaultProducer() throws MQClientException {
      DefaultMQProducer producer = new DefaultMQProducer(
            rocketMQProperties.getProducerGroupName());
      producer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
      producer.setInstanceName(rocketMQProperties.getProducerInstanceName());
      producer.setVipChannelEnabled(false);
      producer.setRetryTimesWhenSendAsyncFailed(10);
      producer.start();
      log.info("rocketmq producer server is starting....");
      return producer;
   }

   /**
    * 创建支持消息事务发送的实例
    * @return
    * @throws MQClientException
    */
   @Bean
   public TransactionMQProducer transactionProducer() throws MQClientException {
      TransactionMQProducer producer = new TransactionMQProducer(
            rocketMQProperties.getTransactionProducerGroupName());
      producer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
      producer.setInstanceName(rocketMQProperties
            .getProducerTranInstanceName());
      producer.setRetryTimesWhenSendAsyncFailed(10);
      // 事务回查最小并发数
      producer.setCheckThreadPoolMinSize(2);
      // 事务回查最大并发数
      producer.setCheckThreadPoolMaxSize(2);
      // 队列数
      producer.setCheckRequestHoldMax(2000);
      producer.start();
      log.info("rocketmq transaction producer server is starting....");
      return producer;
   }

   /**
    * 创建消息消费的实例
    * @return
    * @throws MQClientException
    */
   @Bean
   public DefaultMQPushConsumer pushConsumer() throws MQClientException {
      DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(
            rocketMQProperties.getConsumerGroupName());
      consumer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
      consumer.setInstanceName(rocketMQProperties.getConsumerInstanceName());

      //判断是否是广播模式
      if (rocketMQProperties.isConsumerBroadcasting()) {
         consumer.setMessageModel(MessageModel.BROADCASTING);
      }
      //设置批量消费
      consumer.setConsumeMessageBatchMaxSize(rocketMQProperties
            .getConsumerBatchMaxSize() == 0 ? 1 : rocketMQProperties
            .getConsumerBatchMaxSize());

      //获取topic和tag
      List subscribeList = rocketMQProperties.getSubscribe();
      for (String sunscribe : subscribeList) {
         consumer.subscribe(sunscribe.split(":")[0], sunscribe.split(":")[1]);
      }

      // 顺序消费
      if (rocketMQProperties.isEnableOrderConsumer()) {
         consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(
                  List msgs, ConsumeOrderlyContext context) {
               try {
                  context.setAutoCommit(true);
                  msgs = filterMessage(msgs);
                  if (msgs.size() == 0)
                     return ConsumeOrderlyStatus.SUCCESS;
                  publisher.publishEvent(new MessageEvent(msgs, consumer));
               } catch (Exception e) {
                  e.printStackTrace();
                  return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
               }
               return ConsumeOrderlyStatus.SUCCESS;
            }
         });
      }
      // 并发消费
      else {

         consumer.registerMessageListener(new MessageListenerConcurrently() {

            @Override
            public ConsumeConcurrentlyStatus consumeMessage(
                  List msgs,
                  ConsumeConcurrentlyContext context) {
               try {
                   //过滤消息
                  msgs = filterMessage(msgs);
                  if (msgs.size() == 0)
                     return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                  publisher.publishEvent(new MessageEvent(msgs, consumer));
               } catch (Exception e) {
                  e.printStackTrace();
                  return ConsumeConcurrentlyStatus.RECONSUME_LATER;
               }

               return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
         });
      }
      new Thread(new Runnable() {
         @Override
         public void run() {
            try {
               Thread.sleep(5000);

               try {
                  consumer.start();
               } catch (Exception e) {
                        e.printStackTrace();
               }
               log.info("rocketmq consumer server is starting....");
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }

      }).start();

      return consumer;
   }

    /**
     * 消息过滤
     * @param msgs
     * @return
     */
   private List filterMessage(List msgs) {
      if (isFirstSub && !rocketMQProperties.isEnableHistoryConsumer()) {
         msgs = msgs.stream()
               .filter(item -> startTime - item.getBornTimestamp() < 0)
               .collect(Collectors.toList());
      }
      if (isFirstSub && msgs.size() > 0) {
         isFirstSub = false;
      }
      return msgs;
   }

}

这个类主要加载配置文件里面参数的值,然后初始化生成producer,事务producer,consumer等实例。

@EnableConfigurationProperties:启动自动配置文件属性的获取,通过指定的类

看一下配置文件的内容

# 指定namesrv地址
suning.rocketmq.namesrvAddr=localhost:9876

#生产者group名称
suning.rocketmq.producerGroupName=user_group

#事务生产者group名称
suning.rocketmq.transactionProducerGroupName=order_transaction

#消费者group名称
suning.rocketmq.consumerGroupName=user_consumer_group

#生产者实例名称
suning.rocketmq.producerInstanceName=user_producer_instance

#消费者实例名称
suning.rocketmq.consumerInstanceName=user_consumer_instance

#事务生产者实例名称
suning.rocketmq.producerTranInstanceName=user_producer_transacition

#一次最大消费多少数量消息
suning.rocketmq.consumerBatchMaxSize=1

#广播消费
suning.rocketmq.consumerBroadcasting=false

#消费的topic:tag
suning.rocketmq.subscribe[0]=user-topic:white

#启动的时候是否消费历史记录
suning.rocketmq.enableHistoryConsumer=false

#启动顺序消费
suning.rocketmq.enableOrderConsumer=false

3 创建消息的发送

package com.springboot.rocketmq.two.producer;

import com.springboot.rocketmq.two.bean.User;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSON;



import java.util.List;

@RestController
public class ProducerController {
   @Autowired
   private DefaultMQProducer defaultProducer;

   @Autowired
   private TransactionMQProducer transactionProducer;


    /**
     * 发送普通消息
     */
   @GetMapping("/sendMessage")
   public void sendMsg() {
      
      for(int i=0;i<100;i++){
         User user = new User();
         user.setId(String.valueOf(i));
         user.setUsername("jhp"+i);
         String json = JSON.toJSONString(user);
         Message msg = new Message("user-topic","white",json.getBytes());
         try {
            SendResult result = defaultProducer.send(msg);
            System.out.println("消息id:"+result.getMsgId()+":"+","+"发送状态:"+result.getSendStatus());
      } catch (Exception e) {
         e.printStackTrace();
      }
      }
      
      
   }

    /**
     * 发送事务消息
     * @return
     */
   @GetMapping("/sendTransactionMess")
   public String sendTransactionMsg() {
       SendResult sendResult = null;
       try {
           // a,b,c三个值对应三个不同的状态
           String ms = "c";
           Message msg = new Message("user-topic","white",ms.getBytes());
           // 发送事务消息
           sendResult = transactionProducer.sendMessageInTransaction(msg, (Message msg1, Object arg) -> {
               String value = "";
               if (arg instanceof String) {
                   value = (String) arg;
               }
               if (value == "") {
                   throw new RuntimeException("发送消息不能为空...");
               } else if (value =="a") {
                   return LocalTransactionState.ROLLBACK_MESSAGE;
               } else if (value =="b") {
                   return LocalTransactionState.COMMIT_MESSAGE;
               }
               return LocalTransactionState.ROLLBACK_MESSAGE;
           }, 4);
           System.out.println(sendResult);
       } catch (Exception e) {
           e.printStackTrace();
       }
       return sendResult.toString();
   }

    /**
     * 支持顺序发送消息
     */
  @GetMapping("/sendMessOrder")
   public void sendMsgOrder() {
      for(int i=0;i<100;i++) {
          User user = new User();
          user.setId(String.valueOf(i));
          user.setUsername("jhp" + i);
          String json = JSON.toJSONString(user);
          Message msg = new Message("user-topic", "white", json.getBytes());
          try{
              defaultProducer.send(msg, new MessageQueueSelector() {
                  @Override
                  public MessageQueue select(List mqs, Message msg, Object arg) {
                      int index = ((Integer) arg) % mqs.size();
                      return mqs.get(index);
                  }
              },i);
          }
          catch (Exception e){
              e.printStackTrace();
          }
      }
   }
}

创建消息的消费:

package com.springboot.rocketmq.two.consumer;

import java.util.List;

import com.springboot.rocketmq.two.bean.MessageEvent;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * 监听消息进行消费
 */
@Component
public class ConsumerService {
   @EventListener(condition = "#event.msgs[0].topic=='user-topic' && #event.msgs[0].tags=='white'")
   public void rocketmqMsgListener(MessageEvent event) {
       try {
          List msgs = event.getMsgs();
          for (MessageExt msg : msgs) {
               System.err.println("消费消息:"+new String(msg.getBody()));
           }
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}

为了方便监听消费,增加了一个事件监听功能:

package com.springboot.rocketmq.two.bean;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.context.ApplicationEvent;

import java.io.UnsupportedEncodingException;
import java.util.List;
/**
 * 监听对象
 * @author 18011618
 *
 */
public class MessageEvent extends ApplicationEvent {
   private static final long serialVersionUID = -4468405250074063206L;
   private DefaultMQPushConsumer consumer;
   private List msgs;

   public MessageEvent(List msgs, DefaultMQPushConsumer consumer) throws Exception {
       super(msgs);
       this.consumer = consumer;
       this.setMsgs(msgs);
   }



   public DefaultMQPushConsumer getConsumer() {
       return consumer;
   }

   public void setConsumer(DefaultMQPushConsumer consumer) {
       this.consumer = consumer;
   }
   
   public List getMsgs() {
       return msgs;
   }

   public void setMsgs(List msgs) {
       this.msgs = msgs;
   }
}

写一个启动应用类:

package com.springboot.rocketmq;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

/**
 * @Author 18011618
 * @Description
 * @Date 11:02 2018/7/17
 * @Modify By
 */
@SpringBootApplication
//@ComponentScan(basePackages = "com.springboot.rocketmq.one")
@ComponentScan(basePackages = "com.springboot.rocketmq.two")
public class RocketmqApplication {
    public static void main(String[] args) {
        SpringApplication.run(RocketmqApplication.class,args);
    }
}

这里要区别于第一种方式,所以只扫描第二种方式的包

然后访问浏览器端 http://localhost:8080/sendMessage 效果如下截图所示

Spring Boot整合Rocketmq_第16张图片

至此生产者和消息这就可以进行正常的发送和消费了.

扩展:根据条件动态创建bean

场景:通常在项目中我们会有一些开关或者一些标识值,来做不同的操作,以前的话大家可能都是通过在代码中加入大量的if判断,这样虽然实现了功能,但是维护和可读性都是比较差,所以spring在3.x版本之后开始加入了@Conditonal条件注解:很简单就是开发者根据一些条件来决定是否创建bean或者动态来创建bean,这就比较简单了,而springboot为了方便开发,又对上面这个注解进行了一些列的扩展,让使用起来更加的简单:,下面就列举常用的条件注解:

@ConditionalOnClass:当前上下文存存在个类,才会创建对应的bean实例
解释:如果写入Hello.class,那么就代表在当前的类路径下面肯定又要这样的一个类存在

@ConditionalOnBean:当前上下文存在某个类的实例,才会创建对应的bean实例
解释:所谓实例,就是已经被spring实例化过了,比如一般需要在类上加一些注解@Component
@Service @Configuration @Respositry …..
@ConditionalOnMissingBean:和上面整好相反,不在对应的实例,创建当前bean的实例

@ConditionalOnMissingClass:不存在某个类,就创建当前bean的实例

@ConditionalOnProperty:根据配置文件中的属性条件,来创建当前bean的实例
解释:这个参数组合有好几种,下面一一列举
1 prefix and value
prefix:参数的前缀
value:参数前缀后面的字段名称

2 name and havingValue
name:完整的字段名称
havingValue:字段名称对应的值

3 value and matchIfMissing
value:配置文件里面参数的值 是否为true

看一下这个注解的源码:

Spring Boot整合Rocketmq_第17张图片

@ConditionalOnExpression:根据表达式的值,来创建当前bean的实例

解释:目前该值只能是boolean类型,也就是说是true或者false,在熟悉这些知识之后,优化一下上面的那个配置代码:

package com.springboot.rocketmq.two.config;

import javax.annotation.PostConstruct;

import com.springboot.rocketmq.two.bean.MessageEvent;
import lombok.extern.slf4j.Slf4j;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.stream.Collectors;
/**
* @Author 18011618
* @Date 19:36 2018/7/18
* @Function 通过使用指定的文件读取类 来加载配置文件到字段中
*/
@Configuration
@EnableConfigurationProperties(RocketMQProperties.class)
@Slf4j
@ConditionalOnProperty(prefix = "suning.rocketmq",value = "namesrvAddr")
public class RocketMQConfiguration {

   @Autowired
   private RocketMQProperties rocketMQProperties;

   //事件监听
   @Autowired
   private ApplicationEventPublisher publisher = null;

   private static boolean isFirstSub = true;

   private static long startTime = System.currentTimeMillis();

    /**
     * 容器初始化的时候 打印参数
     */
   @PostConstruct
   public void init() {
        System.out.println("配置信息:"+rocketMQProperties);
   }

   /**
    * 创建普通消息发送者实例
    * @return
    * @throws MQClientException
    */
   @Bean
    @ConditionalOnClass(DefaultMQProducer.class)
    @ConditionalOnMissingBean(DefaultMQProducer.class)
    @ConditionalOnProperty(prefix = "suning.rocketmq",value = "namesrvAddr")
   public DefaultMQProducer defaultProducer() throws MQClientException {
        System.err.println("create defaultProducer....");
      DefaultMQProducer producer = new DefaultMQProducer(
            rocketMQProperties.getProducerGroupName());
      producer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
      producer.setInstanceName(rocketMQProperties.getProducerInstanceName());
      producer.setVipChannelEnabled(false);
      producer.setRetryTimesWhenSendAsyncFailed(10);
      producer.start();
      log.info("rocketmq producer server is starting....");
      return producer;
   }

   /**
    * 创建支持消息事务发送的实例
    * @return
    * @throws MQClientException
    */
   @Bean
    @ConditionalOnProperty(prefix = "suning.rocketmq",value = "transactionProducerGroupName")
    @ConditionalOnClass(TransactionMQProducer.class)
    @ConditionalOnMissingBean(TransactionMQProducer.class)
   public TransactionMQProducer transactionProducer() throws MQClientException {
        System.err.println("create transactionProducer....");
      TransactionMQProducer producer = new TransactionMQProducer(
            rocketMQProperties.getTransactionProducerGroupName());
      producer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
      producer.setInstanceName(rocketMQProperties
            .getProducerTranInstanceName());
      producer.setRetryTimesWhenSendAsyncFailed(10);
      // 事务回查最小并发数
      producer.setCheckThreadPoolMinSize(2);
      // 事务回查最大并发数
      producer.setCheckThreadPoolMaxSize(2);
      // 队列数
      producer.setCheckRequestHoldMax(2000);
      producer.start();
      log.info("rocketmq transaction producer server is starting....");
      return producer;
   }

   /**
    * 创建消息消费的实例
    * @return
    * @throws MQClientException
    */
   @Bean
    @ConditionalOnProperty(prefix = "suning.rocketmq",value = "consumerGroupName")
    @ConditionalOnClass(DefaultMQPushConsumer.class)
    @ConditionalOnMissingBean(DefaultMQPushConsumer.class)
   public DefaultMQPushConsumer pushConsumer() throws MQClientException {
        System.err.println("create pushConsumer....");
      DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(
            rocketMQProperties.getConsumerGroupName());
      consumer.setNamesrvAddr(rocketMQProperties.getNamesrvAddr());
      consumer.setInstanceName(rocketMQProperties.getConsumerInstanceName());

      //判断是否是广播模式
      if (rocketMQProperties.isConsumerBroadcasting()) {
         consumer.setMessageModel(MessageModel.BROADCASTING);
      }
      //设置批量消费
      consumer.setConsumeMessageBatchMaxSize(rocketMQProperties
            .getConsumerBatchMaxSize() == 0 ? 1 : rocketMQProperties
            .getConsumerBatchMaxSize());

      //获取topic和tag
      List subscribeList = rocketMQProperties.getSubscribe();
      for (String sunscribe : subscribeList) {
         consumer.subscribe(sunscribe.split(":")[0], sunscribe.split(":")[1]);
      }

      // 顺序消费
      if (rocketMQProperties.isEnableOrderConsumer()) {
         consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(
                  List msgs, ConsumeOrderlyContext context) {
               try {
                  context.setAutoCommit(true);
                  msgs = filterMessage(msgs);
                  if (msgs.size() == 0)
                     return ConsumeOrderlyStatus.SUCCESS;
                  publisher.publishEvent(new MessageEvent(msgs, consumer));
               } catch (Exception e) {
                  e.printStackTrace();
                  return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
               }
               return ConsumeOrderlyStatus.SUCCESS;
            }
         });
      }
      // 并发消费
      else {

         consumer.registerMessageListener(new MessageListenerConcurrently() {

            @Override
            public ConsumeConcurrentlyStatus consumeMessage(
                  List msgs,
                  ConsumeConcurrentlyContext context) {
               try {
                   //过滤消息
                  msgs = filterMessage(msgs);
                  if (msgs.size() == 0)
                     return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                  publisher.publishEvent(new MessageEvent(msgs, consumer));
               } catch (Exception e) {
                  e.printStackTrace();
                  return ConsumeConcurrentlyStatus.RECONSUME_LATER;
               }

               return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
         });
      }
      new Thread(new Runnable() {
         @Override
         public void run() {
            try {
               Thread.sleep(5000);

               try {
                  consumer.start();
               } catch (Exception e) {
                        e.printStackTrace();
               }
               log.info("rocketmq consumer server is starting....");
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }

      }).start();

      return consumer;
   }

    /**
     * 消息过滤
     * @param msgs
     * @return
     */
   private List filterMessage(List msgs) {
      if (isFirstSub && !rocketMQProperties.isEnableHistoryConsumer()) {
         msgs = msgs.stream()
               .filter(item -> startTime - item.getBornTimestamp() < 0)
               .collect(Collectors.toList());
      }
      if (isFirstSub && msgs.size() > 0) {
         isFirstSub = false;
      }
      return msgs;
   }

}

ok 到此为止本篇文章就讲解完了,谢谢!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(SpringBoot系列)