RocketMq学习

本文介绍了RocketMQ的学习,包括JMS规范、消息传输模型(点对点和发布订阅)、RocketMQ架构及其优于Kafka的消息可靠性。 RocketMQ启动涉及namesrv和broker服务,基本概念涵盖Topic、Tag、MessageQueue、NameServer、Broker等。文章还讨论了消息过滤、可靠性、消费方式(集群消费与广播消费)以及消息持久化。RocketMQ提供DefaultMQPushConsumer和DefaultMQPullConsumer两种消息获取方式,并提供了Console进行管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、JMS概述:Java Message Service规范,规定不同Java客户端之间如何进行消息传输

2、JMS消息传输模型:P2P(点对点)和Pub/Sub(发布订阅)

(1)P2P模型

组成:消息队列(Queue)、发送者(Sender)、接收者(Receiver)

特征:每个消息只有一个消费者,一旦被消费,即从消息队列中删除,如果希望每个消息都被成功处理,则应该使用P2P模型

(2)Pub/Sub模型

组成:主题(Topic)、发布者(Publisher)、订阅者(Subscriber)
特征:如果希望发送的消息可以不做任何处理,或者被一个或多个消费者消费,则应该采用Pub/Sub模型

3、Rocketmq架构


Rocketmq使用了Pub/Sub模型设计实现

Rocketmq设计时参考了kafka,消息可靠性比后者更好

4、Rocketmq启动

进入bin目录

cmd运行  mqnamesrv  -n  127.0.0.1:9876  启动nameserver服务

cmd运行  mqbroker  -n 127.0.0.1:9876    启动broker服务


5、Rocketmq基本概念

(1)Topic:消息逻辑管理单位

           一个Producer可以生产多个Topic

          一个Consumer可以订阅多个Topic

 (2)Tag:用于消息细分,一个Topic下可以有多个Tag

 (3)MessageQueue:消息物理管理单位,一个Topic可以对应多个Queue,默认情况下一个Topic分配4个队列,Producer生产的消息随机?发送到队列中

 (4)NameServer:相当于ZooKeeper的作用

                   ---- NameServer用来保存活跃的 broker 列表,包括 Master 和 Slave

                   ---- NameServer用来保存所有 topic 和该 topic 所有队列的列表

                   ---- NameServer用来保存所有 broker 的 Filter 列表

(5)  Broker:不负责推送消息,只负责存储消息,Consumer主动从broker请求消息

          Rocketmq提供了两种存储方式来保留消费记录:一种是保留在consumer所在的服务器上;另一种是保存在broker服务器上。用户还可以自己实现相应的消费进度存储接口。默认情况下,采用集群消费(CLUSTERING),会将记录保存在broker端;而采用广播消费(BROADCASTING)则会将消费记录保存在本地

(6)Message:消息对象模型,由Topic、Tag、Keys、body(byte[])构建

(7)ConsumerGroup:

(8)ProducerGroup:

6、消息过滤

7、消息可靠性

8、消费者消费两种方式

      集群消费:同一个ConsumerGroup下有多个消费实例,生产的消息将被随机(轮询)方式发送到各个实例,单个实例只消费总量的一部分,默认方式

     广播消费:类似ActiveMQ中的发布订阅模式,每一条消息都会发给Consume Group中的每一个消费者进行消费

8、消息顺序消费

9、消息持久化

10、消息获取方式

     DefaultMQPushConsumer
     DefaultMQPullConsumer

11、Rocketmq Console

12、代码实例

(1)producer生产多个topic消息,失败则默认重试2次

