2020.4.16笔记——RocketMQ应用①

官方网站:https://rocketmq.apache.org/
GitHub地址:https://github.com/apache/rocketmq

RocketMQ介绍

Apache RocketMQ是一个分布式消息传递和流媒体平台,具有低延迟,高性能和可靠性,万亿级容量和灵活的可伸缩性。

它具有多种功能:

  • 消息模式包括发布/订阅,请求/答复和流式传输
  • 财务级交易消息
  • 基于DLedger的内置容错和高可用性配置选项
  • 各种跨语言客户端,例如Java,C / C ++,Python,Go
  • 可插拔的传输协议,例如TCP,SSL,AIO
  • 内置消息跟踪功能,还支持开放式跟踪
  • 多功能的大数据和流生态系统集成
  • 按时间或偏移量追溯消息
  • 可靠的FIFO和严格有序的消息传递在同一队列中
  • 高效的推拉消费模型
  • 单个队列中的百万级消息累积容量
  • 多种消息传递协议,例如JMS和OpenMessaging
  • 灵活的分布式横向扩展部署架构
  • 快如闪电的批量消息交换系统
  • 各种消息过滤器机制,例如SQL和Tag
  • 用于隔离测试和云隔离群集的Docker映像
  • 功能丰富的管理仪表板,用于配置,指标和监视
  • 认证与授权
  • 免费的开源连接器,适用于源和接收器

RocketMQ服务端安装

官方快速开始页面
https://rocketmq.apache.org/docs/quick-start/

这里仅仅演示在window平台下的RocketMQ安装

  1. 首先是下载RocketMQ,4.7.0版本的下载地址在此

下面第一个链接是源码,第二个链接是可执行文件,因为我们是在window下做测试,可以直接下载第二个压缩包
在这里插入图片描述

  1. 解压压缩包,打开文件夹的bin目录

在这里插入图片描述

  1. 设置环境变量

创建一个名为ROCKETMQ_HOME的变量,值就是我们刚刚解压的压缩包的地址
在这里插入图片描述
如果没有配置这个环境变量,那么在启动rocketmq时就会报如下的错误
在这里插入图片描述

  1. 启动nameserver服务端

在bin目录中双击mqnamesrv.cmd就可以启动nameserver服务端
在这里插入图片描述
在这里插入图片描述

  1. 启动broker服务端

同样是在bin目录下通过如下的命令就可以启动broker服务端

start mqbroker.cmd -n 127.0.0.1:9876 -c ../conf/broker.conf

在这里插入图片描述
至此,RocketMQ就已经完全启动了

管理控制台的安装

同样RocketMQ也存在管理控制台,下面就是它的github地址

https://github.com/apache/rocketmq-externals

  1. 下载github上的rocketmq-externals项目
  2. 打开文件夹中的rocketmq-console项目,修改配置

找到resources文件夹中的application.properties配置文件
在这里插入图片描述
主要是将我们刚刚启动配置的地址写上
在这里插入图片描述

  1. 通过maven将项目打包

在项目的根目录执行下面的命令,注意一定要加上-DskipTests用来跳过测试,因为测试很有可能会报错。

mvn package -DskipTests

下面就是我们打包好后的jar包
在这里插入图片描述

  1. 运行jar包,启动管理台

通过下面的命令就可以启动管理台

java -jar rocketmq-console-ng-1.0.1.jar

可以看到这个管理台本身也是个spring boot项目
在这里插入图片描述

  1. 访问管理台页面

直接通过浏览器访问http://localhost:8080就可以访问RocketMQ管理台的页面
在这里插入图片描述
至此,RocketMQ管理台就安装好了,下面也不会过多的使用这个管理台,所以就不再详细介绍了。

RocketMQ架构

下面就是RocketMQ的基本架构,前面启动的时候分别启动的两个服务端就是下面图中的nameserver和broker服务端。

  • nameserver:此服务端负责接收生产者发送过来的消息,并把消息通过topic值再发送给broker服务端,同时nameserver会和broker进行心跳连接,保证可用性。相当于注册中心,负责路由消费者和生产者的请求。
  • broker:负责接收从nameserver发送过来的消息,并把消息存储在本地。
    在这里插入图片描述
    下面是topic的基本结构,topic就是用来区分消息的种类的,在我们生产消息或者消费消息时都需要指定,这些topic都是存储在broker中的,而且namesever则会通过topic来指定存储在那个broker中。

不过需要注意的是topic中并不是单一的队列,其中默认存在八个队列,4个读队列,4个写队列,写入的数据会随机放入队列,也可以指定某个队列,而且在消费端都是通过线程池来消费消息的,默认是开启20个线程去以平均的方式消费队列,所以一般情况下我们消费消息时它们都是乱序的。当然我们可以通过api使消息按顺序被消费。
在这里插入图片描述
在消息中间件中消费者有一个重要的概念,就是消息的消费方式,一般存在两种,分别使推和拉

  • 推:由服务端主动推送给客户端,实现核心需要客户端与服务端保持长连接。因为这是由服务端直接推送给客户端,对于客户端的状态服务端是不知道的,有可能客户端并不需要获取消息,但是服务端还在推送,甚至是客户端处于接收不到消息的状态。
  • 拉:客户端自己主动去服务端获取消息,一般会定时获取。因为需要定时获取,可能多数情况下服务端并没有接收到消息,而消费者还在拉取消息,这样就获取不到任何消息,还会浪费带宽。

