RocketMQ(一)介绍

来源:互联网 发布:求推荐淘宝美国代购店 编辑:程序博客网 时间:2024/05/20 18:16

1.RocketMq

RocketMQ的前身是Metaq,当Metaq3.0发布时,产品名称改为RocketMQ,有以下特点: 
1. 能够保证严格的消息顺序

2. 提供丰富的消息拉取模式

3. 高效的订阅者水平扩展能力

4. 实时的消息订阅机制

5. 亿级消息堆积能力


2.核心原理

2.1. 数据结构

这里写图片描述

这里写图片描述

(1)所有数据单独储存到commit Log ,完全顺序写,随机读

(2)对最终用户展现的队列实际只储存消息在Commit Log 的位置信息,并且串行方式刷盘

(3)按照MessageId查询消息

这里写图片描述

(4)根据查询的key的hashcode%slotNum得到具体的槽位置

这里写图片描述

(5)根据slotValue(slot对应位置的值)查找到索引项列表的最后一项

(6)遍历索引项列表返回查询时间范围内的结果集

2.2. 刷盘策略

rocketmq中的所有消息都是持久化的,先写入系统pagecache,然后刷盘,可以保证内存与磁盘都有一份数据,访问时,可以直接从内存读取

2.3. 内存机制

这里写图片描述

2.4. 工作模式

这里写图片描述

集群方式运维特点消息可靠性(master宕机情况)服务可用性(master宕机情况)其他特点备注单Master结构简单,扩容方便,机器要求低同步刷盘消息一条都不会丢整体可用
未被消费的消息无法取得,影响实时性性能最高适合消息可靠性最高、实时性低的需求多Master 异步有毫秒级丢失
同步双写不丢失差评,主备不能自动切换,且备机只能读不能写,会造成服务整体不可写。 不考虑,除非自己提供主从切换的方案Master-Slave(异步复制)结构复杂,扩容方便故障时会丢失消息整体可用,实时性影响毫秒级别
该组服务只能读不能写性能很高适合消息可靠性中等,实时性中等的要求Master-Slave(同步双写)结构复杂,扩容方便不丢消息整体可用,不影响实时性
该组服务只能读不能写性能比异步低10%,所以实时性也并不比异步方式太高适合消息可靠性略高,实时性中等、性能要求不高的需求
tip

第四种的官方介绍上,比第三种多说了一句:“不支持主从自动切换”。这句话让我很恐慌,因为第三种也是不支持的,干嘛第四种偏偏多说这一句,难道可用性上比第三种差?

于是做了实验,证明第三种和第四种可用性是一模一样的。那么不支持主从切换是什么意思?推断编写者是这个意图:

因为是主从双写的,所以数据一致性非常高,那么master挂了之后,slave本是可以立刻切换为主的,这一点与异步复制不一样。异步复制并没有这么高的一致性,所以这一句话并不是提醒,而是一个后续功能的备注,可以在双写的架构上继续发展出自动主从切换的功能。

3. 环境安装

3.1. RocketMq安装

https://github.com/alibaba/RocketMQ/releases下载3.2.6,解压

3.2. 环境变量配置

export JAVA_HOME=/usr/local/jdk1.7.0_79export ROCKETMQ_HOME=/usr/local/alibaba-rocketmqexport CLASSPATH=/usr/local/jdk1.7.0_79/lib  export PATH=$JAVA_HOME/bin:$PATH:$ROCKETMQ_HOME/bin
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

4. 测试网络拓扑

这里写图片描述

因为手里没有其他服务器,105那台缺少一个slave,在同步双写模式下,发送消息会返回 
SLAVE_NOT_AVAILABLE,不过消息已经发送成功,只是slave没有写成功。


5. 启停操作

这里只给出一个基本的示例,各个模式的启停在本文最后的参考文献中会有详细的说明。这里不再赘述。

新建日志文件夹

