原理:前人之述备矣
1. 启动RocketMQ
-
下载rocketmq http://rocketmq.apache.org/dowloading/releases/
-
配置环境变量
变量名:
ROCKETMQ_HOME
变量值:
${your_install_location}\rocketmq-all-4.8.0-bin-release
-
启动RocketMQ相关组件
-
namesrv : 注册中心,类似nacos的作用,统一管理broker
新建文件:
start-namesrv.cmd
编辑输入 : start ${you_install_location}\rocketmq-all-4.8.0-bin-release\bin\mqnamesrv.cmd 并发送到桌面 -
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了,此外还提供了可视化页面,方便开发时校对信息和队列管理
-
下载地址 : https://github.com/apache/rocketmq-externals.git
-
编辑文件 : 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=
- 在回到rocketmq-console项目的根路径,重新编译项目 mvn clean package -Dmaven.test.skip=true
- 新建文件
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集成跳过测试
- 创建maven项目,引入rocketmq - client 包
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.8.0</version>
</dependency>
- 在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
- 进入可视化工具 查看刚才发送的消息
success!
3. SpringBoot项目集成RocketMQ
- 引入springboot集成rocketmq的包
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
- yml中进行配置
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: rocket-sunshine
若报错RocketMQTemplate注入失败,请检查yml是否完成了配置
- 封装消息实体类 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;
}
- 封装消息发送工具类
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());
}
}
- 同步消息没有失败回调接口,发送信息在SendResult中,可以接收返回
- 异步消息根据自己的业务进行失败/成功的处理
- 发送同步消息不关系返回结果可能造成消息丢失,因为其不会等待broker确认,适用于日志或者监控
- 吞吐量 : 3>2>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());
}
}
- 测试, 创建两个消费者接收消息
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());
}
}
- 测试一下,分别调用 /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. 探索之路
-
事务消息 , MQ保证最终一致性,解决分布式事务
流程: 服务A开始事务时, 会向MQ发送一个预备消息, 发送了但又没完全发送,MQ也不会推送到消费者,随后服务A继续执行自己的本地事务,完成后才会确认提交消息,MQ这时才会向消费者推送消息
MQ的消费者B, 其收到消息后,会执行业务,完成后提交本地事务, 并进行消息的签收,若失败则会将消息进行重试的推送,直至消息被签收为止
-
消费模式
RocketMQ有两种消费模式 集群模式和广播模式
首先需要明白一个概念,消费者的集群的定义,对于多个消费者来说,决定他们是否处于同一集群环境下,主要看他们的consumer-group是否相同,相同的消费者组名处于同一集群环境
集群模式:
该模式下,生产者向MQ发送了一个条消息,MQ会使用负载均衡算法选择集群下满足条件的一个消费者进行消费
满足条件的意思是topic和tag也必须匹配上发送的消息
广播模式:
该模式中,集群下所有满足条件消费者都会收到消息,进行消费
-
不同集群下的消费者是否能接受满足条件的消息
可以的,不同集群下的消费者,能够匹配上(topic:tag) 消息,就能够进行消费, 为了防止消息重复消费,使用时避免使用重复tag
-
不指定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) 可以接收前者 ,而不能接收后者
-
看一下消费者的注解
@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;
-
设置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
打开电脑的
任务计划程序
, 创建任务常规: 为其命令 , 增其描述 , 设其账户
触发器: 新增触发器, 选择任务开始时期
操作 : 新增操作命令 , 操作选择执行操作,程序选择刚才制作的rocket-start.vbs
文件 , 然后确定保存即可