RocketMQ生产者详解

一.管理方面的API

#创建topic
void createTopic(String key, String newTopic, int queueNum, int topicSysFlag)

#查找commitlog偏移量
队列最大偏移量
long maxOffset(final MessageQueue mq)
队列最小偏移量
long minOffset(final MessageQueue mq)
根据时间戳查找队列的偏移量
long searchOffset(MessageQueue mq, long timestamp)

#查找消息
根据offsetMsgId查找消息
MessageExt viewMessage(String offsetMsgId)
根据topic和msg查找消息
MessageExt viewMessage(String topic, String msgId)
根据topic和key朝着消息
QueryResult queryMessage(String topic, String key, int maxNum, long begin,long end)


二.生产者发送相关API

启听相关
void start()
void shutdown()

获取topic下所有队列
List <MessageQueue> fetchPublishMessageQueues(String topic)

消息发送相关
#异步发送
void send(Message msg, SendCallback sendCallback, long timeout)
#同步发送
void send(Message msg,  long timeout)
#自主选择队列发送消息,需要实现MessageQueueSelector接口
SendResult send(Message msg, MessageQueueSelector selector, Object arg,long timeout)
#发送事务消息,需要实现TransactionListener接口,arg参数对应下面方法
TransactionSendResult sendMessageInTransaction(Message msg,Object arg)
#不在乎结果的发送
void sendOneway(Message msg)
#批量发送消息
SendResult send(Collection<Message> msgs, long timeout)
SendResult send(Collection<Message> msgs, MessageQueue mq, long timeout)
#消息发送到broker,需要等到消费完才返回
Message request(Message msg, long timeout)

消息轨迹
可以追踪消息生产和消费的全流程
public DefaultMOProducer(final String producerGroup
,boolean enableMsgTrace,final String customizedTraceTopic){
  this( null, producerGroup,null,enableMsgTrace,customizedTraceTopic )
  }

生产者namespace作用
新建生产者时,可以传入namespace参数,会拼接到topic上,起到隔离环境的作用
DefaultMQProducer producer = new 
            DefaultMQProducer("namespace","please_rename_unique_group_name");

生产者发送消息简单实例

public class Producer {
    public static void main(String[] args) throws Exception{
        DefaultMQProducer producer = new 
            DefaultMQProducer("please_rename_unique_group_name");
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();
        //发送单条消息
        Message msg = new Message("TOPIC_TEST", "hello rocketmq".getBytes());
        SendResult sendResult = null;
        sendResult = producer.send(msg);
        // 输出结果
        System.out.printf("%s%n", sendResult);
        // 发送带 Key 的消息
        msg = new Message("TOPIC_TEST", null, "key","hello rocketmq".getBytes());
        sendResult = producer.send(msg);
        // 输出结果
        System.out.printf("%s%n", sendResult);
        // 批量发送
        List<Message> msgs = new ArrayList<>();
        msgs.add( new Message("TOPIC_TEST", null, "hello rocketmq1".getBytes()) );
        msgs.add( new Message("TOPIC_TEST", null,"hello rocketmq2".getBytes()) );
        sendResult = producer.send(msgs);
        System.out.printf("%s%n", sendResult);
        // 使用完毕后,关闭消息发送者
        producer.shutdown();
    }
}

三.生产者使用场景

发送一般消息:
使用同步消息,客户端内部有重试机制

发送顺序消息:
消息有要求,先发送的消息,先消费,比如mysqlbinlog日志,这就要求消息需要发送到相同的队列
发送时使用MessageQueueSelector指定发送队列,消费时需要使用顺序消费,发送时客户端内部不会进行重试,需要适用方自己重试
image.png

public static void main(String[] args) throws Exception {
 
        DefaultMQProducer producer = new DefaultMQProducer("dw_test_producer_group");
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();
        //这里为了方便查找消息,在构建消息的时候,使用订单编号为 key,
        //这样可以通过订单编号查询消息。
        Message msg = new Message("order_topic", null, order.getOrderNo(), 
            JSON.toJSONString(order).getBytes());
         producer.send(msg, new MessageQueueSelector() {
            @Override
            public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) 
            {
                //根据参数的 hashcode 进行队列选择
                if(mqs == null || mqs.isEmpty()) {
                    return null;
                }
                int index = Math.abs(arg.hashCode()) % mqs.size();
                return mqs.get(index < 0 ? 0 : index );
            }
        }, 传入参数);
    }

