前面我们简单的介绍了常用的消息中间件,本篇我们主要介绍消息中间件RocketMQ生产者的使用,RocketMQ实现了两种类型的
生产者DefaultMQProducer和TransactionMQProducer,前者生产普通消息,后者生产事务消息。而本篇我们主要介绍DefaultMQProducer的使用。在阅读本篇之前你需要了解RocketMQ的总体结构和RocketMQ的相关概念,这些内容RocketMQ官网已经写的非常清楚,如果不清楚的可以参考RocketMQ GitHub。
在此之前我们首先介绍Message的使用,Message是RocketMQ对消息的封装,我们也只能将消息封装为Message实例,才能通过RocketMQ发送出去。首先我们看下RocketMQ对消息的定义:
public class Message implements Serializable {
private static final long serialVersionUID = 8445773977080406428L;
//主题可以通过RocketMQ Console创建
private String topic;
//消息扩展信息,Tag、keys、消息延迟级别都保存在Map中
private Map<String, String> properties;
//目前没用
private int flag;
//消息体,字节数组
private byte[] body;
//事务ID
private String transactionId;
//设置消息的key,多个Key可以用MessageConst.KEY_SEPARATOR分隔或者直接调用第二个方法,传入一个集合
//最终保存在 properties中, key为MessageConst.PROPERTY_KEYS
public void setKeys(String keys) {}
public void setKeys(Collection<String> keys) { }
//设置主题,也可以通过构造传入
public void setTopic(String topic) {}
//设置Tag,消息过滤标记,用户可以订阅topic的某些Tag
public void setTags(String tags) {}
//设置延迟级别,延迟多久消费者可以消费
public void setDelayTimeLevel(int level) {}
//设置消息体,可通过构造传入
public void setBody(byte[] body) {}
//设置是否等消息存储成功
public void setWaitStoreMsgOK(boolean waitStoreMsgOK) {}
//设置用户ID
public void setBuyerId(String buyerId) {}
//设置事务ID
public void setTransactionId(String transactionId) {}
//设置属性properties key为name value为value
void putProperty(final String name, final String value) {}
//放置其他扩展信息,最终调用putProperty(final String name, final String value)
public void putUserProperty(final String name, final String value) {}
//设置properties
void setProperties(Map<String, String> properties) { }
//设置flag
public void setFlag(int flag) {}
}
在学习Message的结构之后,我们开始学习DefaultMQProducer是如何发送消息的,DefaultMQProducer提供了多个API用于发送不同类型的消息。如下为DefaultMQProducer提供的消息API。方法的异常以及主要逻辑这里不再展示,可以参考源码。
/*以下发送方法以同步的模式发送消息,该方法只有整个发送流程全部完成之后,才会返回。该方法有内部
* 的重试机制,可以通过参数retryTimesWhenSendFailed控制,因此可能有多个消息发送到broker,应
* 应用程序开发
* 人员需要解决潜在的重复问题
* 第二个方法额外指定了超时时间
* 第三个方法额外指定了队列MessageQueue,后续介绍
* 第四个额外指定了超时时间和队列MessageQueue
* 第五个方法额外指定了消息选择器MessageQueueSelector,后续介绍
* 第六个额外指定了超时时间和消息选择器MessageQueueSelector
**/
public SendResult send(Message msg) {}
public SendResult send(Message msg, long timeout) {}
public SendResult send(Message msg, MessageQueue mq){}
public SendResult send(Message msg, MessageQueue mq, long timeout){}
public SendResult send(Message msg, MessageQueueSelector selector, Object arg){}
public SendResult send(Message msg, MessageQueueSelector selector, Object arg, long timeout){}
/*
* 以下方法以异步的模式发送消息到broker,该方法将会直接返回,一旦发送流程完成,会执行
* sendCallback回调与send(Message)类似,它内部实现也会在重试次数用完之后失败才会声明发送失败,
* 因此也需要应用程序开发人员解决消息重复的问题
* 其他方法的参数,比如timeout,MessageQueue,MessageQueueSelector 与上面介绍的一样
**/
public void send(Message msg,SendCallback sendCallback) {}
public void send(Message msg, SendCallback sendCallback, long timeout){}
public void send(Message msg, MessageQueue mq, SendCallback sendCallback){}
public void send(Message msg, MessageQueue mq, SendCallback sendCallback, long timeout){}
public void send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback){}
public void send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback, long timeout){}
/*
* 以下类似于UDP报文协议,此方法发送消息之后不会等待broker确认,也就是不关注broker是否收到消息
* 它能最大程度提高吞吐量,但潜在会有消息丢失。其他参数与前面介绍的一致
/**
public void sendOneway(Message msg){}
public void sendOneway(Message msg, MessageQueue mq) {}
public void sendOneway(Message msg, MessageQueueSelector selector, Object arg){}
/*
* 以下方法用于批量发送消息其他参数与前面介绍的一致
**/
public SendResult send(Collection<Message> msgs){}
public SendResult send(Collection<Message> msgs, long timeout){}
public SendResult send(Collection<Message> msgs, MessageQueue messageQueue) {}
public SendResult send(Collection<Message> msgs, MessageQueue messageQueue, long timeout){}
上面我们介绍了DefaultMQProducer发送消息的API,下面我们使用例子查看DefaultMQProducer的使用方式,我们只编写上面的几种发送模式方法中的一种或者两种。总的来说,生产者发送消息逻辑比较简单,只需要连接RocketMQ服务,然后调用send方法即可,代码如下所示:
//使用生产者组名实例化一个生产者
DefaultMQProducer producer = new DefaultMQProducer("DefaultProducer");
// 指定RocketMQ nameServer地址
producer.setNamesrvAddr("10.0.10.63:9876");
// 启动生产者
producer.start();
//创建Message实例
Message msg = new Message("BenchmarkTest" , "TagA", ("Hello RocketMQ").getBytes(RemotingHelper.DEFAULT_CHARSET)
//调用sendOneway()发送消息,该方法不会管消息是否发送成功
producer.sendOneway(msg);
//同步发送消息,根据sendResult结果处理
SendResult sendResult = producer.send(msg);
//异步发送消息
producer.send(msg, new SendCallback() {
public void onSuccess(SendResult sendResult) {
//发送成功,业务处理
}
public void onException(Throwable e) {
//发送异常,业务处理
}
});
前面介绍了使用DefaultMQProducer发送同步、异步消息,上面的示例中没有涉及到MessageQueue和MessageQueueSelector。下面我们简单介绍它们两个的使用,首先我们介绍MessageQueueSelector的使用。在介绍MessageQueueSelector之前,我们先看下RocketMQ 发送消息的流程。
在RocketMQ中,Topic为逻辑概念,表示同一类消息,而消息最终会被发送到队列中,MessageQueue和MessageQueueSelector可以让我们根据某些规则,将消息发送到指定的队列。MessageQueueSelector定一个三种策略,分别为SelectMessageQueueByRandom,SelectMessageQueueByHash,SelectMessageQueueByMachineRoom。SelectMessageQueueByHash其算法为取arg参数的hashcode的绝对值,然后对mqs.size()取余,得到目标队列在mqs的下标
SelectMessageQueueByRandom其算法为直接根据mqs.size()随机一个值作为目标队列在mqs的下标,
SelectMessageQueueByMachineRoom其方法目前返回null。下面为MessageQueueSelector的一个示例:
MessageQueueSelector selector = new SelectMessageQueueByHash();
SendResult sendResult = producer.send(msg, selector, 105);
MessageQueueSelector指定某种规则,有RocketMQ根据规则选择一个队列,而MessageQueue直接指定一个固定的队列,如下示例所示:
MessageQueue queue = new MessageQueue();
queue.setBrokerName("sss");
queue.setQueueId(2);
queue.setTopic("queueTpoic");
SendResult sendResult = producer.send(msg,queue);
前面我们介绍DefaultMQProducer发送消息的API,以及MessageQueue和MessageQueueSelector,DefaultMQProducer的具体的参数,前面我们创建DefaultMQProducer时,只是设置了nameServer的地址,下面我们介绍DefaultMQProducer核心属性,其核心属性如下:
//设置nameSrvAddr地址,如果有多个则以分号隔开。
public void setNamesrvAddr(String namesrvAddr) {}
//设置客户端所在的IP
public void setClientIP(String clientIP) {}
//设置实例名称,每个实例需要取唯一的名字。
public void setInstanceName(String instanceName) {}
//表示是否开启VIP通道,VIP和非VIP区别是使用的端口不同
public void setVipChannelEnabled(final boolean vipChannelEnabled) {}
//客户端回调线程数,表示Netty通信层回调线程的个数
public void setClientCallbackExecutorThreads(int clientCallbackExecutorThreads) {}
//获取Topic路由信息的间隔时长,单位为毫秒,默认为30000毫秒
public void setPollNameServerInterval(int pollNameServerInterval) {}
//与Broker心跳间隔的时长,单位为毫秒,默认为30000毫秒
public void setHeartbeatBrokerInterval(int heartbeatBrokerInterval) {}
//生产者组这是一个必须传递的参数同一个生产者组中的生产者实例行为需要一致。
public void setProducerGroup(String producerGroup) {}
//发送超时时间 单位为毫秒
public void setSendMsgTimeout(int sendMsgTimeout) {}
//消息的容量上限,超时该值会通过ZIP压缩,默认为4M
public void setCompressMsgBodyOverHowmuch(int compressMsgBodyOverHowmuch) {}
//同步发送消息失败重试的次数,默认两次
public void setRetryTimesWhenSendFailed(int retryTimesWhenSendFailed){}
//异步发送消息失败重试的次数,默认两次
public void setRetryTimesWhenSendAsyncFailed(final int retryTimesWhenSendAsyncFailed){}
//在发送消息时,自动创建服务器不存在的topic,需要指定Key,该Key可用于配置发送消息所在topic的默认路由。
public void setCreateTopicKey(String createTopicKey){}
//在发送消息,自动创建服务器不存在的topic时,默认创建的队列数
public void setDefaultTopicQueueNums(int defaultTopicQueueNums){}
//如果发送消息返回sendResult,但是sendStatus!=SEND_OK,是否重试发送另一个broker 默认为false
public void setRetryAnotherBrokerWhenNotStoreOK(boolean retryAnotherBrokerWhenNotStoreOK) {}
上面是DefaultMQProducer的核心参数除此之外,我们还可在实例化DefaultMQProducer的时候传入RPCHooK实例,RPCHooK实例包含消息发送前的预处理和消息响应后的处理用,户可以在第一个接口中做一些安全控制或者其他操作。如下为RPCHooK接口的定义和DefaultMQProducer的构造定义:
public interface RPCHook {
void doBeforeRequest(final String remoteAddr, final RemotingCommand request);
void doAfterResponse(final String remoteAddr, final RemotingCommand request, final RemotingCommand response);
}
public DefaultMQProducer(final String producerGroup, RPCHook rpcHook) {
this.producerGroup = producerGroup;
defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}
除了sendOneway(Message message)之外,无论同步还是异步发送消息都会返回消息发送的结果,结果被封装在SendResult实例中。如下为SendResult的定义:
public class SendResult {
private SendStatus sendStatus;
private String msgId;
private MessageQueue messageQueue;
private long queueOffset;
private String transactionId;
private String offsetMsgId;
private String regionId;
private boolean traceOn = true;
}
上面的字段中,我们这里要讲述的SendStatus,表示发送消息的状态,send消息方法只要不抛异常,就代表发送成功。发送成功会有多个状态,如下为SendStatus的定义:
public enum SendStatus {
SEND_OK,
FLUSH_DISK_TIMEOUT,
FLUSH_SLAVE_TIMEOUT,
SLAVE_NOT_AVAILABLE,
}
- SEND_OK:消息发送成功。要注意的是消息发送成功也不意味着它是可靠的。要确保不会丢失任何消息,还应启用同步Master服务器或同步刷盘,即SYNC_MASTER或SYNC_FLUSH。
- FLUSH_DISK_TIMEOUT:消息发送成功但是服务器刷盘超时。此时消息已经进入服务器队列(内存),只有服务器宕机,消息才会丢失。消息存储配置参数中可以设置刷盘方式和同步刷盘时间长度,如果Broker服务器设置了刷盘方式为同步刷盘,即FlushDiskType=SYNC_FLUSH(默认为异步刷盘方式),当Broker服务器未在同步刷盘时间内(默认为5s)完成刷盘,则将返回该状态——刷盘超时。
- FLUSH_SLAVE_TIMEOUT:消息发送成功,但是服务器同步到Slave时超时。此时消息已经进入服务器队列,只有服务器宕机,消息才会丢失。如果Broker服务器的角色是同步Master,即SYNC_MASTER(默认是异步Master即ASYNC_MASTER),并且从Broker服务器未在同步刷盘时间(默认为5秒)内完成与主服务器的同步,则将返回该状态——数据同步到Slave服务器超时。
- SLAVE_NOT_AVAILABLE:消息发送成功,但是此时Slave不可用。如果Broker服务器的角色是同步Master,即SYNC_MASTER(默认是异步Master服务器即ASYNC_MASTER),但没有配置slave Broker服务器,则将返回该状态——无Slave服务器可用。
本篇详细介绍了RockerMQ生产者DefaultMQProducer的使用,相对而言DefaultMQProducer的使用比较简单,主要是Send方法的使用。下一篇我们介绍RockerMQ消费者的使用,至于什么事RocketMQ、RocketMQ的部署等问题可以自行查看RocketMQ官网。