RocketMQ,从入门到弃坑

原理:前人之述备矣

1. 启动RocketMQ

  1. 下载rocketmq http://rocketmq.apache.org/dowloading/releases/

  2. 配置环境变量

    变量名: ROCKETMQ_HOME

    变量值:${your_install_location}\rocketmq-all-4.8.0-bin-release

  3. 启动RocketMQ相关组件

    1. namesrv : 注册中心,类似nacos的作用,统一管理broker

      新建文件:start-namesrv.cmd 编辑输入 : start ${you_install_location}\rocketmq-all-4.8.0-bin-release\bin\mqnamesrv.cmd 并发送到桌面

    2. broker : RocketMQ的核心模块,负责接收并存储消息,默认是将消息推送给(Topic:Tag)匹配的消费者

      新建文件:start-broker.cmd 编辑输入 : start ${you_install_location}\rocketmq-all-4.8.0-bin-release\bin\mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true , 同理

    完成以上后可以使用RocketMQ了,此外还提供了可视化页面,方便开发时校对信息和队列管理

    1. 下载地址 : https://github.com/apache/rocketmq-externals.git

    2. 编辑文件 : rocketmq-externals-master\rocketmq-console\src\main\resources\application.yml

      已经忘了改了啥了,下列是改了之后的

    server.address=0.0.0.0
    server.port=8080
    ### SSL setting
    #server.ssl.key-store=classpath:rmqcngkeystore.jks
    #server.ssl.key-store-password=rocketmq
    #server.ssl.keyStoreType=PKCS12
    #server.ssl.keyAlias=rmqcngkey
    #spring.application.index=true
    spring.application.name=rocketmq-console
    spring.http.encoding.charset=UTF-8
    spring.http.encoding.enabled=true
    spring.http.encoding.force=true
    logging.level.root=INFO
    logging.config=classpath:logback.xml
    #if this value is empty,use env value rocketmq.config.namesrvAddr  NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
    rocketmq.config.namesrvAddr=127.0.0.1:9876
    #if you use rocketmq version < 3.5.8, rocketmq.config.isVIPChannel should be false.default true
    rocketmq.config.isVIPChannel=
    #rocketmq-console's data path:dashboard/monitor
    rocketmq.config.dataPath=/tmp/rocketmq-console/data
    #set it false if you don't want use dashboard.default true
    rocketmq.config.enableDashBoardCollect=true
    #set the message track trace topic if you don't want use the default one
    rocketmq.config.msgTrackTopicName=
    rocketmq.config.ticketKey=ticket
    #Must create userInfo file: ${rocketmq.config.dataPath}/users.properties if the login is required
    rocketmq.config.loginRequired=false
    #set the accessKey and secretKey if you used acl
    #rocketmq.config.accessKey=
    #rocketmq.config.secretKey=
    
    1. 在回到rocketmq-console项目的根路径,重新编译项目 mvn clean package -Dmaven.test.skip=true
    2. 新建文件start-console.cmd 编辑输入 : java -jar ${install_location}\rocketmq-externals-master\rocketmq-console\target\rocketmq-console-ng-2.0.0.jar

    至此, RocketMQ安装完毕,启动时依次启动

    start-namesrv.cmd , start-broker.cmd ,start-console.cmd 即可

    页面大概长这样吧

    在这里插入图片描述

2. Java 测试RocketMQ发送

SpringBoot集成跳过测试
  1. 创建maven项目,引入rocketmq - client 包
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.8.0</version>
</dependency>
  1. 在Test中插入代码
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.junit.Test;

import java.util.concurrent.atomic.AtomicInteger;

public class ProducerTest {