发送消息可追踪
构造消息时,可以设置keys,多个key以空格分隔,这样就会以通过RocketMQ console查看消息的状态

image.png
消息设置tag
构造消息时可以设置tag,消费者消费时可以指定消费topic的那些tag

offsetId和msgid
offsetId: MessageDecoder.decodeMessageId解码
broker ip和端口
commitlog偏移量

msgid:MessageClientIDSetter.getIPStrFromID获取IP
客户端ip,进程pid,类加载器,时间戳差值,自增序列

四. 生产者核心参数

//发送总超时时间,超过了时间就会停止重试,及时没有达到最大的重试次数
sendMsgTimeout = 3000
//超过此大小,客户端就会对消息进行gzip压缩,默认4k
compressMsgBodyOverHowmuch
//消息发送失败重试次数,跟sendMsgTimeout配合使用
retryTimesWhenSendFailed
//消息最大大小,客户端和broker都有这个配置
maxMessageSize


消息发送高可用
生产者线程安全的,发送消息时,会轮训topic中的每条队列发送消息,使用threadlocal的保存了上次发送的队列,发送时就累加序号;当发送失败时,会排除发送broker的所有队列,选择另外的broker的队列进行发送,尽可能保证消息发送成功。

客户端ID
生成规则如下
image.png
MQClientInstance是以这个客户端id为key来生成,如果需要向不同的nameserver发送消息,需要创建不一样的MQClientInstance,可以通过设置客户端的unitName来实现