cd /usr/local/alibaba-rocketmqmkdir logtouch log/ng.logtouch log/ng-error.logtouch log/mq.log
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 启动nameserver
nohup sh mqnamesrv 1>$ROCKETMQ_HOME/log/ng.log 2>$ROCKETMQ_HOME/log/ng-error.log &
  • 1
  • 1
  • 验证nameserver是否启动
$tail -f $ROCKETMQ_HOME/log/ng.logThe Name Server boot success.
  • 1
  • 2
  • 1
  • 2
  • 停止nameServer
sh mqshutdown namesrvThe mqnamesrv(12248) is running...Send shutdown request to mqnamesrv(12248) OK
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • 启动broker(单master)(多master,多master+slave)对应的(异步复制,同步双写)
nohup sh mqbroker -n 192.168.1.101:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-a.properties >$ROCKETMQ_HOME/log/mq.log &
  • 1
  • 1
  • 验证mqbroker是否启动
tail -f $ROCKETMQ_HOME/log/mq.logJava HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=320m; support was removed in 8.0Java HotSpot(TM) 64-Bit Server VM warning: UseCMSCompactAtFullCollection is deprecated and will likely be removed in a future release.The broker[e010125001186.bja, 10.125.1.186:10911] boot success. and name server is 10.125.1.186:9876
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 停止broker
sh mqshutdown brokerThe mqbroker(13634) is running...Send shutdown request to mqbroker(13634) OK
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

6. 运维指令

  • 查看集群情况
./mqadmin clusterList -n 127.0.0.1:9876
  • 1
  • 1
  • 查看broker状态
./mqadmin brokerStatus -n 127.0.0.1:9876 -b 192.168.146.105:10911
  • 1
  • 1
  • 查看topic列表
./mqadmin topicList -n 127.0.0.1:9876
  • 1
  • 1
  • 查看topic状态
./mqadmin topicStatus -n 127.0.0.1:9876 -t PushTopic
  • 1
  • 1
  • 查看topic路由
./mqadmin topicRoute  -n 127.0.0.1:9876 -t PushTopic
  • 1
  • 1

7. 基本测试

基本测试采用Java直接编码的方式生产和消费消息,例子来源于参考文献的《RocketMQ开发教程》。

  • Producer
