本章主要介绍RocketMq的消息格式和消息的发送
一、RocketMQ的消息格式
在RocketMq中,每一个消息被封装为Message,有属性topic,flag,扩展字段和消息内容。
public class Message implements Serializable{
private String topic;
private int flag;
// 扩展属性字段
private Map<String, String> properties;
private byte[] body;
...
}
二、Producer的启动
消息的发送和接收都在client包下。
1、接口MQAdmin提供了基本的MQ管理操作,
public interface MQAdmin{
// 创建topic
void createTopic(final String key, final String newTopic, final int queueNum) throws MQClientException;
// 增加topic的系统标识
void createTopic(String key, String newTopic, int queueNum, int topicSysFlag) throws MQClientException;
// 根据时间戳获取消息队列的offset
long searchOffset(final MessageQueue mq, final long timestamp) throws MQClientException;
long maxOffset(final MessageQueue mq) throws MQClientException;
long minOffset(final MessageQueue mq) throws MQClientException;
// 消息存储的最早时间
long earliestMsgStoreTime(final MessageQueue mq) throws MQClientException;
// 根据messageId查询消息
MessageExt viewMessage(final String offsetMsgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException;
MessageExt viewMessage(String topic, String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException;
QueryResult queryMessage(final String topic, final String key, final int maxNum, final long begin, final long end) throws MQClientException, InterruptedException;
}
2、MQProducer继承自MQAdmin,增加了消息的发送方式
public interface MQProducer extends MQAdmin{
// 根据topic获取消息队列
List<MessageQueue> fetchPublishMessageQueues(final String topic) throws MQClientException;
SendResult send(final Message msg) throws MQClientException, RemotingException, MQBrokerException,InterruptedException;
// 发送消息后,会回调
void send(final Message msg, final SendCallback sendCallback, final long timeout)
throws MQClientException, RemotingException, InterruptedException;
SendResult send(final Message msg, final MessageQueue mq, final long timeout)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException;
// 通过策略发送消息
void send(final Message msg, final MessageQueueSelector selector, final Object arg,
final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, InterruptedException;
// 发送事务消息
TransactionSendResult sendMessageInTransaction(final Message msg,
final LocalTransactionExecuter tranExecuter, final Object arg) throws MQClientException;
}
基本实现类是DefaultMqProducer, 继承了client的依稀额基本配置clientConfig
public class ClientConfig{
// VIP通道
public static final String SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY = "com.rocketmq.sendMessageWithVIPChannel";
// 获取NameSrv的地址
private String namesrvAddr = System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY, System.getenv(MixAll.NAMESRV_ADDR_ENV));
// 对外暴露地址
private String clientIP = RemotingUtil.getLocalAddress();
// 实例名字
private String instanceName = System.getProperty("rocketmq.client.name", "DEFAULT");
// 回调线程初始化大小
private int clientCallbackExecutorThreads = Runtime.getRuntime().availableProcessors();
// 和namsrv30秒交换一次信息
private int pollNameServerInteval = 1000 * 30;
// 和broker30秒心跳一次
private int heartbeatBrokerInterval = 1000 * 30;
// 5秒持久化一次consumer的消费offset
private int persistConsumerOffsetInterval = 1000 * 5;
}
public class DefaultMQProducer extends ClientConfig implements MQProducer{
// 所有的实际操作委托给这个实现
protected final transient DefaultMQProducerImpl defaultMQProducerImpl;
// 默认topic队列数
private volatile int defaultTopicQueueNums = 4;
private int sendMsgTimeout = 3000;
private int compressMsgBodyOverHowmuch = 1024 * 4;
private int retryTimesWhenSendFailed = 2;
private int retryTimesWhenSendAsyncFailed = 2;
private boolean retryAnotherBrokerWhenNotStoreOK = false;
private int maxMessageSize = 1024 * 1024 * 4;
// 组合模式,委托到具体的实现类
protected final transient DefaultMQProducerImpl defaultMQProducerImpl;
...
public DefaultMQProducer() {
this(MixAll.DEFAULT_PRODUCER_GROUP, null);
}
public DefaultMQProducer(final String producerGroup, RPCHook rpcHook) {
this.producerGroup = producerGroup;
defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}
public DefaultMQProducer(final String producerGroup) {
this(producerGroup, null);
}
public DefaultMQProducer(RPCHook rpcHook) {
this(MixAll.DEFAULT_PRODUCER_GROUP, rpcHook);
}
...
}
继续查看DefaultMQProducerImpl类
3、启动producer,调用DefaultMQProducer.start()方法,实际调用到DefaultMQProducerImpl.start()方法
public void start() throws MQClientException {
this.start(true);
}
public void start(final boolean startFactory) throws MQClientException {
// 默认为启动的时候,serviceState是CREATE_JUST
switch (this.serviceState) {
case CREATE_JUST:
// 标记初始化失败,这个技巧不错。
this.serviceState = ServiceState.START_FAILED;
// 检查producer的group信息
this.checkConfig();
if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
this.defaultMQProducer.changeInstanceNameToPID();
}
// 创建或获取MQClient对象,并且存储到内存factoryTable中
this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQProducer, rpcHook);
// 注册Producer,只是保存了group和producer的一个关系map
boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);
if (!registerOK) {
this.serviceState = ServiceState.CREATE_JUST;
throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup() + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), null);
}
// 将topic和topic的路由信息保存到内存中
this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
// 启动MQClient对象
if (startFactory) {
mQClientFactory.start();
}
log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),this.defaultMQProducer.isSendMessageWithVIPChannel());
// 标记初始化成功
this.serviceState = ServiceState.RUNNING;
break;
case RUNNING:
case START_FAILED:
case SHUTDOWN_ALREADY:
throw new MQClientException("The producer service state not OK, maybe started once, "//+ this.serviceState// + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),null);
default:
break;
}
// 发送心跳
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
}
getAndCreateMQClientInstance方法会根据clientId查看内存中是否有MQClientInstance,这个包含了与底层交互的netty client,注册netty的事件处理processor。
//初始化客户端请求实例
public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId, RPCHook rpcHook) {
//mq的核心配置信息
this.clientConfig = clientConfig;
//当前进程内的唯一标识,升序数值
this.instanceIndex = instanceIndex;
//netty通信的客户端配置
this.nettyClientConfig = new NettyClientConfig();
this.nettyClientConfig.setClientCallbackExecutorThreads(clientConfig.getClientCallbackExecutorThreads());
this.nettyClientConfig.setUseTLS(clientConfig.isUseTLS());
//解析客户端请求,封装的事件处理
this.clientRemotingProcessor = new ClientRemotingProcessor(this);
//客户端实例的实际实现,网络通信的核心,只是初始化了通信框架,具体的链接后面根据不同的地址再进行链接操作
this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, this.clientRemotingProcessor, rpcHook, clientConfig);
//设置核心的nameserv地址
if (this.clientConfig.getNamesrvAddr() != null) {
this.mQClientAPIImpl.updateNameServerAddressList(this.clientConfig.getNamesrvAddr());
log.info("user specified name server address: {}", this.clientConfig.getNamesrvAddr());
}
this.clientId = clientId;
//mq管理
this.mQAdminImpl = new MQAdminImpl(this);
//拉取消息的实现
this.pullMessageService = new PullMessageService(this);
//负载均衡的实现,可能有相关的机器增加删除,需要定期的进行重负载操作
this.rebalanceService = new RebalanceService(this);
//消息发送者的包装,发送者的发送者,这个逻辑有点乱,并且在构造方法中重新初始化的
//producer -> DefaultMQProducer -> DefaultMQProducerImpl -> MQClientInstance -> DefaultMQProducer
this.defaultMQProducer = new DefaultMQProducer(MixAll.CLIENT_INNER_PRODUCER_GROUP);
this.defaultMQProducer.resetClientConfig(clientConfig);
//消费客户端的状态管理
this.consumerStatsManager = new ConsumerStatsManager(this.scheduledExecutorService);
log.info("Created a new client Instance, InstanceIndex:{}, ClientID:{}, ClientConfig:{}, ClientVersion:{}, SerializerType:{}",
this.instanceIndex,
this.clientId,
this.clientConfig,
MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION), RemotingCommand.getSerializeTypeConfigInThisServer());
}
网络通信MQClientAPIImpl的构造
public MQClientAPIImpl(final NettyClientConfig nettyClientConfig,
final ClientRemotingProcessor clientRemotingProcessor,
RPCHook rpcHook, final ClientConfig clientConfig) {
//核心的配置
this.clientConfig = clientConfig;
//该功能主要是如果namesrv为空,从约定的服务上去拉取
topAddressing = new TopAddressing(MixAll.getWSAddr(), clientConfig.getUnitName());
//通信客户端的核心实现,底层基于netty的链接
this.remotingClient = new NettyRemotingClient(nettyClientConfig, null);
//请求事件封装处理
this.clientRemotingProcessor = clientRemotingProcessor;
this.remotingClient.registerRPCHook(rpcHook);
//将事件处理绑定到上层传递过来的事件处理封装类上
this.remotingClient.registerProcessor(RequestCode.CHECK_TRANSACTION_STATE, this.clientRemotingProcessor, null);
this.remotingClient.registerProcessor(RequestCode.NOTIFY_CONSUMER_IDS_CHANGED, this.clientRemotingProcessor, null);
this.remotingClient.registerProcessor(RequestCode.RESET_CONSUMER_CLIENT_OFFSET, this.clientRemotingProcessor, null);
this.remotingClient.registerProcessor(RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT, this.clientRemotingProcessor, null);
this.remotingClient.registerProcessor(RequestCode.GET_CONSUMER_RUNNING_INFO, this.clientRemotingProcessor, null);
this.remotingClient.registerProcessor(RequestCode.CONSUME_MESSAGE_DIRECTLY, this.clientRemotingProcessor, null);
}
registerProducer只是在MQClient的Map中保存一个信息,ConcurrentHashMap<String/* group */, MQProducerInner> producerTable。mQClientFactory.start()方法会启动netty服务,启动定时线程任务,从namsesrv获取broker信息和topic信息。
public class MQClientInstance {
...
// 启动producer
public void start() throws MQClientException {
synchronized (this) {
switch (this.serviceState) {
case CREATE_JUST:
this.serviceState = ServiceState.START_FAILED;
// If not specified,looking address from name server
if (null == this.clientConfig.getNamesrvAddr()) {
this.mQClientAPIImpl.fetchNameServerAddr(); // TODO 待读:获取namesrv,从url
}
// Start request-response channel
this.mQClientAPIImpl.start();
// Start various schedule tasks
this.startScheduledTask();
// Start pull service
this.pullMessageService.start(); // TODO 疑问:producer调用这个干啥
// Start rebalance service
this.rebalanceService.start(); // TODO 疑问:producer调用这个干啥
// Start push service
this.defaultMQProducer.getDefaultMQProducerImpl().start(false); // TODO 疑问:为什么这里要调用
log.info("the client factory [{}] start OK", this.clientId);
this.serviceState = ServiceState.RUNNING;
break;
case RUNNING:
break;
case SHUTDOWN_ALREADY:
break;
case START_FAILED:
throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null);
default:
break;
}
}
}
private void startScheduledTask() {
if (null == this.clientConfig.getNamesrvAddr()) { //获取namesrv,从url
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr();
} catch (Exception e) {
log.error("ScheduledTask fetchNameServerAddr exception", e);
}
}
}, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS);
}
// 定时拉取 Topic路由配置
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.updateTopicRouteInfoFromNameServer();
} catch (Exception e) {
log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e);
}
}
}, 10, this.clientConfig.getPollNameServerInteval(), TimeUnit.MILLISECONDS);
// 定时同步消费进度
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
MQClientInstance.this.cleanOfflineBroker();
MQClientInstance.this.sendHeartbeatToAllBrokerWithLock();
} catch (Exception e) {
log.error("ScheduledTask sendHeartbeatToAllBroker exception", e);
}
}
}, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS);
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() { // TODO 待读:ALL
try {
MQClientInstance.this.persistAllConsumerOffset();
} catch (Exception e) {
log.error("ScheduledTask persistAllConsumerOffset exception", e);
}
}
}, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS);
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() { // TODO 待读:ALL
try {
MQClientInstance.this.adjustThreadPool();
} catch (Exception e) {
log.error("ScheduledTask adjustThreadPool exception", e);
}
}
}, 1, 1, TimeUnit.MINUTES);
}
}
4、通过心跳的方式,向所有的broker注册Producer,并且将对应的groupName注册到broker。
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
public void sendHeartbeatToAllBrokerWithLock() {
if (this.lockHeartbeat.tryLock()) {
try {
this.sendHeartbeatToAllBroker();
this.uploadFilterClassSource();
} catch (final Exception e) {
log.error("sendHeartbeatToAllBroker exception", e);
} finally {
this.lockHeartbeat.unlock();
}
} else {
log.warn("lock heartBeat, but failed.");
}
}
private void sendHeartbeatToAllBroker() {
// 组装心跳数据
final HeartbeatData heartbeatData = this.prepareHeartbeatData();
final boolean producerEmpty = heartbeatData.getProducerDataSet().isEmpty();
final boolean consumerEmpty = heartbeatData.getConsumerDataSet().isEmpty();
if (producerEmpty && consumerEmpty) {
log.warn("sending heartbeat, but no consumer and no producer");
return;
}
long times = this.storeTimesTotal.getAndIncrement();
// 遍历所有的broker,开始发送心跳包
Iterator<Entry<String, HashMap<Long, String>>> it = this.brokerAddrTable.entrySet().iterator();
while (it.hasNext()) {
Entry<String, HashMap<Long, String>> entry = it.next();
String brokerName = entry.getKey();
HashMap<Long, String> oneTable = entry.getValue();
if (oneTable != null) {
for (Map.Entry<Long, String> entry1 : oneTable.entrySet()) {
Long id = entry1.getKey();
String addr = entry1.getValue();
if (addr != null) {
if (consumerEmpty) {
if (id != MixAll.MASTER_ID)
continue;
}
try {
this.mQClientAPIImpl.sendHearbeat(addr, heartbeatData, 3000);
if (times % 20 == 0) {
log.info("send heart beat to broker[{} {} {}] success", brokerName, id, addr);
log.info(heartbeatData.toString());
}
} catch (Exception e) {
if (this.isBrokerInNameServer(addr)) {
log.error("send heart beat to broker exception", e);
} else {
log.info("send heart beat to broker[{} {} {}] exception, because the broker not up, forget it", brokerName,
id, addr);
}
}
}
}
}
}
}
到此,producer整体服务启动完成,状态变成RUNNING,可以创建topic和发送消息。