public static void main(String[] args) throws MQClientException, InterruptedException {
		
        /**
         * 一个应用创建一个Producer,由应用来维护此对象,可以设置为全局对象或者单例<br>
         * 注意:ProducerGroupName需要由应用来保证唯一<br>
         * ProducerGroup这个概念发送普通的消息时,作用不大,但是发送分布式事务消息时,比较关键,
         * 因为服务器会回查这个Group下的任意一个Producer
         */
	    DefaultMQProducer producer = new DefaultMQProducer("producer_test");
        //在本地搭建好broker后,记得指定nameServer的地址
        producer.setNamesrvAddr("127.0.0.1:9876");
//        producer.setRetryTimesWhenSendFailed(3); // 发送消息到broker失败,重试次数,默认是2次

        /**
         * Producer对象在使用之前必须要调用start初始化,初始化一次即可<br>
         * 注意:切记不可以在每次发送消息时,都调用start方法
         */
        producer.start();
        
        /**
         * 下面这段代码表明一个Producer对象可以发送多个topic,多个tag的消息。
         * 注意:send方法是同步调用,只要不抛异常就标识成功。但是发送成功也可会有多种状态,<br>
         * 例如消息写入Master成功,但是Slave不成功,这种情况消息属于成功,但是对于个别应用如果对消息可靠性要求极高,<br>
         * 需要对这种情况做处理。另外,消息可能会存在发送失败的情况,失败重试由应用来处理。
         */
         //用一个short标识生产者停止生产数据
        byte [] zero = new  byte[]{0,0};
        Message endMessage = new Message("EndSignal", zero);

        
        Message msg = new Message("TopicTest1",// topic
                "TagA",// tag
                "OrderID001",// key
                ("Hello MetaQ111").getBytes());// body
        
        Message msg2 = new Message("TopicTest2",// topic
                "TagB",// tag
                "OrderID0034",// key
                ("Hello MetaQ222").getBytes());// body
        
        Message msg3 = new Message("TopicTest3",// topic
                "TagC",// tag
                "OrderID0035",// key
                ("Hello MetaQ333").getBytes());// body
        
        try {
        	
//          SendResult sendResult =  producer.send(msg, timeout);  // 发送消息超时时间配置,默认是3000毫秒
            for(int i=0;i<30;i++){
            	Message msgA = new Message("TopicTest1","TagA","OrderID" + i,("Hello MetaQ"+i).getBytes());
            	producer.send(msgA);
            }
            System.out.println("TopicTest1" + "----" + "TagA 发送成功");
            
            SendResult sendResult2 = producer.send(msg2);
            System.out.println("TopicTest2" + "----" + "TagB 发送成功");
            
            SendResult sendResult3 = producer.send(msg3);
            System.out.println("TopicTest3" + "----" + "TagC 发送成功");
            
            SendResult sendResult_over = producer.send(endMessage);
            System.out.println("EndSignal 发送成功");
            
        } catch (Exception e) {
            e.printStackTrace();
            
            // 生产者发送消息失败,自动重新发送
            Thread.sleep(1000);
        }
        
        /**
         * 应用退出时,要调用shutdown来清理资源,关闭网络连接,从MetaQ服务器上注销自己
         * 注意:我们建议应用在JBOSS、Tomcat等容器的退出钩子里调用shutdown方法
         */
        producer.shutdown();
	}

(2)使用长连接轮询方式拉取消息

