RocketMQ笔记(七):普通消息
普通消息为 RocketMQ 中最基础的消息,支持生产者和消费者的异步解耦通信。
一、普通消息的生命周期
1、初始化
消息被生产者构建并完成初始化,待发送到服务端的状态。
2、待消费
消息被发送到服务端,对消费者可见,等待消费者消费的状态。
3、消费中
消息被消费者获取,并按照消费者本地的业务逻辑进行处理的过程。此时服务端会等待消费者完成消费并提交消费结果,如果一定时间后没有收到消费者的响应,RocketMQ会对消息进行重试处理。
4、消费提交
消费者完成消费处理,并向服务端提交消费结果,服务端标记当前消息已经被处理(包括消费成功和失败)。 RocketMQ默认支持保留所有消息,此时消息数据并不会立即被删除,只是逻辑标记已消费。消息在保存时间到期或存储空间不足被删除前,消费者仍然可以回溯消息重新消费。
5、消息删除
RocketMQ按照消息保存机制滚动清理最早的消息数据,将消息从物理文件中删除。
二、普通消息的发送方式
RocketMQ提供了三种发送消息的模式,分别为同步发送消息、异步发送消息、单向发送消息。
1 public enum CommunicationMode { 2 SYNC, // 同步 3 ASYNC, // 异步 4 ONEWAY; // 单向 5 6 private CommunicationMode() { 7 } 8 }
1、同步发送
同步发送是指消息发送方发出数据后,同步等待,直到收到接收方发回响应之后才发下一个请求。
2、异步发送
消息发送方在发送了一条消息后,不等接收方发回响应,接着进行第二条消息发送。发送方通过回调接口的方式接收服务器响应,并对响应结果进行处理。
异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应。
3、单向发送
单向(Oneway)发送特点为发送方只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答。
单向发送方式发送消息的过程耗时非常短,一般在微秒级别。不需要关心发送结果的场景,例如日志发送。
三、普通消息的消费方式
1 // 消息消费模式 2 public enum MessageModel { 3 // 广播 4 BROADCASTING("BROADCASTING"), 5 // 负载均衡 6 CLUSTERING("CLUSTERING"); 7 private String modeCN; 8 private MessageModel(String modeCN) { 9 this.modeCN = modeCN; 10 } 11 public String getModeCN() { 12 return this.modeCN; 13 } 14 }
在实际的开发过程中,使用consumer的setMessageModel()方法,指定消费方式。
1、负载均衡消费模式 - CLUSTERING
消费者采用负载均衡方式消费消息,一个分组(Group)下的多个消费者共同消费队列消息,每个消费者处理的消息不同。
一个Consumer Group中的各个Consumer实例分摊去消费消息,即一条消息只会投递到一个Consumer Group下面的一个实例。
例如某个Topic有3个队列,其中一个Consumer Group 有 3 个实例,那么每个实例只消费其中的1个队列。集群消费模式是消费者默认的消费方式。
2、广播消费模式 - BROADCASTING
广播消费模式中消息将对一个Consumer Group下的各个Consumer实例都投递一遍。即使这些 Consumer属于同一个Consumer Group,消息也会被Consumer Group 中的每个Consumer都消费一次。
实际上,是一个消费组下的每个消费者实例都获取到了topic下面的每个Message Queue去拉取消费。所以消息会投递到每个消费者实例。
3、消费模式特点
1、负载均衡消费模式
消费端集群化部署,每条消息只需要被处理一次;
每一条消息都只会被分发到一台机器上处理;
不保证每一次失败重投的消息路由到同一台机器上。
2、集群消费模式
每条消息都需要被相同逻辑的多台机器处理;
消息队列 RocketMQ 保证每条消息至少被每台客户端消费一次,但是并不会对消费失败的消息进行失败重投;
客户端每一次重启都会从最新消息消费。客户端在被停止期间发送至服务端的消息将会被自动跳过;
广播消费模式下不支持重置消费位点,广播模式下服务端不维护消费进度,所以消息队列 RocketMQ 控制台不支持消息堆积查询、消息堆积报警和订阅关系查询功能;
广播消费模式下不支持顺序消息。
四、普通消息的示例demo
工具类详见:RocketMQ笔记(六):示例代码工具类。
1、消息发送封装类 SDKSendMsg
1 import org.apache.rocketmq.client.exception.MQClientException; 2 import org.apache.rocketmq.client.producer.DefaultMQProducer; 3 import org.apache.rocketmq.client.producer.RequestCallback; 4 import org.apache.rocketmq.client.producer.SendResult; 5 import org.apache.rocketmq.common.message.Message; 6 import org.apache.rocketmq.remoting.common.RemotingHelper; 7 import java.util.Objects; 8 9 /** 10 * @Description: 发送消息方式 11 */ 12 public class SDKSendMsg { 13 // 默认组 14 private final static String DEFAULT_GROUP_NAME = "test-group"; 15 // 测试地址 16 private final static String DEFAULT_NAMESRV_ADDR = "192.168.33.55:9876"; 17 // 默认Topic 18 public final static String DEFAULT_TOPIC = "TestTopic"; 19 // 同步消息的标签与键 20 public final static String DEFAULT_SYNC_MSG_TAG = "SYNC_TAG"; 21 public final static String DEFAULT_SYNC_MSG_KEY = "SYNC_KEY"; 22 // 异步消息的标签与键 23 public final static String DEFAULT_ASYNC_MSG_TAG = "ASYNC_TAG"; 24 public final static String DEFAULT_ASYNC_MSG_KEY = "ASYNC_KEY"; 25 // 单向 26 public final static String DEFAULT_ONEWAY_MSG_TAG = "ONEWAY_TAG"; 27 public final static String DEFAULT_ONEWAY_MSG_KEY = "ONEWAY_KEY"; 28 29 // 声明生产者 30 private DefaultMQProducer producer; 31 32 public SDKSendMsg() { 33 // 启动生产者实例 34 try { 35 // 实例化生产者组名称 36 DefaultMQProducer producer = new DefaultMQProducer(DEFAULT_GROUP_NAME); 37 // 指定name server地址 38 producer.setNamesrvAddr(DEFAULT_NAMESRV_ADDR); 39 this.producer = producer; 40 this.producer.start(); 41 } catch (MQClientException e) { 42 e.printStackTrace(); 43 } 44 } 45 46 /** 47 * 关闭生产者实例 48 */ 49 public void shutdownProducer() { 50 this.producer.shutdown(); 51 } 52 53 54 /** 55 * 同步发送消息, 使用默认超时时间 56 * @param topic 主题 57 * @param msgTag 消息标签 58 * @param msgKey 消息key 59 * @param msgBody 消息内容 60 * @return 61 */ 62 public SendResult syncSendMsg(String topic, String msgTag, String msgKey, String msgBody) { 63 return syncSendMsg(topic, msgTag, msgKey, msgBody, null); 64 } 65 66 /** 67 * 同步发送消息, 使用指定的超时时间 68 * @param topic 主题 69 * @param msgTag 消息标签 70 * @param msgKey 消息key 71 * @param msgBody 消息内容 72 * @param timeout 超时时间 73 * @return 74 */ 75 public SendResult syncSendMsg(String topic, String msgTag, String msgKey, String msgBody, Long timeout) { 76 System.out.println("发送同步消息: " + msgBody); 77 try { 78 Message msg = new Message(topic, msgTag, msgKey, msgBody.getBytes(RemotingHelper.DEFAULT_CHARSET)); 79 SendResult send; 80 if (Objects.isNull(timeout)) { 81 send = this.producer.send(msg); 82 System.out.printf("消息发送结果:%s%n", send); 83 return send; 84 } 85 send = this.producer.send(msg, timeout); 86 System.out.printf("消息发送结果:%s%n", send); 87 return send; 88 } catch (Exception e) { 89 e.printStackTrace(); 90 } 91 return null; 92 } 93 94 /** 95 * 异步发送消息,使用默认的回调处理 96 * @param topic 主题 97 * @param msgTag 消息标签 98 * @param msgKey 消息key 99 * @param msgBody 消息内容 100 * @param timeout 超时时间 101 * @return 102 */ 103 public Message asynSendMsg(String topic, String msgTag, String msgKey, String msgBody, Long timeout) { 104 System.out.println("发送异步消息: " + msgBody); 105 try { 106 Message msg = new Message(topic, msgTag, msgKey, msgBody.getBytes(RemotingHelper.DEFAULT_CHARSET)); 107 return this.producer.request(msg, timeout); 108 } catch (Exception e) { 109 e.printStackTrace(); 110 } 111 return null; 112 } 113 114 /** 115 * 异步发送消息,自定义回调处理 116 * @param topic 主题 117 * @param msgTag 消息标签 118 * @param msgKey 消息key 119 * @param msgBody 消息内容 120 * @param timeout 超时时间 121 * @param callback 回调处理 122 */ 123 public void asynSendMsg(String topic, String msgTag, String msgKey, String msgBody, Long timeout, RequestCallback callback) { 124 System.out.println("发送异步消息: " + msgBody); 125 try { 126 Message msg = new Message(topic, msgTag, msgKey, msgBody.getBytes(RemotingHelper.DEFAULT_CHARSET)); 127 this.producer.request(msg, callback, timeout); 128 } catch (Exception e) { 129 e.printStackTrace(); 130 } 131 } 132 133 /** 134 * 发送单向消息 135 * @param topic 136 * @param msgTag 137 * @param msgKey 138 * @param msgBody 139 */ 140 public void sendOneWay(String topic, String msgTag, String msgKey, String msgBody) { 141 System.out.println("发送单向消息: " + msgBody); 142 try { 143 Message msg = new Message(topic, msgTag, msgKey, msgBody.getBytes(RemotingHelper.DEFAULT_CHARSET)); 144 this.producer.sendOneway(msg); 145 } catch (Exception e) { 146 e.printStackTrace(); 147 } 148 } 149 }
2、生产者 ProducerMsg
1 import org.apache.rocketmq.client.producer.RequestCallback; 2 import org.apache.rocketmq.common.message.Message; 3 /** 4 * @Description: 发送消息 5 */ 6 public class ProducerMsg { 7 public static void main(String[] args) { 8 // 创建消息发送实例 9 SDKSendMsg sdkSendMsg = new SDKSendMsg(); 10 11 // 同步消息发送 12 String syncMsg = "同步消息 -- " + 0; 13 sdkSendMsg.syncSendMsg(SDKSendMsg.DEFAULT_TOPIC, SDKSendMsg.DEFAULT_SYNC_MSG_TAG, 14 SDKSendMsg.DEFAULT_SYNC_MSG_KEY, syncMsg); 15 16 // 异步消息发送 17 String asynMsg = "异步消息 --" + 1; 18 sdkSendMsg.asynSendMsg(SDKSendMsg.DEFAULT_TOPIC, SDKSendMsg.DEFAULT_ASYNC_MSG_TAG, 19 SDKSendMsg.DEFAULT_ASYNC_MSG_KEY, asynMsg, 3000l, new RequestCallback() { 20 @Override 21 public void onSuccess(Message message) { 22 System.out.println("异步消息发送成功"); 23 // TODO 业务数据状态更新 24 } 25 @Override 26 public void onException(Throwable throwable) { 27 System.out.println("异步消息发送失败"); 28 // TODO 业务状态回滚 29 } 30 }); 31 32 // 单向消息发送 33 String oneWayMsg = "单向消息 --" + 2; 34 sdkSendMsg.sendOneWay(SDKSendMsg.DEFAULT_TOPIC, SDKSendMsg.DEFAULT_ONEWAY_MSG_TAG, 35 SDKSendMsg.DEFAULT_ONEWAY_MSG_KEY, oneWayMsg); 36 37 // 销毁生产者实例 38 sdkSendMsg.shutdownProducer(); 39 } 40 }
3、消费者 ProducerMsg
1 import com.snails.rmq.common.RMQConstant; 2 import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; 3 import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; 4 import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; 5 import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; 6 import org.apache.rocketmq.client.exception.MQClientException; 7 import org.apache.rocketmq.common.message.MessageExt; 8 import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; 9 10 import java.io.UnsupportedEncodingException; 11 import java.util.List; 12 13 /** 14 * @Description: RocketMQ并发消费 15 */ 16 public class ConsumerMsg { 17 public static void main(String[] args) throws MQClientException { 18 19 // 实例化消费者组名称 20 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(RMQConstant.TEST_GROUP); 21 // 指定name server地址 22 consumer.setNamesrvAddr(RMQConstant.NAEMSRV_ADDR); 23 // 订阅至少一个主题以供消费 24 consumer.subscribe(RMQConstant.TEST_TOPIC, "*"); 25 // 负载均衡消费模式 26 consumer.setMessageModel(MessageModel.CLUSTERING); 27 // 广播消费模式 28 // consumer.setMessageModel(MessageModel.BROADCASTING); 29 // 注册回调,处理从服务端获取的消息 30 consumer.registerMessageListener(new MessageListenerConcurrently() { 31 @Override 32 public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { 33 for (MessageExt msg : msgs) { 34 System.out.println("当前消息的KEY: " + msg.getKeys()); 35 try { 36 if (SDKSendMsg.DEFAULT_SYNC_MSG_KEY.equals(msg.getKeys())) { 37 System.out.println(String.format("线程%s,接收同步消息:%s", Thread.currentThread().getName(), new String(msg.getBody(), "UTF-8"))); 38 } else if (SDKSendMsg.DEFAULT_ASYNC_MSG_KEY.equals(msg.getKeys())) { 39 System.out.println(String.format("线程%s,接收异步消息:%s", Thread.currentThread().getName(), new String(msg.getBody(), "UTF-8"))); 40 }else if (SDKSendMsg.DEFAULT_ONEWAY_MSG_KEY.equals(msg.getKeys())) { 41 System.out.println(String.format("线程%s,接收单向消息:%s", Thread.currentThread().getName(), new String(msg.getBody(), "UTF-8"))); 42 } 43 } catch (UnsupportedEncodingException e) { 44 // TODO 补偿机制 45 System.out.println(e.getMessage()); 46 } 47 } 48 // 消费消息确认 49 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; 50 } 51 52 }); 53 // 启动消费者实例 54 consumer.start(); 55 System.out.printf("消费者已启动.%n"); 56 } 57 }