消息中间件RocketMQ(三):RocketMQTemplate的比较使用

依赖

		
            org.apache.rocketmq
            rocketmq-spring-boot-starter
            2.0.3
        
        
            org.apache.rocketmq
            rocketmq-client
            4.5.1
        
        
            org.apache.rocketmq
            rocketmq-common
            4.5.1
        
  • 说明:rocketmq-spring-boot-starter的依赖包如果不能直接从中央仓库下载的,需要自己通过源码install到本地仓库的。

    • 源码地址:https://github.com/apache/rocketmq-spring
    • 进入源码目录,执行如下命令:mvn clean install
    • 执行完上述两步 依赖会自动加入本地的maven仓库
  • 附:如何把jar包加入本地maven仓库的方法

    • 下载好要使用的jar包
    • 把要本地maven仓库该jar包的文件夹路径创建好 不然找不到指定路径(注意文件里要删干净)
      • 文件夹路径:groupId/artifactId/version
      #例如:
      
          org.apache.rocketmq
          rocketmq-spring-boot-starter
          2.0.3
      
      
      #在本地maven仓库中创建的文件夹路径
      C:\Users\用户名\.m2\repository\org\apache\rocketmq\rocketmq-spring-boot-starter\2.0.3
      
    • 执行命令

      mvn install:install-file -Dfile=jar包的绝对路径 -DgroupId=依赖的groupId -DartifactId=依赖的artifactId -Dversion=依赖的version -Dpackaging=jar

      mvn install:install-file -Dfile=C:\Download\rocketmq-spring-boot-starter-2.0.3.jar -DgroupId=org.apache.rocketmq -DartifactId=rocketmq-spring-boot-starter -Dversion=2.0.3 -Dpackaging=jar
      

简单使用

生产者
1.配置文件

#必须配置
#指定nameServer
rocketmq.nameServer=192.168.56.129:9876 
#指定发送者组名 相当于rabbitmq的virtual host 逻辑上的划分
rocketmq.producer.group=my-group

#其他可选配置
rocketmq.producer.send-message-timeout=300000
rocketmq.producer.compress-message-body-threshold=4096
rocketmq.producer.max-message-size=4194304
rocketmq.producer.retry-times-when-send-async-failed=0
rocketmq.producer.retry-next-server=true
rocketmq.producer.retry-times-when-send-failed=2

2.发送消息

    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    public void send(){
    	//实体类User
        User user=new User(999L,"testUser");
        //发送自定对象
        rocketMQTemplate.convertAndSend("test_topic",user);
    }

消费者
1.配置文件

#指定nameServer
rocketmq.nameServer=192.168.56.129:9876 

2.接收消息

@RocketMQMessageListener(
        topic = "test_topic", 					//topic:和消费者发送的topic相同
        consumerGroup = "test_my-consumer",     //group:不用和生产者group相同
        selectorExpression = "*") 			    //tag
@Component  //必须注入spring容器
//泛型必须和接收的消息类型相同
public class TestListner implements RocketMQListener {

    @Override
    public void onMessage(User user) {
        System.out.println(user);
    }
}
  • 注意:测试的时候尽管默认集群模式 但还是多个消费者的时候重复消费

顺序消费

生产者

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    public void send(){
        rocketMQTemplate.setMessageQueueSelector((List mqs, Message msg, Object arg)->{
            /**
             * mqs:要发送消息的topic下的所有消息队列集合
             * msg:发送的消息
             * arg:发送消息时传递的参数 通过该参数指定发送到哪个队列
             */
            int queueNum = Integer.valueOf(String.valueOf(arg)) % mqs.size();
            System.out.println("队列id:"+queueNum+" 消息:"+new String(msg.getBody()));
            return mqs.get(queueNum);
        });
        
        for(int i=1;i<=100;i++){
            String msg="type:"+i%4+" value:"+i;
            rocketMQTemplate.syncSendOrderly("test_topic",msg, String.valueOf(i));
        }
    }

消费者

@RocketMQMessageListener(
        topic = "test_topic",
        consumerGroup = "test_my-consumer",
        selectorExpression = "*",
        messageModel = MessageModel.CLUSTERING,
        consumeMode = ConsumeMode.ORDERLY
        )
@Component
public class TestListner implements  RocketMQListener{