public static void main(String[] args) throws Exception{
        //省略代码
        DefaultMQProducer producer2 = new DefaultMQProducer("test_producer_group2");
        producer2.setNamesrvAddr("127.0.0.1:9876");
        producer2.setUnitName("DefaultClusterb");
        producer2.start();
      //省略代码

五. 发送常见错误

5.1 No route info of this topic


开启自动创建topic时,broker启动会自动创建TBW102,并上报此topic的路由信息给nameserver
发送消息时,本地没有路由信息,就会从namserver查询,namserver有就直接返回;
如果namserver没有,开启了自动创建topic时,就会向namserver查询TBW102的路由信息,用TBW102的路由信息发生消息;
如果没有开启自动创建topic时,就会抛出上面错误;
生产建议关闭自动创建topic配置

5.2 发送超时错误


broker问题:
排查是否broker存在性能瓶颈,关键字‘PAGECACHERT’搜索store.log,每分钟打印前一分钟的发送耗时

100-200ms及以上范围 >20个。说明broker存在一定的瓶颈
看是否触发了快速失败机制,关键字 [TIMEOUT_CLEAN_QUEUE] broker busy
客户端问题:
应用gc 和网络异常
解决办法:
1.增加broker快速失败时长:为1000ms,默认200ms
mamaxWaitTimeMillsInQueue=1000
2.减少发送超时时间和增加重试次数
4.3以下版本通过生产者api控制
4.3版本需要自己外部控制

5.3 System busy, Broker busy


[REJECTREQUEST]system busy
too many requests and system thread pool busy
[PC_SYNCHRONIZED]broker busy
[PCBUSY_CLEAN_QUEUE]broker busy
[TIMEOUT_CLEAN_QUEUE]broker busy


1.PageCache压力大

[REJECTREQUEST]system busy
[PC_SYNCHRONIZED]broker busy
[PCBUSY_CLEAN_QUEUE]broker busy

写入消息,内存追加消息时,加锁超过1s,就认为PageCache压力大
通过 putMessage in lock cost time ,在这个日志store.log

解决办法:
开启transientStorePoolEnable=true,读走pagecache写走堆外内存分离(避免加锁),使用内存锁定机制,避免发送内存缺页中断
消息写到堆外内存,由一个后台线程定时刷到pagecache,当rocketmq进程挂时,会有丢数据风险

扩容:
增加broker节点,扩展topic队列数
2.发送线程池拒绝策略
broker接收到消息时,会把消息丢到线程池处理,线程池队列是有界队列,默认1w,超过1w就会抛出
[too many requests and system thread pool busy] 错误

3.Borker快速失败策略
请求在队列中超过200ms:抛出【TIMEOUT_CLEAN_QUEUE】 broker busy
pagecachebusy: 抛出【PCBUSY_CLEAN_QUEUE】broker busy

解决办法:
mamaxWaitTimeMillsInQueue=1000

六.事务消息

事务消息流程

流程:

  • 生产者使用TransactionMQProducer发送消息,先发送prepare消息,topic会被修改为RMQ_SYS_TRAN_HALF_TOPIC,队列为0,此时不可消费,生产者需要传入事务回调执行器

(继承TransactionListener)

  • 消息发送成功后,会执行TransactionListener里的executeLocalTransaction方法执行本地事务,如果成功就返回Commit_message,消息就可以被消费了,失败就返回rollback,消息就会丢弃;如果次方法没有返回结果,就会调用下面流程;
  • broker会定时回调生产者,执行checkLocalTransaction方法,会携带消息,通过反序列化消息查询到数据库数据,就返回Commit_message,消息就可以被消费了,否则返回Unkonw
  • 返回Unkonw时,broker会再次进行回调checkLocalTransaction,默认15次,超过就会把消息丢弃

使用场景: 操作数据库后,需要发送一条mq消息,确保mq消息是操作数据库成功才能被消费
例子:

/**
 * 消息事务生产者
 *
 */
public class TransactionProducer {

    public static void main(String[] args) throws MQClientException, InterruptedException {

        // 生产者事务监听器
        TransactionListener transactionListener = new OrderTransactionListener();
        TransactionMQProducer producer = new TransactionMQProducer();

        producer.setTransactionListener(transactionListener);
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.setProducerGroup("producer_order_trans_group");
        producer.start();

        // 发送消息
        String topic = "transaction-topic";
        String tags = "trans-order";
        Order order = new Order();
        order.setId(1);
        order.setUserId(1);
        order.setTotal(2);
        String orderJson = JSON.toJSONString(order);
        try {
            byte[] orderBytes = orderJson.getBytes(RemotingHelper.DEFAULT_CHARSET);
          
            Message msg = new Message(topic, tags, "order", orderBytes);
            producer.sendMessageInTransaction(msg, null);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        producer.shutdown();
    }
}



public class OrderTransactionListener implements TransactionListener {
    /**
     * When send transactional prepare(half) message succeed, this method will be invoked to execute local transaction.
     *
     * @param msg Half(prepare) message
     * @param arg Custom business parameter
     * @return Transaction state
     */
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        // RocketMQ 半消息发送成功,开始执行本地事务
        System.out.println("执行本地事务");
        TransactionUtil.startTransaction();
        LocalTransactionState state;
        try {
            // 创建订单
            System.out.println("创建订单");
            String orderStr = new String(msg.getBody());
            Order order = JSON.parseObject(orderStr, Order.class);
            String sql = "insert into orders(id, user_id, total) values(?, ?, ?)";
            int executeUpdates = TransactionUtil.execute(sql, order.getId(), order.getUserId(),
                     order.getTotal());
            if (executeUpdates > 0) {
                // 写入本地事务日志
                System.out.println("写入本地事务日志");
                String logSql = "insert into transaction_log(id, business, foreign_key) values(?, ?, ?)";
                String business = msg.getKeys();
                TransactionUtil.execute(logSql, msg.getTransactionId(), business, order.getId());
            }
            TransactionUtil.commit();
            state = LocalTransactionState.COMMIT_MESSAGE;
        } catch (SQLException e) {
            TransactionUtil.rollback();
            state = LocalTransactionState.ROLLBACK_MESSAGE;
            System.out.println("本地事务异常,回滚");
            e.printStackTrace();
        }
        return state;
    }

    /**
     * When no response to prepare(half) message. broker will send check message to check the transaction status, and this
     * method will be invoked to get local transaction status.
     *
     * @param msg Check message
     * @return Transaction state
     */
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 回查本地事务
        System.out.printf("回查本地事务, transactionId = %s%n", msg.getTransactionId());
        TransactionUtil.startTransaction();
        String sql = "select id, business, foreign_key from transaction_log where id = ?";
        try (ResultSet transactionLog = TransactionUtil.select(sql, msg.getTransactionId())) {
            if (transactionLog == null) {
                return LocalTransactionState.UNKNOW;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return LocalTransactionState.COMMIT_MESSAGE;
    }
}


七.总结

本节主要介绍生产者相关的内容,生产者api, 生产者核心参数,生产者使用场景,发送常见错误,以及事务消息的使用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值