    public static void main(String[] args) {
        DefaultMQProducer producer = null;
        try {
            //实例化生产者producer
            producer = new DefaultMQProducer("producer");
            //设置nameServer的地址 换成自己的ip和端口号
            producer.setNamesrvAddr("192.168.1.7:9876");
            //设置消息同步失败发送的重试次数
            producer.setRetryTimesWhenSendFailed(2);
            //设置消息发送超时时间 ,默认3000ms
            producer.setSendMsgTimeout(2000);
            //启动producer实例
            producer.start();
            for (int i = 0; i < 5; i++) {
                //创建消息,指定topic,tag,和message
                Message message = new Message("topic1",
                        "tag1",
                        ("我是第" + i + "条消息").getBytes(RemotingHelper.DEFAULT_CHARSET));
                //发送到broker中
                SendResult send = producer.send(message);
                //通过返回的result查看是否发送成功
                System.out.println(send);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (producer != null) {
                //关闭product
                producer.shutdown();
            }
        }
    }
}

控制面板显示 :

SendResult [sendStatus=SEND_OK, msgId=7F000001215C18B4AAC29864386A0000, offsetMsgId=C0A8010700002A9F000000000004A3BB, messageQueue=MessageQueue [topic=topic1, brokerName=DESKTOP-51A3NCJ, queueId=3], queueOffset=19]
SendResult [sendStatus=SEND_OK, msgId=7F000001215C18B4AAC2986438F10001, offsetMsgId=C0A8010700002A9F000000000004A484, messageQueue=MessageQueue [topic=topic1, brokerName=DESKTOP-51A3NCJ, queueId=0], queueOffset=19]
SendResult [sendStatus=SEND_OK, msgId=7F000001215C18B4AAC2986438F40002, offsetMsgId=C0A8010700002A9F000000000004A54D, messageQueue=MessageQueue [topic=topic1, brokerName=DESKTOP-51A3NCJ, queueId=1], queueOffset=21]
SendResult [sendStatus=SEND_OK, msgId=7F000001215C18B4AAC2986438F50003, offsetMsgId=C0A8010700002A9F000000000004A616, messageQueue=MessageQueue [topic=topic1, brokerName=DESKTOP-51A3NCJ, queueId=2], queueOffset=21]
SendResult [sendStatus=SEND_OK, msgId=7F000001215C18B4AAC2986438F50004, offsetMsgId=C0A8010700002A9F000000000004A6DF, messageQueue=MessageQueue [topic=topic1, brokerName=DESKTOP-51A3NCJ, queueId=3], queueOffset=20]
14:11:45.025 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[192.168.1.7:9876] result: true
14:11:45.028 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[192.168.1.7:10911] result: true
  1. 进入可视化工具 查看刚才发送的消息

在这里插入图片描述

​ success!

3. SpringBoot项目集成RocketMQ

  1. 引入springboot集成rocketmq的包
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.0.3</version>
</dependency>
  1. yml中进行配置
rocketmq:
  name-server: 127.0.0.1:9876
  producer:
    group: rocket-sunshine

​ 若报错RocketMQTemplate注入失败,请检查yml是否完成了配置

  1. 封装消息实体类 Message
import lombok.Data;

@Data
public class RocketMqMessage {
	//自定义id , 可以保存到数据库防止重复消费
    private String id;
	//主题,用于匹配消费者,消费者订阅了该topic才会被推送消息
    private String topic;
	//标签,用于更精准的匹配消费者
    private String tag;
	//消息类容
    private String content;
	//超时时间 默认2000ms,可以自定义
    private int outTime = 2000;
	//延迟队列, 分为18个登记 
	//时间分别为 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h 
    private int delayTimeLevel = 0;
}
  1. 封装消息发送工具类
import cn.qiuming.domain.RocketMqMessage;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicInteger;

@Component
public class BaseProducer {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 发送同步消息 方法
     * @param message 消息内容
     */
    public SendResult syncSend(RocketMqMessage message) {
        SendResult sendResult = rocketMQTemplate.syncSend(message.getTopic() + ":" + message.getTag(),
                MessageBuilder.withPayload(message.getContent()).build(),
                message.getOutTime(),
                message.getDelayTimeLevel()
        );
        return sendResult;
    }

    /**
     * 发送 异步消息
     * @param message 消息内容
     */
    public void asyncSend(RocketMqMessage message, AtomicInteger integer) {
        rocketMQTemplate.asyncSend(message.getTopic() + ":" + message.getTag(),
                MessageBuilder.withPayload(message.getContent()).build(),
                new SendCallback() {
                    //消息发送成功处理
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        System.out.println(message.getId() + " send success!");
                    }
                    //消息发送失败处理
                    @Override
                    public void onException(Throwable e) {
                        System.out.println("消息发送失败,你打我呀!");
                    }
                },
                message.getOutTime(),
                message.getDelayTimeLevel()
        );
    }