public static void main(String[] args) throws MQClientException {
		
	    /**
	     * 当前例子是PushConsumer用法,使用方式给用户感觉是消息从RocketMQ服务器推到了应用客户端。<br>
	     * 但是实际PushConsumer内部是使用长轮询Pull方式从Broker拉消息,然后再回调用户Listener方法<br>
	     */
	     /**
         * 一个应用创建一个Consumer,由应用来维护此对象,可以设置为全局对象或者单例<br>
         * 注意:ConsumerGroupName需要由应用来保证唯一
         */
		  DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_test");

	        /**
	         * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费<br>
	         * 如果非第一次启动,那么按照上次消费的位置继续消费
	         */
	        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

	        // 在本地搭建好broker后,指定nameServer地址
	        consumer.setNamesrvAddr("127.0.0.1:9876");
	        
	        // 设置消费方式,集群消费还是广播消费,默认是集群消费,即消息随机轮询发送到同一个ConsumerGroup下所有消费者
	        consumer.setMessageModel(MessageModel.BROADCASTING);

	        /**
	         * 订阅指定topic下tags分别等于TagA或TagC或TagD
	         */
	        consumer.subscribe("TopicTest1", "TagA || TagC || TagD");
	        /**
	         * 订阅指定topic下所有消息<br>
	         * 注意:一个consumer对象可以订阅多个topic
	         */
	        consumer.subscribe("TopicTest2", "*");
	        
	        /**
	         * 没有这样的写法!!!!!
	         */
//	        consumer.subscribe("*", "*");
	        
	        consumer.subscribe("EndSignal", "*");
	        
	        consumer.registerMessageListener(new MessageListenerConcurrently() {
				
	        	
	            /**
	             * * 默认msgs里只有一条消息,可以通过设置consumeMessageBatchMaxSize参数来批量接收消息
	             */
				@Override
				public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
				
					for(MessageExt msg : msgs){
						
						// 开始消费
						try {
							System.out.println(msg);
							byte [] body = msg.getBody();
		                    if (body.length == 2 && body[0] == 0 && body[1] == 0) {
		                    	System.out.println("消息接收完毕");
		                        continue;
		                    }
		                    if (msg.getTopic().equals("TopicTest1")) {
		                        // 执行TopicTest1的消费逻辑
		                        if (msg.getTags() != null && msg.getTags().equals("TagA")) {
		                            // 执行TagA的消费
		                        } else if (msg.getTags() != null && msg.getTags().equals("TagC")) {
		                            // 执行TagC的消费
		                        } else if (msg.getTags() != null && msg.getTags().equals("TagD")) {
		                            // 执行TagD的消费
		                        }
		                    } else if (msg.getTopic().equals("TopicTest2")) {
		                        // 执行TopicTest2的消费逻辑
		                    }
						} catch(Exception e){
							// 消费时发生异常
							// messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m ......1h 2h
							if(msg.getReconsumeTimes() == 3){
								return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
							}
							return ConsumeConcurrentlyStatus.RECONSUME_LATER;
						}
					}
					return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
				}
			});
	        
	        
	        /**
	         * Consumer对象在使用之前必须要调用start初始化,初始化一次即可<br>
	         */
	        consumer.start();
	        System.out.println("consumer start");
	}

(3)使用主动pull模式拉取消息

