• producer消息


    1. 顺序消息

    消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。

    顺序消费的原理解析,在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。

    下面用订单进行分区有序的示例。一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定是同一个队列。

    /**
     * 参数一:消息对象
     * 参数二:消息队列选择权
     * 参数三:业务标示
     */
    SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
        /**
         *
         * @param mqs   队列集合
         * @param msg   消息对象
         * @param arg   业务标示
         * @return      某个队列
         */
        @Override
        public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
            Long id = (Long) arg;  // 根据订单id选择发送queue
            long index = id % mqs.size();
            return mqs.get((int) index);
        }
    }, "1");// 订单id
    
    // 消费者
    consumer.registerMessageListener(new MessageListenerOrderly() {
        Random random = new Random();
        
        @Override
        public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
           context.setAutoCommit(true);
           for (MessageExt msg : msgs) {
               // 可以看到每个queue有唯一的consume线程来消费, 订单对每个queue(分区)有序
               System.out.println("consumeThread=" + Thread.currentThread().getName() + "queueId=" + msg.getQueueId() + ", content:" + new String(msg.getBody()));
           }
        
           try {
               //模拟业务逻辑处理中...
               TimeUnit.SECONDS.sleep(random.nextInt(10));
           } catch (Exception e) {
               e.printStackTrace();
           }
           return ConsumeOrderlyStatus.SUCCESS;
        }
        });
    

    2. 延时消息

    比如电商里,提交了一个订单就可以发送一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。

    // 设置延时等级3,这个消息将在10s之后发送(现在只支持固定的几个时间,详看delayTimeLevel)
    message.setDelayTimeLevel(3);
    

    使用限制:

    // org/apache/rocketmq/store/config/MessageStoreConfig.java
    private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
    

    现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18。

    3. 批量消息

    批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic,相同的waitStoreMsgOK,而且不能是延时消息。此外,这一批消息的总大小不应超过4MB。

    String topic = "BatchTest";
    List<Message> messages = new ArrayList<>();
    messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes()));
    messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes()));
    messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes()));
    try {
       producer.send(messages);
    } catch (Exception e) {
       e.printStackTrace();
       //处理error
    }
    

    如果消息的总长度可能大于4MB时,这时候最好把消息进行分割。

    4. 过滤消息

    在大多数情况下,TAG是一个简单而有用的设计,其可以来选择您想要的消息。例如:

    consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC");
    

    消费者将接收包含TAGA或TAGB或TAGC的消息。但是限制是一个消息只能有一个标签,这对于复杂的场景可能不起作用。在这种情况下,可以使用SQL表达式筛选消息。SQL特性可以通过发送消息时的属性来进行计算。在RocketMQ定义的语法下,可以实现一些简单的逻辑。下面是一个例子:

    ------------
    | message  |
    |----------|  a > 5 AND b = 'abc'
    | a = 10   |  --------------------> Gotten
    | b = 'abc'|
    | c = true |
    ------------
    ------------
    | message  |
    |----------|   a > 5 AND b = 'abc'
    | a = 1    |  --------------------> Missed
    | b = 'abc'|
    | c = true |
    ------------
    
    4.1 SQL基本语法

    RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。

    数值比较,比如:>,>=,<,<=BETWEEN,=字符比较,比如:=,<>INIS NULL 或者 IS NOT NULL逻辑符号 ANDORNOT

    常量支持类型为:

    数值,比如:1233.1415字符,比如:'abc',必须用单引号包裹起来;
    NULL,特殊的常量
    布尔值,TRUE FALSE
    

    只有使用push模式的消费者才能用使用SQL92标准的sql语句,接口如下:

    public void subscribe(finalString topic, final MessageSelector messageSelector)
    
    4.2 消息生产者

    发送消息时,你能通过putUserProperty来设置消息的属性

    DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
    producer.start();
    Message msg = new Message("TopicTest",
       tag,
       ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
    );
    // 设置一些属性
    msg.putUserProperty("a", String.valueOf(i));
    SendResult sendResult = producer.send(msg);
    
    producer.shutdown();
    
    4.3 消息消费者

    用MessageSelector.bySql来使用sql筛选消息

    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
    // 只有订阅的消息有这个属性a, a >=0 and a <= 3
    consumer.subscribe("TopicTest", MessageSelector.bySql("a between 0 and 3");
    consumer.registerMessageListener(new MessageListenerConcurrently() {
       @Override
       public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
           return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
       }
    });
    consumer.start();
    

    记得开启过滤功能 在broker的配置文件

    enablePropertyFilter=true
    

    5. 事务消息

    image

    上图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。

    事务消息发送及提交

    1. 发送消息(half消息)。
    2. 服务端响应消息写入结果。
    3. 根据发送结果执行本地事务(如果写入失败,==此时half消息对业务不可见==,本地逻辑不执行)。
    4. 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,==此时消息对消费者可见==)

    事务消息的补偿

    1. 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“==回查==”
    2. Producer收到回查消息,检查回查消息对应的本地事务的状态
    3. 根据本地事务状态,重新Commit或者Rollback

    其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。

    事务消息状态

    事务消息共有三种状态,提交状态、回滚状态、中间状态:
    1. TransactionStatus.CommitTransaction: 提交事务,它允许消费者消费此消息。
    2. TransactionStatus.RollbackTransaction: 回滚事务,它代表该消息将被删除,不允许被消费。
    3. TransactionStatus.Unknown: 中间状态,它代表需要检查消息队列来确定状态。

    生产者需要使用TransactionMQProducer来创建生产者,然后设置本地事务的监听器用于处理本地事务,发送消息时使用sendMessageInTransaction方法。消费者不需要变动。执行本地事务后、需要根据执行结果对消息队列进行回复。

    public class TransactionProducer {
        public static void main(String[] args) throws Exception {
            TransactionMQProducer producer = new TransactionMQProducer("group1");
            producer.setNamesrvAddr("192.168.1.1:9876");
     
            // 设置事务消息的监听器
            producer.setTransactionListener(new TransactionListener() {
                /**
                 * 执行本地事务
                 */
                @Override
                public LocalTransactionState executeLocalTransaction(Message message, Object o) {
                    // 根据不同tag做不同操作
                    if ("TagA".equals(message.getTags())) {
                        return LocalTransactionState.COMMIT_MESSAGE;
                    } else if ("TagB".equals(message.getTags())) {
                        return LocalTransactionState.ROLLBACK_MESSAGE;
                    } else if ("TagC".equals(message.getTags())) {
                        return LocalTransactionState.UNKNOW;
                    }
                    return LocalTransactionState.UNKNOW;
                }
     
                /**
                 * 本地事务的回查,UNKNOW状态的消息回调这个方法
                 */
                @Override
                public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
                    System.out.println("MQ检查消息Tag【"+messageExt.getTags()+"】的本地事务执行结果");
                    return LocalTransactionState.COMMIT_MESSAGE;
                }
            });
            producer.start();
     
            String[] tags = {"TagA", "TagB", "TagC"};
     
            for (int i = 0; i < 10; i++) {
                String tag = tags[i % 3];
                Message message = new Message("Transaction", tag, (tag + "消息" + i).getBytes());
                // 此处的第二个参数会传递到executeLocalTransaction()方法的第二个参数去
                producer.sendMessageInTransaction(message, null);
            }
     
            // 不关闭生产者的原因是其要监听回传
    //        TimeUnit.SECONDS.sleep(5);
    //        producer.shutdown();
        }
    }
    
    使用限制
    1. 事务消息不支持延时消息和批量消息。
    2. 为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为 15 次,但是用户可以通过 Broker 配置文件的 transactionCheckMax参数来修改此限制。如果已经检查某条消息超过 N 次的话( N = t ransactionCheckMax ) 则 Broker 将丢弃此消息,并在默认情况下同时打印错误日志。用户可以通过重写 AbstractTransactionCheckListener 类来修改这个行为。
    3. 事务消息将在 Broker 配置文件中的参数 transactionMsgTimeout 这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制,该参数优先于 transactionMsgTimeout 参数。 事务性消息可能不止一次被检查或消费。
    4. 提交给用户的目标主题消息可能会失败,目前这依日志的记录而定。它的高可用性通过 RocketMQ 本身的高可用性机制来保证,如果希望确保事务消息不丢失、并且事务完整性得到保证,建议使用同步的双重写入机制。
    5. 事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同,事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。
    6. 事务消息可能会被check或者consume多次,要在Consumer端做好幂等控制。
  • 相关阅读:
    html添加注释怎么弄?
    编程语言本身是怎么开发出来的?
    一句话说明Facbook React证书的矛盾点
    XAMPP是什么?
    HTTP解析
    version control
    函数式编程语言
    Servlet之Filter
    Build tool
    container和injection
  • 原文地址:https://www.cnblogs.com/xhyouyou/p/12465537.html
Copyright © 2020-2023  润新知