一.管理方面的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指定发送队列,消费时需要使用顺序消费,发送时客户端内部不会进行重试,需要适用方自己重试
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查看消息的状态
消息设置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
生成规则如下
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, 生产者核心参数,生产者使用场景,发送常见错误,以及事务消息的使用。