对消息队列+storm+hbase的框架很感兴趣,感觉好多事情可以做。。。hbase已经搭了,现在开始消息队列的选择:
开源的消息中间件有好多,RabbitMQ、ActiveMQ、ZeroMQ、kafka,还有淘宝的rocketMq, 前身是metaq,淘宝还有另一款消息中间件notify。
对淘宝开源的东东比较感兴趣,毕竟是经过生产验证。。。经受住了血与火的考验的。。。。
从rocketMq开搞吧。。。:
淘宝的RocketMQ是什么?
RocketMQ 是一款分布式、队列模型的消息中间件,具有以下特点:
-
能够保证严格的消息顺序
-
提供丰富的消息拉取模式
-
高效的订阅者水平扩展能力
-
实时的消息订阅机制
-
亿级消息堆积能力
-
Metaq3.0 版本改名,产品名称改为RocketMQ
先学习几个术语:
Producer
消息生产者,负责产生消息,一般由业务系统负责产生消息。
Consumer
消息消费者,负责消费消息,一般是后台系统负责异步消费。
Push Consumer
Consumer的一种,应用通常向Consumer对象注册一个Listener接口,一旦收到消息,Consumer对象立 刻回调Listener接口方法。
Pull Consumer
Consumer的一种,应用通常主动调用Consumer的拉消息方法从Broker拉消息,主动权由应用控制。
Producer Group
一类Producer的集合名称,这类Producer通常发送一类消息,且发送逻辑一致。
Consumer Group
一类Consumer的集合名称,这类Consumer通常消费一类消息,且消费逻辑一致。
Broker
消息中转角色,负责存储消息,转发消息,一般也称为Server。在JMS规范中称为Provider。
广播消费
一条消息被多个Consumer消费,即使这些Consumer属于同一个Consumer Group,消息也会被Consumer Group中的每个Consumer都消费一次,广播消费中的Consumer Group概念可以认为在消息划分方面无意义。
在CORBA Notification规范中,消费方式都属于广播消费。
在JMS规范中,相当于JMS publish/subscribe model
集群消费
一个Consumer Group中的Consumer实例平均分摊消费消息。例如某个Topic有9条消息,其中一个Consumer Group有3个实例(可能是3个进程,或者3台机器),那么每个实例只消费其中的3条消息。
在CORBA Notification规范中,无此消费方式。
在JMS规范中,JMS point-to-point model与之类似,但是RocketMQ的集群消费功能大等于PTP模型。因为RocketMQ单个Consumer Group内的消费者类似于PTP,但是一个Topic/Queue可以被多个Consumer Group消费。
顺序消息
消费消息的顺序要同发送消息的顺序一致,在RocketMQ中,主要指的是局部顺序,即一类消息为满足顺序性,必须Producer单线程顺序发送,且发送到同一个队列,这样Consumer就可以按照Producer发送的顺序去消费消息。
普通顺序消息
顺序消息的一种,正常情况下可以保证完全的顺序消息,但是一旦发生通信异常,Broker重启,由于队列总数发生变化,哈希取模后定位的队列会变化,产生短暂的消息顺序不一致。
如果业务能容忍在集群异常情况(如某个Broker宕机或者重启)下,消息短暂的乱序,使用普通顺序方式比较合适。
严格顺序消息
顺序消息的一种,无论正常异常情况都能保证顺序,但是牺牲了分布式Failover特性,即Broker集群中只要有一台机器不可用,则整个集群都不可用,服务可用性大大降低。
如果服务器部署为同步双写模式,此缺陷可通过备机自动切换为主避免,不过仍然会存在几分钟的服务不可用。(依赖同步双写,主备自动切换,自动切换功能目前还未实现)
目前已知的应用只有数据库binlog同步强依赖严格顺序消息,其他应用绝大部分都可以容忍短暂乱序,推荐使用普通的顺序消息。
Message Queue
在RocketMQ中,所有消息队列都是持久化,长度无限的数据结构,所谓长度无限是指队列中的每个存储单元都是定长,访问其中的存储单元使用Offset来访问,offset为java long类型,64位,理论上在100年内不会溢出,所以认为是长度无限,另外队列中只保存最近几天的数据,之前的数据会按照过期时间来删除。
也可以认为Message Queue是一个长度无限的数组,offset就是下标。
好吧。。开始动手:
1、下载
从https://github.com/alibaba/RocketMQ下载rocketMq
2.开发测试环境搭建
1. 安装&启动
解压RocketMQ包,进到bin目录
1. 安装&启动
解压RocketMQ包,进到bin目录
启动服务
//启动mqnamesrv
//启动mqnamesrv
D:\devtest\message\rockmq\alibaba-rocketmq\bin>start/b mqnamesrv.exe >d:\devtest
\log\mqnamesrv.log
查看:mqnamesrv.log
The Name Server boot success.
//启动mqbroker
D:\devtest\message\rockmq\alibaba-rocketmq\bin>start/b mqbroker.exe -n "192.168.
119.1:9876" >d:/devtest/log/mqbroker.log
看日志中信息:mqbroker.log
The broker[papawa, 192.168.119.1:10911] boot success.
用jps看看当前java进程
jps(Java Virtual Machine Process Status Tool)是JDK提供的一个显示当前所有java进程pid的命令,简单实用,非常适合在linux/unix平台上简单察看当前java进程的一些简单情况。
D:\devtest\message\rockmq\alibaba-rocketmq\bin>jps -v
15640 -Djava.ext.dirs=D:\devtest\message\rockmq\alibaba-rocketmq\bin/../lib -Dr
ocketmq.home.dir=D:\devtest\message\rockmq\alibaba-rocketmq\bin/.. -XX:MaxNewSiz
e=512M -XX:MaxPermSize=128M -XX:NewSize=256M -XX:PermSize=128M -Xms512m -Xmx1g e
xit abort
18664 PULSEI~1.JAR -Xmx768m -XX:MaxPermSize=320m -XX:ReservedCodeCacheSize=64m -
Dosgi.nls.warnings=ignore
18576 -Djava.ext.dirs=D:\devtest\message\rockmq\alibaba-rocketmq\bin/../lib -Dr
ocketmq.home.dir=D:\devtest\message\rockmq\alibaba-rocketmq\bin/.. -XX:MaxNewSiz
e=512M -XX:MaxPermSize=128M -XX:NewSize=256M -XX:PermSize=128M -Xms512m -Xmx1g e
xit abort
14652 Jps -Denv.class.path=.;D:\develop\jdk/lib/rt.jar;D:\develop\jdk/lib/tools.
jar; -Dapplication.home=D:\develop\jdk -Xms8m
启动起来后:
3、运行:rocketmq带的例子
RocketMQ-3.0.9\rocketmq-example\src\main\java\com\alibaba\rocketmq\example\quickstart
生产者:
producer.java
package com.alibaba.rocketmq.example.quickstart;
import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.client.producer.DefaultMQProducer;
import com.alibaba.rocketmq.client.producer.SendResult;
import com.alibaba.rocketmq.common.message.Message;
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
DefaultMQProducer producer = new DefaultMQProducer("testProductGroup");
producer.start();
for (int i = 0; i < 20; i++) {
try {
Message msg = new Message("TopicTest",// topic
"TagA",// tag
("Hello RocketMQ " + i).getBytes()// body
);
SendResult sendResult = producer.send(msg);
System.out.println(sendResult);
}
catch (Exception e) {
e.printStackTrace();
Thread.sleep(1000);
}
}
producer.shutdown();
}
}
跑起来报错
com.alibaba.rocketmq.client.exception.MQClientException: No name server address, please set it.
没有指定
--需要在淘宝的例子中增加以下两行
producer.setNamesrvAddr("192.168.119.1:9876");
producer.setInstanceName("Producer");
再次运行:
6:22:21.681 [NettyClientWorkerThread_1] DEBUG io.netty.util.ResourceLeakDetector - -Dio.netty.leakDetectionLevel: simple
SendResult [sendStatus=SEND_OK, msgId=C0A8770100002A9F0000000000021920, messageQueue=MessageQueue [topic=TopicTest, brokerName=
papawa, queueId=0], queueOffset=250]
SendResult [sendStatus=SEND_OK, msgId=C0A8770100002A9F00000000000219A8, messageQueue=MessageQueue [topic=TopicTest, brokerName=
papawa, queueId=1], queueOffset=250]
SendResult [sendStatus=SEND_OK, msgId=C0A8770100002A9F0000000000021A30, messageQueue=MessageQueue [topic=TopicTest, brokerName=
papawa, queueId=2], queueOffset=249]
在样例代码中增加发送的消息主题
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
DefaultMQProducer producer = new DefaultMQProducer("testProductGroup");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.setInstanceName("Producer1");
producer.start();
for (int i = 0; i < 2; i++) {
try {
{ Message msg1 = new Message("TopicTest1",// topic
"TagA",// tag
("Hello RocketMQ-1 " + i).getBytes()// body
);
SendResult sendResult1 = producer.send(msg1);
System.out.println(sendResult1);
}
{
Message msg2 = new Message(
"TopicTest2",// topic
"TagB",// tag
"OrderID0034",// key
("Hello RocketMq-2 "+ i).getBytes());// body
SendResult sendResult2 = producer.send(msg2);
System.out.println(sendResult2);
}
{
Message msg3 = new Message(
"TopicTest3",// topic
"TagC",// tag
"OrderID061",// key
("Hello RocketMq-3 "+ i).getBytes());// body
SendResult sendResult3 = producer.send(msg3);
System.out.println(sendResult3);
}
}
catch (Exception e) {
e.printStackTrace();
Thread.sleep(1000);
}
}
// 应用退出时,要调用shutdown来清理资源,关闭网络连接,从rocketMq服务器上注销自己
producer.shutdown();
}
}
运行看到消息发送情况:
SendResult [sendStatus=SEND_OK, msgId=C0A8770100002A9F000000000002BCFE, messageQueue=MessageQueue [topic=TopicTest1, brokerName=bfsc-
papawa, queueId=0], queueOffset=26]
SendResult [sendStatus=SEND_OK, msgId=C0A8770100002A9F000000000002BD89, messageQueue=MessageQueue [topic=TopicTest2, brokerName=bfsc-
papawa, queueId=0], queueOffset=31]
SendResult [sendStatus=SEND_OK, msgId=C0A8770100002A9F000000000002BE25, messageQueue=MessageQueue [topic=TopicTest3, brokerName=bfsc-
papawa, queueId=0], queueOffset=31]
SendResult [sendStatus=SEND_OK, msgId=C0A8770100002A9F000000000002BEC0, messageQueue=MessageQueue [topic=TopicTest1, brokerName=bfsc-
papawa, queueId=1], queueOffset=18]
SendResult [sendStatus=SEND_OK, msgId=C0A8770100002A9F000000000002BF4B, messageQueue=MessageQueue [topic=TopicTest2, brokerName=bfsc-
papawa, queueId=1], queueOffset=23]
SendResult [sendStatus=SEND_OK, msgId=C0A8770100002A9F000000000002BFE7, messageQueue=MessageQueue [topic=TopicTest3, brokerName=bfsc-
papawa, queueId=1], queueOffset=23]
消息说明:
字段名 默认值 说明
Topic:null:必填,线下环境不需要申请,线上环境需要申请后才能使用
Body:null:必填,二进制形式,序列化由应用决定,Producer与Consumer要协商好序列化形式。
Tags:null:选填,类似于Gmail为每封邮件设置的标签,方便服务器过滤使用。目前只支持每个消息设置一个tag,所以也可以类比为Notify的MessageType概念
Keys:null:选填,代表这条消息的业务关键词,服务器会根据keys创建哈希索引,设置后,可以在Console系统根据Topic、Keys来查询消息,由于是哈希索引,请尽可能保证key唯一,例如订单号,商品Id等。
Flag:0:选填,完全由应用来设置,RocketMQ不做干预
DelayTimeLevel:0:选填,消息延时级别,0表示不延时,大于0会延时特定的时间才会被消费
WaitStoreMsgOK:TRUE:选填,表示消息是否在服务器落盘后才返回应答。
Message数据结构各个字段都可以通过get、set方式访问,例如访问topic
msg.getTopic();
msg.setTopic("TopicTest");
其他字段访问方式类似。
运行Consumer.java收消息:
Consumer.java
说明:
在Producer端,使用com.alibaba.rocketmq.common.message.Message这个数据结构,由于Broker会为Message增加数据结构,所以消息到达Consumer后,会在Message基础之上增加多个字段,Consumer看到的是com.alibaba.rocketmq.common.message.MessageExt这个数据结构,MessageExt继承于Message,
public class Consumer {
public static void main(String[] args) throws InterruptedException, MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumeGroupName");
consumer.setNamesrvAddr("192.168.119.1:9876");
consumer.setInstanceName("Consumer");
/**
* 订阅指定topic下tags分别等于TagA或TagC或TagD
*/
consumer.subscribe("TopicTest1", "");
/**
* 订阅指定topic下所有消息<br>
* 注意:一个consumer对象可以订阅多个topic
*/
consumer.subscribe("TopicTest2","*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
System.out.println(Thread.currentThread().getName() + " Receive New Messages1: " + msgs);
System.out.println(Thread.currentThread().getName()
+" Receive New Messages-size: " + msgs.size());
MessageExt msg = msgs.get(0);
if (msg.getTopic().equals("TopicTest1")) {
// 执行TopicTest1的消费逻辑
if (msg.getTags() != null && msg.getTags().equals("TagA")) {
// 执行TagA的消费
System.out.println("topic1-"+new String(msg.getBody()));
} else if (msg.getTags() != null
&& msg.getTags().equals("TagC")) {
// 执行TagC的消费
System.out.println("topic1-"+new String(msg.getBody()));
} else if (msg.getTags() != null
&& msg.getTags().equals("TagD")) {
// 执行TagD的消费
System.out.println("topic1-"+new String(msg.getBody()));
}
} else if (msg.getTopic().equals("TopicTest2")) {
System.out.println("topic2-"+new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Consumer Started.");
}
}
运行结果:
Consumer Started.
ConsumeMessageThread-consumeGroupName-2 Receive New Messages1: [MessageExt [queueId=1, storeSize=139, queueOffset=17, sysFlag=0, bornTimestamp=1398762553796, bornHost=/192.168.119.1:52514, storeTimestamp=1398762553797, storeHost=/192.168.119.1:10911, msgId=C0A8770100002A9F000000000002BB3C, commitLogOffset=179004, bodyCRC=1336612692, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message [topic=TopicTest1, flag=0, properties={TAGS=TagA, WAIT=true, MAX_OFFSET=18, MIN_OFFSET=0}, body=18]]]
ConsumeMessageThread-consumeGroupName-2 Receive New Messages-size: 1
topic1-Hello RocketMQ-1 1
ConsumeMessageThread-consumeGroupName-1 Receive New Messages1: [MessageExt [queueId=0, storeSize=139, queueOffset=25, sysFlag=0, bornTimestamp=1398762553718, bornHost=/192.168.119.1:52514, storeTimestamp=1398762553737, storeHost=/192.168.119.1:10911, msgId=C0A8770100002A9F000000000002B97A, commitLogOffset=178554, bodyCRC=950806466, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message [topic=TopicTest1, flag=0, properties={TAGS=TagA, WAIT=true, MAX_OFFSET=26, MIN_OFFSET=0}, body=18]]]
ConsumeMessageThread-consumeGroupName-1 Receive New Messages-size: 1
topic1-Hello RocketMQ-1 0
ConsumeMessageThread-consumeGroupName-3 Receive New Messages1: [MessageExt [queueId=1, storeSize=156, queueOffset=22, sysFlag=0, bornTimestamp=1398762553805, bornHost=/192.168.119.1:52514, storeTimestamp=1398762553808, storeHost=/192.168.119.1:10911, msgId=C0A8770100002A9F000000000002BBC7, commitLogOffset=179143, bodyCRC=204244489, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message [topic=TopicTest2, flag=0, properties={TAGS=TagB, KEYS=OrderID0034, WAIT=true, MAX_OFFSET=23, MIN_OFFSET=0}, body=18]]]
ConsumeMessageThread-consumeGroupName-3 Receive New Messages-size: 1
topic2-Hello RocketMq-2 1
ConsumeMessageThread-consumeGroupName-4 Receive New Messages1: [MessageExt [queueId=0, storeSize=156, queueOffset=30, sysFlag=0, bornTimestamp=1398762553757, bornHost=/192.168.119.1:52514, storeTimestamp=1398762553761, storeHost=/192.168.119.1:10911, msgId=C0A8770100002A9F000000000002BA05, commitLogOffset=178693, bodyCRC=2066462367, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message [topic=TopicTest2, flag=0, properties={TAGS=TagB, KEYS=OrderID0034, WAIT=true, MAX_OFFSET=31, MIN_OFFSET=0}, body=18]]]
ConsumeMessageThread-consumeGroupName-4 Receive New Messages-size: 1
topic2-Hello RocketMq-2 0