5.RocketMQ-基础

RocketMQ推送方式

以下是作者个人的理解,欢迎大家纠错

同步推送–等待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<OrderStep> orderSteps=OrderStep.buildOrders();
        for (int i=0;i<orderSteps.size();i++) {
            Message message = new Message("delayTopic", "order","i"+i,(orderSteps.get(i)+"").getBytes());

            message.setDelayTimeLevel(2);
            SendResult sendStatus=producer.send(message, new MessageQueueSelector() {
                /**
                 *
                 * @param mqs 所有消息队列集合
                 * @param msg 消息对象
                 * @param arg 消息标识,这就是orderid
                 * @return
                 */
                @Override
                public MessageQueue select(List<MessageQueue> 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<MessageExt> 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<Message> messages = new ArrayList<Message>(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<Message> listItem = splitter.next();
            SendResult sendStatus=producer.send(listItem);
            System.out.println("发送条数:"+listItem.size());
        }


    }
}


public class ListSplitter implements Iterator<List<Message>> {
    private int sizeLimit = 1000 * 1000;
    private final List<Message> messages;
    private int currIndex;

    public ListSplitter(List<Message> messages) {
        this.messages = messages;
    }

    @Override
    public boolean hasNext() {
        return currIndex < messages.size();
    }

    @Override
    public List<Message> 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<String, String> properties = message.getProperties();
            for (Map.Entry<String, String> 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<Message> 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<MessageExt> 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查询到消费者

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值