这两种方式都有各自的缺陷,所以RocketMQ推出了一种推加拉的机制获取数据,也是定时从服务端拉取消息,但是会和服务端保持的长连接,默认是5秒钟。

RocketMQ客户端的基本使用

官方案例
https://rocketmq.apache.org/docs/simple-example/

这里只介绍RocketMQ的java客户端的使用

引入依赖

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.3.0</version>
</dependency>

简单示例

  • 生产者
public class SyncProducer {
    public static void main(String[] args) throws Exception {
        //Instantiate with a producer group name.
        DefaultMQProducer producer = new
            DefaultMQProducer("please_rename_unique_group_name");
        // Specify name server addresses.
        producer.setNamesrvAddr("localhost:9876");
        //Launch the instance.
        producer.start();
        for (int i = 0; i < 100; i++) {
            //Create a message instance, specifying topic, tag and message body.
            Message msg = new Message("TopicTest" /* Topic */,
                "TagA" /* Tag */,
                ("Hello RocketMQ " +
                    i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            //Call send message to deliver message to one of brokers.
            SendResult sendResult = producer.send(msg);
            System.out.printf("%s%n", sendResult);
        }
        //Shut down once the producer instance is not longer in use.
        producer.shutdown();
    }
}
  • 消费者
public class Consumer {

    public static void main(String[] args) throws InterruptedException, MQClientException {

        // Instantiate with specified consumer group name.
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name");
         
        // Specify name server addresses.
        consumer.setNamesrvAddr("localhost:9876");
        
        // Subscribe one more more topics to consume.
        consumer.subscribe("TopicTest", "*");
        // Register callback to execute on arrival of messages fetched from brokers.
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,ConsumeConcurrentlyContext context) {
				for (MessageExt msg : msgs){
	            	System.out.printf("%s Receive New Messages: %s %n",Thread.currentThread().getName(), msg);
                }
               	return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //Launch the consumer instance.
        consumer.start();

        System.out.printf("Consumer Started.%n");
    }
}

RocketMQ的应用

实现消息有序消费

在上面就已经说了因为生产者和消费者的关系,所以消息一般都是乱序的,但是我们也可以通过一些api使得消息变得有序,其实我们只要满足以下两个条件就可以使得消息队列变得有序。

  1. 生产者在将消息发送给服务端时指定将消息发送到指定的topic中的某一个写队列中。
  2. 消费者在消费消息时只用单线程去获取消息。

上面两个条件都可以通过api实现

  • 生产者
public static void main(String[] args) throws Exception {
    //Instantiate with a producer group name.
    DefaultMQProducer producer = new
            DefaultMQProducer("please_rename_unique_group_name");
    // Specify name server addresses.
    producer.setNamesrvAddr("localhost:9876");
    //Launch the instance.
    producer.start();
    for (int i = 0; i < 100; i++) {
        //Create a message instance, specifying topic, tag and message body.
        Message msg = new Message("TopicTest" /* Topic */,
                "TagA" /* Tag */,
                ("Hello RocketMQ " +
                        i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
        );
        //Call send message to deliver message to one of brokers.
        // 第二个参数可以指定如何选择把消息存储在哪一个队列中,这里时传递了一个根据hash选择的对象,最后一个参数就是需要hash的数据
        // 这里因为要把消息全部指定到同一个队列中,所以可以直接传递固定值
        SendResult sendResult = producer.send(msg,new SelectMessageQueueByHash(),"1");
        System.out.printf("%s%n", sendResult);
    }
    //Shut down once the producer instance is not longer in use.
    producer.shutdown();
}

指定一个选择队列的规则,而不是让消息通过默认的随机分配队列了,这里使用的是通过hash的方式分配队列。
在这里插入图片描述

  • 消费者
public static void main(String[] args) throws InterruptedException, MQClientException {

    // Instantiate with specified consumer group name.
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name");

    // Specify name server addresses.
    consumer.setNamesrvAddr("localhost:9876");

    // Subscribe one more more topics to consume.
    consumer.subscribe("TopicTest", "*");
    // Register callback to execute on arrival of messages fetched from brokers.

    // 单线程有序消费
    consumer.registerMessageListener(new MessageListenerOrderly() {
        public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext consumeOrderlyContext) {
            for (MessageExt msg : msgs){
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msg);
            }
            return ConsumeOrderlyStatus.SUCCESS;
        }

    });
    // 多线程无序消费
//        consumer.registerMessageListener(new MessageListenerConcurrently() {
//
//            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
//                                                            ConsumeConcurrentlyContext context) {
//                for (MessageExt msg : msgs){
//                    System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msg);
//                }
//                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
//            }
//        });

    //Launch the consumer instance.
    consumer.start();

    System.out.printf("Consumer Started.%n");
}

消费者最重要的就是对于消费者对象注册的消息监听器,之前我们注册的是MessageListenerConcurrently,这个采用的是多线程消费消息,所以是无序的。如果我们注册的是MessageListenerOrderly,它就是单线程消费消息的,所以能保证消息的顺序。
在这里插入图片描述
需要注意的是当我们吧消费者停掉然后在启动的时候就会出现延迟(此时消费者组里面发生了异常),当消费者组发生变化会触发队列重平衡(每隔20s去检查一下是否需要重平衡当队列重平衡的时候是不能消费),只有用单线程单队列的消费模式的时候才会出现延迟。

这是因为消费者每隔20s去锁定一次属于自己的队列,当队列锁定了之后就不会给其他消费消费,当他本次拉取消息消费完之后解锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值