RocketMQ源码解析(一)——RocketMQ的使用
快速入门(官方Quick Start)
1.环境准备:
- 推荐64位 OS, Linux/Unix/Mac;
- 64bit JDK 1.8以上;
- Maven 3.2.x;
- Git;
- 为Broker server 准备4GB以上的磁盘空间
2.下载、构建
官方提供两种形式,binary可以直接下载使用,source需要进行build,下载链接地址:http://rocketmq.apache.org/release_notes/
使用source进行构建:
> unzip rocketmq-all-4.4.0-source-release.zip
> cd rocketmq-all-4.4.0/
> mvn -Prelease-all -DskipTests clean install -U
> cd distribution/target/apache-rocketmq
启动NameServer
> nohup sh bin/mqnamesrv &
> tail -f ~/logs/rocketmqlogs/namesrv.log
The Name Server boot success...
启动broker
> nohup sh bin/mqbroker -n localhost:9876 &
> tail -f ~/logs/rocketmqlogs/broker.log
The broker[%s, 172.30.30.233:10911] boot success...
关闭broker,NameServer
> sh bin/mqshutdown broker
The mqbroker(36695) is running...
Send shutdown request to mqbroker(36695) OK
> sh bin/mqshutdown namesrv
The mqnamesrv(36664) is running...
Send shutdown request to mqnamesrv(36664) OK
3.发送,接收消息
官方提供source中提供有example工程,NameServer和broker启动后即可通过这个工程进行简单的测试,参考路径:org.apache.rocketmq.example.quickstart
Producer:
/**
* 此类展示如何使用DefaultMQProducer发送消息
*/
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
//初始化producer实例,参数是producerGroup的名字,自行修改
DefaultMQProducer producer = new DefaultMQProducer("ordinary-producer");
//设置NameServer的服务地址,本地一般是localhost:9876
producer.setNamesrvAddr("localhost:9876");
//执行start操作
producer.start();
//循环生成消息
for (int i = 0; i < 1000; i++) {
try {
//创建消息实例,三个参数分别是话题名称(string)、标签(string,用于过滤消息)、消息体(byte[])
Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
//调用send方法发送消息到broker,并拿到发送的结果SendResult
SendResult sendResult = producer.send(msg);
//SendResult的toString方法被重写了,此处可以直接打印内部封装的message处理详情
System.out.printf("%s%n", sendResult);
} catch (Exception e) {
e.printStackTrace();
//线程休眠1000ms
Thread.sleep(1000);
}
}
//消息发送完毕后,关闭此producer实例
producer.shutdown();
}
Consumer:
/**
* 此类展示如何使用DefaultMQPushConsumer订阅,消费消息
*/
public class Consumer {
public static void main(String[] args) throws InterruptedException, MQClientException {
//创建consumer实例,指定consumerGroup的名称
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ordinary-consumer");
//指定NameServer地址
consumer.setNamesrvAddr("localhost:9876");
//指定consumer开始消费的位置
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//订阅话题,次数topic的名称要和上面producer中的保持一致,*对应producer中的tag,表示不过滤,如果此时写tag-a,那就只消费被打上tag-a标签的message
consumer.subscribe("TopicTest", "*");
//注册listener监听消息消费
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
//MessageExt是message的子类,封装了包括queueid在内的更多信息
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者
consumer.start();
System.out.printf("Consumer Started.%n");
}
}
启动producer,成功显示日志,默认情形下queueid为0,1,2,3,这是因为系统默认创建的队列是4个
...
SendResult [sendStatus=SEND_OK, msgId=0A3C005D1A980E1641C0466AC3D20290, offsetMsgId=0A3C005D00002A9F0000000000024E46, messageQueue=MessageQueue [topic=TopicTest, brokerName=LZZ-PC, queueId=1], queueOffset=164]
SendResult [sendStatus=SEND_OK, msgId=0A3C005D1A980E1641C0466AC3D30291, offsetMsgId=0A3C005D00002A9F0000000000024EFA, messageQueue=MessageQueue [topic=TopicTest, brokerName=LZZ-PC, queueId=2], queueOffset=164]
SendResult [sendStatus=SEND_OK, msgId=0A3C005D1A980E1641C0466AC3D40292, offsetMsgId=0A3C005D00002A9F0000000000024FAE, messageQueue=MessageQueue [topic=TopicTest, brokerName=LZZ-PC, queueId=3], queueOffset=164]
SendResult [sendStatus=SEND_OK, msgId=0A3C005D1A980E1641C0466AC3D60293, offsetMsgId=0A3C005D00002A9F0000000000025062, messageQueue=MessageQueue [topic=TopicTest, brokerName=LZZ-PC, queueId=0], queueOffset=164]
...
启动consumer,成功显示日志
ConsumeMessageThread_3 Receive New Messages: [MessageExt [queueId=2, storeSize=180, queueOffset=208, sysFlag=0, bornTimestamp=1565770202335, bornHost=/10.60.0.93:49939, storeTimestamp=1565770202336, storeHost=/10.60.0.93:10911, msgId=0A3C005D00002A9F000000000002CABA, commitLogOffset=182970, bodyCRC=1948249169, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='TopicTest', flag=0, properties={TAGS=TagA, WAIT=true, UNIQ_KEY=0A3C005D1A980E1641C0466AC4DF0341, MAX_OFFSET=222, CONSUME_START_TIME=1565770445958, MIN_OFFSET=0}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 57, 52, 55], transactionId='null'}]]
ConsumeMessageThread_5 Receive New Messages: [MessageExt [queueId=2, storeSize=180, queueOffset=206, sysFlag=0, bornTimestamp=1565770202324, bornHost=/10.60.0.93:49939, storeTimestamp=1565770202325, storeHost=/10.60.0.93:10911, msgId=0A3C005D00002A9F000000000002C51A, commitLogOffset=181530, bodyCRC=1558599569, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='TopicTest', flag=0, properties={TAGS=TagA, WAIT=true, UNIQ_KEY=0A3C005D1A980E1641C0466AC4D40339, MAX_OFFSET=222, CONSUME_START_TIME=1565770445958, MIN_OFFSET=0}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 57, 51, 57], transactionId='null'}]]
ConsumeMessageThread_2 Receive New Messages: [MessageExt [queueId=2, storeSize=180, queueOffset=205, sysFlag=0, bornTimestamp=1565770202320, bornHost=/10.60.0.93:49939, storeTimestamp=1565770202321, storeHost=/10.60.0.93:10911, msgId=0A3C005D00002A9F000000000002C24A, commitLogOffset=180810, bodyCRC=1431313338, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='TopicTest', flag=0, properties={TAGS=TagA, WAIT=true, UNIQ_KEY=0A3C005D1A980E1641C0466AC4D00335, MAX_OFFSET=222, CONSUME_START_TIME=1565770445958, MIN_OFFSET=0}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 57, 51, 53], transactionId='null'}]]
此时,发送端和消费端都已经将消息处理完毕。可见使用消息队列有以下好处:
- 解耦。上面的例子中,producer和consumer互不依赖,双发通过NameServer获取broker的路由信息,在broker中完成信息的发送和消费,系统的耦合性大大降低。
- 削峰。海量数据处理中,单体应用往往无法承受住高并发的冲击。此时可以将数据放到 队列去,然后通过consumer异步消费,降低系统运行的压力。比如秒杀场景下,用户点击下单后即存放到消息rocketmq中,并不需要同步地完成实际的订单操作。等到秒杀结束,consumer再去消费消息完成物品出仓,物流跟踪等。
- 复用。producer发送消息到broker中后,可以启动多个consumer去订阅,消费消息,这样就可以为多个系统提供稳定的数据支撑。比如电商场景下,一个订单的数据需要提供给支付,仓储,物流,售后服务,用户行为分析等多套系统使用,此时使用消息队列可以很好的提供这种支持。
消息队列并不是完美的,使用时着消息丢失,重复消费等问题,那么RocketMQ是如何设计来保证系统的高可用呢?这就需要我们对RocketMQ的源码进一步分析。
参考资料:1.RocketMQ官方文档