    /**
     * 发送 同步消息 不关心返回结果
     * @param message
     */
    public void syncSendOneWay(RocketMqMessage message) {
        rocketMQTemplate.sendOneWay(message.getTopic() + ":" + message.getTag(), message.getContent());
    }

}
  1. 同步消息没有失败回调接口,发送信息在SendResult中,可以接收返回
  2. 异步消息根据自己的业务进行失败/成功的处理
  3. 发送同步消息不关系返回结果可能造成消息丢失,因为其不会等待broker确认,适用于日志或者监控
  4. 吞吐量 : 3>2>1 ,根据业务的类型 , 选用最相应的方式发送消息
  1. 测试生产者消息发送
import cn.qiuming.domain.RocketMqMessage;
import cn.qiuming.productor.BaseProducer;
import cn.qiuming.threadjob.MyThreadJob;
import org.apache.rocketmq.client.producer.SendResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.time.LocalDateTime;
import java.util.UUID;

@Controller
@RequestMapping("/access")
public class TestController {

    @Autowired
    private BaseProducer producer;

    //发送同步无延迟消息
    @GetMapping("/test1")
    public void test1() {
        RocketMqMessage message = new RocketMqMessage();
        message.setId(UUID.randomUUID().toString());
        message.setTopic("basic-topic");
        message.setTag("tag-one");
        message.setContent("路漫漫其修远兮,吾将上下而求索");
        SendResult sendResult = producer.syncSend(message);
        System.out.println("\n" + "time = " + LocalDateTime.now());
        System.out.println("sendResult = " + sendResult);
    }

    //发送异步延迟消息
    @GetMapping("/test3")
    public void test3() throws Exception {
        RocketMqMessage message = new RocketMqMessage();
        message.setId(UUID.randomUUID().toString());
        message.setTopic("basic-topic");
        message.setTag("tag-three");
        message.setContent("也无风雨也无晴");
        message.setDelayTimeLevel(3);
        producer.asyncSend(message);
        System.out.println("\n" + "time = " + LocalDateTime.now());
    }

}
  1. 测试, 创建两个消费者接收消息
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import java.time.LocalDateTime;

@RocketMQMessageListener(topic = "basic-topic", consumerGroup = "consumer-one", selectorExpression = "tag-one")
public class OneConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String message) {
        System.out.println("------------ one consumer get message ------------");
        System.out.println(message);
        System.out.println("time = " + LocalDateTime.now());
    }
}
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import java.time.LocalDateTime;

@RocketMQMessageListener(topic = "basic-topic", consumerGroup = "consumer-three", selectorExpression = "tag-three")
public class ThreeConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String message) {
        System.out.println("------------ three consumer get message ------------");
        System.out.println(message);
        System.out.println("time = " + LocalDateTime.now());
    }
}
  1. 测试一下,分别调用 /access/test1 和 /access/test3
调用接口 /access/test1

time = 2021-07-30T15:27:32.179
sendResult = SendResult [sendStatus=SEND_OK, msgId=7F000001295818B4AAC298A99AC10000, offsetMsgId=C0A8010700002A9F000000000004A7A8, messageQueue=MessageQueue [topic=basic-topic, brokerName=DESKTOP-51A3NCJ, queueId=0], queueOffset=32]
------------ one consumer get message ------------
路漫漫其修远兮,吾将上下而求索
time = 2021-07-30T15:27:32.214



调用接口 /access/test3

time = 2021-07-30T15:28:45.940
8d872f17-f4ab-4280-b39b-cf799a586f72 send success!
------------ three consumer get message ------------
烟花易冷
time = 2021-07-30T15:28:55.949

​ 使用异步发送的方法,并且设置延迟等级为3 , 可以看到 消费者接收时间-生产者发送时间 = 10.009s, 与3级延迟的10s差异很小, 能满足大多场合使用