    @Override
    public void onMessage(MessageExt messageExt) {
        System.out.println(Thread.currentThread().getName() + " onMessage: " + new String(messageExt.getBody()));
    }
}

  • 结论:一个queue中的消息体会由一个消费者处理,从而实现顺序消费消息。
  • 注意
    • 只有在一个消费者节点 顺序消费才实验成功 每个topic的队列会绑定这唯一一个消费者
      消息中间件RocketMQ(三):RocketMQTemplate的比较使用_第1张图片
    • 当有多个消费节点 会出现消费队列没有消费者终端绑定的情况 并且多个消费者都重复消费
      • 下面是四个消费者节点的情况 消息中间件RocketMQ(三):RocketMQTemplate的比较使用_第2张图片
      • 结果有三个节点重复且只消费第一个队列 下面是那三个节点消费情况截图(可以看出确实是只有一个线程在消费一个队列)
        消息中间件RocketMQ(三):RocketMQTemplate的比较使用_第3张图片
        消息中间件RocketMQ(三):RocketMQTemplate的比较使用_第4张图片
      • 还有一个节点消费混乱
    • 当使用集群进行顺序消费测试时 也出现了多个节点同时监听消费部分队列的情况

事务消费

生产者

@Component 
public class SpringTransactionProducer { 

	@Autowired 
	private RocketMQTemplate rocketMQTemplate;
	
	 /**
	 * 发送消息 
	 * @param topic  
	 * @param msg 
	 */ 
	 public void sendMsg(String topic, String msg) { 
	 	/**
	 	 * 这里的Message不是rocketmq.commen的 
	 	 * 是springframework的接口
	 	 * /
		 Message message = MessageBuilder.withPayload(msg).build(); 
		 
		 /**
		  * myTransactionGroup要和@RocketMQTransactionListener(txProducerGroup = "myTransactionGroup")定义的Group一致 
		  * 消息会通过TransactionGroup找到事务消费者、通过topic普通消费者 只有事务消费者commit 普通消费者的结果才会执行 
		  * /
		 this.rocketMQTemplate.sendMessageInTransaction("myTransactionGroup", topic, message, null); 
		 
		 System.out.println("发送消息成功"); 
	 } 
 }

消费者
1.通过topic找到的消费者

  • 代码不变 正常执行消费逻辑
@RocketMQMessageListener(
        topic = "test_topic", 				
        consumerGroup = "test_my-consumer",  
        selectorExpression = "*") 			 
@Component 
public class TestListner implements RocketMQListener {

    @Override
    public void onMessage(String msg) {
        System.out.println(msg);
    }
}

2.通过TransactionGroup找到的事务消费者

  • 决定一起发送的 通过topic路由到的普通消费者的执行结果 是否提交或回滚
@RocketMQTransactionListener(txProducerGroup = "myTransactionGroup") 
public class TransactionListenerImpl implements RocketMQLocalTransactionListener { 

	/**
	 * 可以定义一个static final的Map 用来保存返回unknown要回查消息的一些属性 那么所有对象都可以获取该消息回滚前的一些信息
	 * private static Map INFO_MAP = new HashMap<>();
	 */
	
	@Override 
	public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) { 
		 /**
		   * 该Message是springframework包下的 其获取事务消息的唯一id的方法
		   * String transId = (String)message.getHeaders().get(RocketMQHeaders.TRANSACTION_ID); 
		   * /
		try {
			System.out.println("执行操作1"); 
			Thread.sleep(500); 
			
			System.out.println("执行操作2"); 
			Thread.sleep(800); 
			

	   	    if(...) return LocalTransactionState.COMMIT_MESSAGE

            if(...) return LocalTransactionState.ROLLBACK_MESSAGE

            //如果在检查事务时数据库出现宕机可以让broker过一段时间回查 和return null 效果相同
             if(...) return LocalTransactionState.UNKNOW 

		} catch (Exception e) { 
			e.printStackTrace(); 
			/**
			 * 回滚
			 * 
			 * 可以在该处给 INFO_MAP放一些信息 以便会查时调用
			 * INFO_MAP.put(transId,...);
			 * /
			return RocketMQLocalTransactionState.ROLLBACK;
		}
		
	}

	@Override 
	public RocketMQLocalTransactionState checkLocalTransaction(Message message) { 
		/**
		 * 只去返回commit或者rollback
		 * 
		 * 可以用INFO_MAP取得一些回滚前的信息
		 * String transId = (String)message.getHeaders().get(RocketMQHeaders.TRANSACTION_ID); 
		 * INFO_MAP.get(transId);
		 * /
    } 
}

其他事项

  • 如果消费者想使用原生消息
    implements  RocketMQListener
    
  • 如果消费者想使用原生DefaultMQPushConsumer
    implements RocketMQPushConsumerLifecycleListener
    

总结

  • 使用RocketMQTemplate收发消息总出现bug 个人感觉还是spring的好用 就是稍微复杂一点
  • 如何快速使用原生的api参考下篇博客:https://blog.csdn.net/weixin_43934607/article/details/102756379

你可能感兴趣的:(消息队列,搜索引擎)