以下是作者个人的理解,欢迎大家纠错
同步推送–等待broker响应;
如果broker集群采用主从数据同步模式,则会主从都收到此消息以后,给客户端响应;
如果broker集群采用主从异步数据模式,则主机收到消息则会给发送者响应;
异步推送—消息回执采用异步方式通知
单向推送–无须接受回执
public class PracticeTest {
private DefaultMQProducer start() throws MQClientException {
DefaultMQProducer producer = new
DefaultMQProducer("GID_TCP_GROUP");
// Specify name server addresses.
producer.setNamesrvAddr("127.0.0.1:9876");
//Launch the instance.
producer.start();
return producer;
}
//测试同步发送 适用于发送重要的消息
@Test
public void testSynSend() throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
DefaultMQProducer producer=start();
for (int i = 0; i < 10; i++) {
Message message=new Message("base","Tag1","hello world".getBytes());
//发送状态
SendResult sendResult=producer.send(message);
//消息id
String msgId=sendResult.getMsgId();
//消息队列
int queueId=sendResult.getMessageQueue().getQueueId();
System.out.println("消息发送状态:"+sendResult+",消息ID="+msgId+",队列="+queueId);//只发送到主broker上
}
producer.shutdown();
}
//发送异步消息
@Test
public void testAsySend() throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException {
DefaultMQProducer producer=start();
producer.setRetryTimesWhenSendAsyncFailed(0);
for (int i = 0; i < 10; i++) {
final int index=i;
Message message=new Message("TopicTest","TagA","hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
producer.send(message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.printf("%-10d OK %s %n",index,sendResult.getMsgId());
}
@Override
public void onException(Throwable e) {
System.out.printf("%-10d Exception %s %n",index);
}
});
}
TimeUnit.SECONDS.sleep(10);
producer.shutdown();
}
//单向消息
@Test
public void sendSigleSide() throws MQClientException, UnsupportedEncodingException, RemotingException, InterruptedException {
DefaultMQProducer producer = start();
producer.setRetryTimesWhenSendAsyncFailed(0);
Message message = new Message("TopicTest", "TagA", "hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
producer.sendOneway(message);
producer.shutdown();
}
消费者消费会从offest的最大值地方开始消费,如果是新的消费组,则最大offset都是-1;
–默认就是集群订阅的方式
–消费者指定为广播模式,该主题的消息就变成了广播消息
—一个queue中的消息本身就是有顺序的,先进先出的
–只能按照设定的延迟等级进行延迟
预设值的延迟时间间隔为:1s、 5s、 10s、 30s、 1m、 2m、 3m、 4m、 5m、 6m、 7m、 8m、 9m、 10m、 20m、 30m、 1h、 2h
public class Producer {
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
DefaultMQProducer producer = new
DefaultMQProducer("GID_TCP_GROUP");
// Specify name server addresses.
producer.setNamesrvAddr("127.0.0.1:9876");
//Launch the instance.
producer.start();
List orderSteps=OrderStep.buildOrders();
for (int i=0;i mqs, Message msg, Object arg) {
long orderId=(Long)arg;
long index=orderId%mqs.size();
return mqs.get((int)index);
}
},orderSteps.get(i).getOrderId());
System.out.println("发送状态:"+sendStatus);
}
}
}
public class Consumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer=new DefaultMQPushConsumer("group1");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("delayTopic","order");
consumer.setMessageModel(MessageModel.CLUSTERING);//默认负载均衡模式
// consumer.setMessageModel(MessageModel.BROADCASTING);
consumer.registerMessageListener(new MessageListenerOrderly(){
@Override
public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("从创建到消费时间="+(System.currentTimeMillis()-msg.getStoreTimestamp()));
System.out.println(Thread.currentThread().getName()+"--"+new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
}
}
要求必须是同一个topic,不能是延迟消息;必须是集群模式,不能是广播模式;
总体大小不能超过4M,而且一个批次发送,后只收到一次回执
实际应用中我们没有办法保证一次批次的数据就一定是少于4M的,如何发送批次消息呢?
可以split,拆分为多个小批次
public class Producer {
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
DefaultMQProducer producer = new
DefaultMQProducer("GID_TCP_GROUP");
// Specify name server addresses.
producer.setNamesrvAddr("127.0.0.1:9876");
//Launch the instance.
producer.start();
List messages = new ArrayList(100 * 1000);
for (int i=0; i<100*1000; i++) {
messages.add(new Message("batchTopic", "Tag", "OrderID" + i, ("Hello world " + i).getBytes()));
}
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
List listItem = splitter.next();
SendResult sendStatus=producer.send(listItem);
System.out.println("发送条数:"+listItem.size());
}
}
}
public class ListSplitter implements Iterator> {
private int sizeLimit = 1000 * 1000;
private final List messages;
private int currIndex;
public ListSplitter(List messages) {
this.messages = messages;
}
@Override
public boolean hasNext() {
return currIndex < messages.size();
}
@Override
public List next() {
int nextIndex = currIndex;
int totalSize = 0;
for (; nextIndex < messages.size(); nextIndex++) {
Message message = messages.get(nextIndex);
int tmpSize = message.getTopic().length() + message.getBody().length;
Map properties = message.getProperties();
for (Map.Entry entry : properties.entrySet()) {
tmpSize += entry.getKey().length() + entry.getValue().length();
}
tmpSize = tmpSize + 20; //for log overhead
if (tmpSize > sizeLimit) {
//it is unexpected that single message exceeds the sizeLimit
//here just let it go, otherwise it will block the splitting process
if (nextIndex - currIndex == 0) {
//if the next sublist has no element, add this one and then break, otherwise just break
nextIndex++;
}
break;
}
if (tmpSize + totalSize > sizeLimit) {
break;
} else {
totalSize += tmpSize;
}
}
List subList = messages.subList(currIndex, nextIndex);
currIndex = nextIndex;
return subList;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not allowed to remove");
}
}
分为两个步骤
发送事务消息,执行本地逻辑
之后,提交事务或者消息回滚
如果rokcetmq收到事务消息,但是过了很久依然没有收到消息的提交或者回滚,该如何?
可以由rocketMQ回查,触发发送方的回调(回调中执行提交后者回滚)
1)发送消息(half消息)
2)服务端响应消息写入结果。
3)根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)
4)根据本地事务状态执行commit或者rollback
(commit操作生成消息索引,消息对消费者可见)
### 事务补偿
1)对没有commit rollback的事务消息(pending状态的消息),从服务端发起一次回查
2)producer收到回查消息,检查回查消息对应的本地事务状态
3)根据本地事务状态,重新commit或者rollback
其中,补充阶段用于解决消息commit或者rollback发生超时或者失败的情况
普通消息,已发送,消费方就能消费到此消息;而事务消息,只有被提交以后,才会被消费
具体会回查多少次,回查N此后,依然是中间状态如何处理?
public class Producer {
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
TransactionMQProducer producer = new
TransactionMQProducer("GID_TCP_GROUP");
// Specify name server addresses.
producer.setNamesrvAddr("127.0.0.1:9876");
//Launch the instance.
( producer).setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
String tag=msg.getTags();
if("TAGA".equals(tag)){
return LocalTransactionState.COMMIT_MESSAGE;
}else if("TAGB".equals(tag)){
return LocalTransactionState.ROLLBACK_MESSAGE;
}else{
return LocalTransactionState.UNKNOW;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
System.out.println("回查:"+msg.getTags());
//不止一次回查,会回查几次呢
return LocalTransactionState.UNKNOW;
}
});
producer.start();
String[] tags={"TAGA","TAGB","TAGC"};
for(int i=0;i<3;i++){
Message message = new Message("transactionTopic", tags[i],("hello world "+tags[i]).getBytes());
TransactionSendResult sendStatus=producer.sendMessageInTransaction(message,null);
System.out.println("发送状态:"+sendStatus);
TimeUnit.SECONDS.sleep(1);
}
}
}
public class Consumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer=new DefaultMQPushConsumer("group1");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("transactionTopic","*");
consumer.registerMessageListener(new MessageListenerConcurrently(){
@Override
public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("从创建到消费时间="+(System.currentTimeMillis()-msg.getStoreTimestamp()));
System.out.println(Thread.currentThread().getName()+"--"+new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
}
}
1.事务消息不支持批量消息和延时消息
2.为了避免单个消息被检查太多次从而导致半队列消息累计,默认检查最多次数是15次;
用户可以通过修改broker配置文件的transactionCheckMax参数来修改此限制,如果已经超过设置的次数,则broker将丢弃此消息,并在默认情况下打印错误日志;
可以自定义重写:集成AbstractTransactionCheckListener类
3.回查时间间隔设置:broker 配置文件中参数transactionMsgTimeout,用户也可以设置用户属性CHECK_IMMUNITY_TIME_IN_SECONDS来改变设这个值,优先于transactionMsgTimeout参数
4.事务消息可能不止一次被检查或消费---为什么会被重复消费---消费方如何保证消费幂等性
5.为了保证消息不丢失,并且事务完整,建议同步的双重写入机制;
6.事务消息的生产者ID不能与其他类型的生产者ID共享,与其他类型的消息不同,事务消息允许反向查询,MQ服务能通过他们的生产者ID查询到消费者