4. 探索之路

  1. 事务消息 , MQ保证最终一致性,解决分布式事务

    流程: 服务A开始事务时, 会向MQ发送一个预备消息, 发送了但又没完全发送,MQ也不会推送到消费者,随后服务A继续执行自己的本地事务,完成后才会确认提交消息,MQ这时才会向消费者推送消息

    MQ的消费者B, 其收到消息后,会执行业务,完成后提交本地事务, 并进行消息的签收,若失败则会将消息进行重试的推送,直至消息被签收为止

  2. 消费模式

    RocketMQ有两种消费模式 集群模式和广播模式

    首先需要明白一个概念,消费者的集群的定义,对于多个消费者来说,决定他们是否处于同一集群环境下,主要看他们的consumer-group是否相同,相同的消费者组名处于同一集群环境

    集群模式:

    该模式下,生产者向MQ发送了一个条消息,MQ会使用负载均衡算法选择集群下满足条件的一个消费者进行消费

    满足条件的意思是topic和tag也必须匹配上发送的消息

    广播模式:

    该模式中,集群下所有满足条件消费者都会收到消息,进行消费

  3. 不同集群下的消费者是否能接受满足条件的消息

    可以的,不同集群下的消费者,能够匹配上(topic:tag) 消息,就能够进行消费, 为了防止消息重复消费,使用时避免使用重复tag

  4. 不指定tag的消息 或者 不指定tag的消费者,会有什么影响? 这里分情况讨论

    消息不指定tag,那就只有没用指定tag的消费者进行消费

    消费者不指定tag, 那么该消费者能够接收所有tag匹配的消息进行消费(不在同一集群的情况下)

    举个栗子
    message (topic=basic, tag = null)  那就只有consumer(topic=basic,consumergroup=onlyone) 能接收
    而 consumer(topic=basic,consumergroup=onlyone , tag=everything) 不能接收
    
    message (topic=basic, tag = tag1) 和 message (topic=basic, tag = null) 
    consumer(topic=basic,consumergroup=onlyone) 都可以接收
    而 consumer(topic=basic,consumergroup=onlytwo,tag=1) 可以接收前者 ,而不能接收后者
    
  5. 看一下消费者的注解@RocketMQMessageListener ,可以指定的配置信息

    // 设置消费者组名
    String consumerGroup();
    
    // 设置topic主题名
    String topic();
    
    // 设置tag名
    String selectorExpression() default "*";
    
    //消费者的处理消息模式,默认并发,需要保证消息顺序读写可以使用 ConsumeMode.ORDERLY
    ConsumeMode consumeMode() default ConsumeMode.CONCURRENTLY;
    
    //消费模式 默认集群模式,可以设置为广播模式MessageModel.BROADCASTING
    MessageModel messageModel() default MessageModel.CLUSTERING;
    
    //消费者使用的是多线程消费消息,减小以降低服务器压力
    int consumeThreadMax() default 64;
    
    //消费者默认超时时间 3s
    long consumeTimeout() default 30000L;
    
  6. 设置rocketmq开机则启动

    新建一个文件,命名rocket-start.vbs,编辑内容:

    createobject("wscript.shell").run "{install_location}\rocketmq-all-4.8.0-bin-release\bin\mqnamesrv.cmd",0
    wscript.sleep 1000*2
    createobject("wscript.shell").run "{install_location}\rocketmq-all-4.8.0-bin-release\bin\mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true",0
    wscript.sleep 1000*2
    createobject("wscript.shell").run "java -jar {install_location}\rocketmq-externals-master\rocketmq-console\target\rocketmq-console-ng-2.0.0.jar",0
    

    打开电脑的任务计划程序 , 创建任务

    常规: 为其命令 , 增其描述 , 设其账户

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8tEBcKHl-1627634880353)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210730164317023.png)]

​ 触发器: 新增触发器, 选择任务开始时期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PYdPJT5g-1627634880354)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210730164508622.png)]

​ 操作 : 新增操作命令 , 操作选择执行操作,程序选择刚才制作的rocket-start.vbs文件 , 然后确定保存即可

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值