RocketMq——顺序消费和事务

RocketMQ不遵循JMS规范,自己有一套自定义的机制,使用订阅主题的方式发送和接收任务,支持广播集群两种消费模式。

集群模式:设置消费端对象属性:MessageModel.CLUSTERING,这种方式就可以达到ActiveMQ水平扩展负载均衡消费消息的实现。比较特殊的是这种行为可以支持先发送数据(生产端先发送到MQ),消费端订阅主题发生在生产端之后也可以收到数据,比较灵活。

广播模式:设置消费端对象属性:MessageModel.BROADCASTING,相当于生产端发送数据到MQ,多个消费端都可以获得数据。

在RocketMQ里有个很重要的概念,就是GroupName,无论是生产端还是消费端,都必须指定一个GroupName,这个组名称是维护在应用系统级别上。

比如生产端指定一个ProduccerGroupName,这个名称需要由应用系统来保证唯一性,一类Producer集合的名称,这类Producer通常发送一类消息,且发送逻辑一致。消费端同理。

Topic主题,每个主题表示一个逻辑上的存储概念,而在MQ上,会有着与之对应的多个Queue队列,这个是物理存储的概念

RocketMQ提供了三种不同的producer:

1.NomalProducer 普通
2.OrderProducer 顺序
3.TransactionProducer 事务

1.普通模式:使用传统的send发送消息,不能保证消息的顺序一致性。

2.顺序模式:可以严格的保证消息的顺序执行。遵循全局顺序的时候使用一个queue,局部顺序使用多个queue并行消费。

3.事务模式:支持事务方式对消息进行提交处理,在rocket里事务分两个阶段

第一个阶段把消息传给MQ,只不过消费端不可见,但数据其实已经在Broker上了。

第二个阶段为本地消息回调处理,如果都成功返回COMMIT_MESSAGE,则在broker上的数据对消费端可见,失败则为ROLLBACK_MESSAGE,消费端不可见。

顺序消费

//如果使用顺序消费,则必须自己实现MessageQueueSelector,保证消息进入同一个队列中
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {

    @Override
    public MessageQueue select(List<MessageQueue> msgs, Message msg, Object arg) {
        Integer id = (Integer)arg;
        return msgs.get(id);
    }
}, 0);

broker只保证消息是顺序发送到消费端,但若消费端是多线程的,可能收到的第二个消息会比第一个消息处理得更快。

//messageListenerOrderly 保证顺序消费,消费端接收的是同一个队列的消息,避免多线程时顺序错乱
consumer.registerMessageListener(new MessageListenerOrderly() {

    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> arg0, ConsumeOrderlyContext arg1) {

        return null;
    }
});

consumer开启多线程,只需设置consumer的线程数。

consumer.setConsumeThreadMax(10);
consumer.setConsumeThreadMin(10);

事务

生产端先将凭证消息发送到broker服务器上,凭证消息对消费端不可见;再回调执行本地事务,若执行成功则返回COMMIT,broker再将凭证消息对消费端可见,若失败返回ROLLBACK。

Producer:

public class Producer {

    public static void main(String[] args) throws MQClientException, InterruptedException {
        String group_name = "transaction_producer";

        final TransactionMQProducer producer = new TransactionMQProducer(group_name);

        producer.setNamesrvAddr("192.168.0.2:9876;192.168.0.3:9876");
        producer.setCheckRequestHoldMax(200);
        producer.setCheckThreadPoolMaxSize(20);
        producer.setCheckThreadPoolMinSize(5);
        producer.start();
        //服务器回调producer,检查本地事务分支成功还是失败
        producer.setTransactionCheckListener(new TransactionCheckListener() {

            @Override
            public LocalTransactionState checkLocalTransactionState(MessageExt msg) {
                System.out.println("state --" + new String(msg.getBody()));
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });

        TransactionExecuterImpl tranExecuter = new TransactionExecuterImpl();

        for(int i=0; i<2 ; i++) {
            try {
                Message msg = new Message("TopicTransaction","transaction" + i,"key",("hello " + i).getBytes());
                SendResult sendResult = producer.sendMessageInTransaction(msg, tranExecuter, "tq");
                System.out.println(sendResult);
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
        Thread.sleep(3000);
        producer.shutdown();
    }

}

TransactionExecuterImpl:

/*
 * 执行本地事务,由客户端回调
 */
public class TransactionExecuterImpl implements LocalTransactionExecuter {

    @Override
    public LocalTransactionState executeLocalTransactionBranch(Message msg, Object arg) {
        System.out.println("msg :" + new String(msg.getBody()));
        System.out.println("arg :" + arg);
        String tag = msg.getTags();
        if(tag.equals("transaction1")) {
            //这里有一个分阶段提交任务概念
            System.out.println("这里处理业务逻辑,如操作数据库,失败情况下进行ROLLBACK");
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }

        return LocalTransactionState.COMMIT_MESSAGE;
        //return LocalTransactionState.UNKNOW;
    }

}

Consumer:

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("transaction_consumer");
        //设置consumer第一次启动是从队列头部开始还是尾部开始消费,若非第一次启动,那么按照上次消费的位置继续消费
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTransaction", "*");
        //批量消费,一次消费多少条消息,默认为1条,最大情况能拿多少条不代表每次能拿这么多条
        //consumer.setConsumeMessageBatchMaxSize(3);
        //messageListenerOrderly 保证顺序消费,消费端接收的是同一个队列的消息,避免多线程时顺序错乱
        /*consumer.registerMessageListener(new MessageListenerOrderly() {

            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> arg0, ConsumeOrderlyContext arg1) {

                return null;
            }
        });*/
        consumer.setConsumeThreadMax(10);
        consumer.setConsumeThreadMin(10);
        consumer.registerMessageListener(new MessageListenerConcurrently() {

            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                //System.out.println(Thread.currentThread().getName() + "Receive: " + msgs);
                //获取一次性消费多少条消息
                //System.out.println("消息条数 : " + msgs.size());
                MessageExt msg1 = null;
                try {
                    for(MessageExt msg : msgs) {
                        msg1 = msg;
                        String topic = msg.getTopic();
                        String msgbody = new String(msg.getBody(),"utf-8");
                        String tag = msg.getTags();
                        System.out.println("收到消息: " + "topic:" + topic + " tags:" + tag + " msg:" + msgbody);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    //若已经重试了5次则不再重试
                    if(msg1.getReconsumeTimes() == 5) {
                        //此处记录日志操作。。。
                        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                    }
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }

                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.setNamesrvAddr("192.168.0.2:9876;192.168.0.3:9876");
        consumer.start();
        System.out.println("Consumer started...");
    }

}

Producer console:

msg :hello 0
arg :tq
SendResult [sendStatus=SEND_OK, msgId=C0A8000200002A9F0000000000003B1C, messageQueue=MessageQueue [topic=TopicTransaction, brokerName=broker-a, queueId=0], queueOffset=0]
msg :hello 1
arg :tq
这里处理业务逻辑,如操作数据库,失败情况下进行ROLLBACK
SendResult [sendStatus=SEND_OK, msgId=C0A8000200002A9F0000000000003C9E, messageQueue=MessageQueue [topic=TopicTransaction, brokerName=broker-a, queueId=1], queueOffset=0]

Consumer console :

收到消息: topic:TopicTransaction tags:transaction0 msg:hello 0
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值