public static void main(String[] args) throws MQClientException {
		final DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("consumerPullLiu");
		String topic = "TopicTest1";
		consumer.setNamesrvAddr("127.0.0.1:9876");
		consumer.start();
		
		try {
			Set<MessageQueue> mqs = consumer.fetchSubscribeMessageQueues(topic);
			System.out.println("mqs.size() " + mqs.size()); 
			// 必须加上此监听才能在消费过后,自动回写消费进度
			consumer.registerMessageQueueListener(topic, null);
			
			// 循环每一个队列
			for(MessageQueue mq: mqs){
				System.out.println("consume message from queue: " + mq + " mqsize = " + mqs.size());
				int cnter = 0;
				// 每个队列里无限循环,分批拉取未消费的消息,直到拉取不到新消息为止
				SINGLE_MQ:while(cnter++ < 100){
					long offset = consumer.fetchConsumeOffset(mq, false);
					offset = offset < 0 ? 0:offset;
					System.out.println("消费进度 offset: " + offset);
					PullResult result = consumer.pull(mq, null, offset, 10);
					System.out.println("接收到的消息集合 " + result);
					
					switch(result.getPullStatus()){
					case FOUND:
						if(result.getMsgFoundList() != null){
							int prSize = result.getMsgFoundList().size();
							System.out.println("pullResult.getMsgFoundList().size()====" + prSize);
							if(prSize != 0){
								for(MessageExt me : result.getMsgFoundList()) {
									// 消费每条消息,如果消费失败,比如更新数据库失败,就重新再拉取一次消息
	                                System.out.println("pullResult.getMsgFoundList()消息体内容===="+ new String(me.getBody()));
								}
							}
						}
						   // 获取下一个下标位置
	                    offset = result.getNextBeginOffset();
	                    // 消费完后,更新消费进度
	                    consumer.updateConsumeOffset(mq, offset);
						break;
					case NO_MATCHED_MSG:
						System.out.println("没有匹配的消息");
						break;
					case NO_NEW_MSG:
						System.out.println("没有未消费的新消息");
						// 拉取不到新消息,跳出SINGLE_MQ当前队列循环,开始下一队列循环
						break SINGLE_MQ;
					case OFFSET_ILLEGAL:
						System.out.println("下标错误");
						break;
					default:
						break;
					}
				}
			}
 		} catch(Exception ex){
			System.out.println(ex.getMessage());
		}
		
		 // 定义一个定时器,用于测试pull方法时,模拟延时以便自动更新消费进度操作,生产环境中,因consumer一直在运行,因此不需要此步操作。
	    final Timer timer = new Timer("TimerThread", true);
	    // 定时器延时30秒后,关闭cousumer,因为客户端从首次启动时在1000*10ms即10秒后,后续每5秒定期执行一次(由参数:persistConsumerOffsetInterval控制)向本机及broker端回写记录消费进度,
	    // 因此consumer启动后需要延时至少15秒才能执行回写操作,否则下次运行pull方法时,因上次未能及时更新消费进度,程序会重复取出上次消费过的消息重新消费,所以此处延时30秒,留出回写的时间
	    timer.schedule(new TimerTask() {
	        @Override
	        public void run() {
	            consumer.shutdown();
	            // 如果只要这个延迟一次,用cancel方法取消掉.
	            this.cancel();
	        }
	    }, 30000);
	}




### RocketMQ学习教程:从入门到精通 #### 了解RocketMQ基础概念 RocketMQ 是一个开源的分布式消息队列系统,最初由阿里巴巴集团开发并于2012年开源。该系统提供了可靠的消息传递服务,能够支持大规模并发消息发布和订阅,具备低延迟、高吞吐量以及高可用性的特点[^2]。 #### 安装与配置环境 为了更好地理解和实践RocketMQ的功能,安装并配置好运行环境是非常重要的一步。对于可视化管理界面的选择,可以考虑使用Apache官方维护的一个名为`rocketmq-dashboard`的项目来监控和管理系统状态;另一个可选方案则是通过`release-rocketmq-console-1.0.0`版本提供的控制台工具来进行日常运维工作。这两个面板的操作方式基本一致,在完成下载后(例如放置于路径 `D:\develop\rocketmq-externals-release-rocketmq-console-1.0.0`),可以通过IDEA等集成开发环境中跳过测试环节直接利用Maven构建项目[^1]。 #### 启动Broker实例 当准备好所有必要的组件之后,就可以按照如下命令行指令启动Broker节点: ```bash nohup sh bin/mqbroker -n localhost:9876 & ``` 这将会以后台进程的方式执行,并且可以在指定的日志文件中追踪其运行状况: ```bash tail -f ~/logs/rocketmqlogs/broker.log ``` #### 掌握核心特性 深入理解RocketMQ的核心特性和高级应用是成为专家的关键所在。比如掌握如何实现消息的有序发送、处理事务型消息等功能,这些都是在实际生产环境中非常有用的技术点。此外,还需要熟悉不同类型的消费者模式及其适用场景,以便根据具体需求选择最合适的设计策略。 #### 实践案例研究 最后但同样重要的是参与真实的项目练习或是模仿一些典型的应用场景,如电子商务平台中的订单处理流程、实时数据分析管道建设等等。这些实践活动不仅有助于巩固理论知识,更能培养解决复杂问题的能力。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值