package com.somnus.rocketmq;import java.util.concurrent.TimeUnit;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 {        /**         * 一个应用创建一个Producer,由应用来维护此对象,可以设置为全局对象或者单例<br>         * 注意:ProducerGroupName需要由应用来保证唯一<br>         * ProducerGroup这个概念发送普通的消息时,作用不大,但是发送分布式事务消息时,比较关键,         * 因为服务器会回查这个Group下的任意一个Producer         */        DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");        producer.setNamesrvAddr("172.16.235.77:9876;172.16.235.78:9876");        producer.setInstanceName("Producer");        /**         * Producer对象在使用之前必须要调用start初始化,初始化一次即可<br>         * 注意:切记不可以在每次发送消息时,都调用start方法         */        producer.start();        /**         * 下面这段代码表明一个Producer对象可以发送多个topic,多个tag的消息。         * 注意:send方法是同步调用,只要不抛异常就标识成功。但是发送成功也可会有多种状态,<br>         * 例如消息写入Master成功,但是Slave不成功,这种情况消息属于成功,但是对于个别应用如果对消息可靠性要求极高,<br>         * 需要对这种情况做处理。另外,消息可能会存在发送失败的情况,失败重试由应用来处理。         */        for (int i = 0; i < 10; i++) {            try {                {                    Message msg = new Message("TopicTest11",// topic                            "TagA",                         // tag                            "OrderID001",                   // key                            ("Hello MetaQ").getBytes());    // body                    SendResult sendResult = producer.send(msg);                    System.out.println(sendResult);                }                {                    Message msg = new Message("TopicTest12",// topic                            "TagB",                         // tag                            "OrderID002",                   // key                            ("Hello MetaQ").getBytes());    // body                    SendResult sendResult = producer.send(msg);                    System.out.println(sendResult);                }                {                    Message msg = new Message("TopicTest13",// topic                            "TagC",                         // tag                            "OrderID003",                   // key                            ("Hello MetaQ").getBytes());    // body                    SendResult sendResult = producer.send(msg);                    System.out.println(sendResult);                }            } catch (Exception e) {                e.printStackTrace();            }            TimeUnit.MILLISECONDS.sleep(1000);        }        /**         * 应用退出时,要调用shutdown来清理资源,关闭网络连接,从MetaQ服务器上注销自己         * 注意:我们建议应用在JBOSS、Tomcat等容器的退出钩子里调用shutdown方法         */        producer.shutdown();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • Consumer
package com.somnus.rocketmq;import java.util.List;import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;  import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;  import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;  import com.alibaba.rocketmq.client.consumer.listener.MessageListenerConcurrently;  import com.alibaba.rocketmq.client.exception.MQClientException;import com.alibaba.rocketmq.common.message.MessageExt;  public class Consumer {    /**     * 当前例子是PushConsumer用法,使用方式给用户感觉是消息从RocketMQ服务器推到了应用客户端。<br>     * 但是实际PushConsumer内部是使用长轮询Pull方式从MetaQ服务器拉消息,然后再回调用户Listener方法<br>     */    public static void main(String[] args) throws InterruptedException,            MQClientException {        /**         * Consumer组名,多个Consumer如果属于一个应用,订阅同样的消息,且消费逻辑一致,则应该将它们归为同一组         * 一个应用创建一个Consumer,由应用来维护此对象,可以设置为全局对象或者单例<br>         * 注意:ConsumerGroupName需要由应用来保证唯一         */        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");        consumer.setNamesrvAddr("172.16.235.77:9876;172.16.235.78:9876");        consumer.setInstanceName("Consumber");        /**         * 订阅指定topic下tags分别等于TagA或TagB或TagC         */        consumer.subscribe("TopicTest11", "TagA || TagB || TagC");        /**         * 订阅指定topic下所有消息<br>         * 注意:一个consumer对象可以订阅多个topic         */        /*consumer.subscribe("TopicTest12", "*");*/        consumer.registerMessageListener(new MessageListenerConcurrently() {            /**             * 默认msgs里只有一条消息,可以通过设置consumeMessageBatchMaxSize参数来批量接收消息             * consumeThreadMin:消费线程池数量 默认最小值10             * consumeThreadMax:消费线程池数量 默认最大值20             */            @Override            public ConsumeConcurrentlyStatus consumeMessage(                    List<MessageExt> msgs, ConsumeConcurrentlyContext context) {                System.out.println(Thread.currentThread().getName()                        + " Receive New Messages: " + msgs.size());                MessageExt msg = msgs.get(0);                if (msg.getTopic().equals("TopicTest11")) {                    // 执行TopicTest1的消费逻辑                    if (msg.getTags() != null                             && msg.getTags().equals("TagA")) {                        // 执行TagA的消费                        System.out.println("TopicTest11------>"+new String(msg.getBody()));                    } else if (msg.getTags() != null                             && msg.getTags().equals("TagB")) {                        // 执行TagC的消费                    } else if (msg.getTags() != null                            && msg.getTags().equals("TagC")) {                        // 执行TagD的消费                    }                } else if (msg.getTopic().equals("TopicTest12")) {                    /*System.out.println("TopicTest2------>"+new String(msg.getBody()));*/                }                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;            }        });        /**         * Consumer对象在使用之前必须要调用start初始化,初始化一次即可<br>         */        consumer.start();        System.out.println("Consumer Started.");    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

运行结果: 
这里写图片描述

这里写图片描述


8.宕机实验

这里写图片描述


9.参考文献

1) 《RocketMq入门(上)》 
2) 《RocketMq入门(下)》 
3) 《Rokectmq开发教程》 
4) 《阿里Rocketmq Quict Start》 
5) 《RocketMQ与Kafka对比(18项差异)》 
6) 《RocketMq命令整理》 
7) 《RocketMq原理简介》

0 0
原创粉丝点击