概念
- MQ(Message Queue):也就是消息队列。主要是为了吧传送的数据放在队列中,通过消息队列来实现消息传递的作用,其中生产者是把消息放入消息队列中,消费者是到指定的队列中拿到消息进行具体处理。
- 特性
- 削峰填谷:主要是解决系统瞬间(流量突然变大)时系统写压力大于平时系统的处理能力,导致系统崩溃,消息丢失
- 系统解耦合:将系统解耦合。不会使系统过分依赖所有业务模块导致系统臃肿,维护困难
- 提升系统性能:当系统解耦合后,当遇到系统一个系统调用多个系统的时候,可以通过给消息队列发一条消息,再让消息队列通知需要调用的系统。
- 蓄流压测:线上有些链路可能不好压测,可以通过消息队列进行堆积一定量的消息再到一定量时开放消息来压测
RocketMQ的特点:
- 支持事务消息:消息发送和DB操作保持两方最终一致性,rabbitmq,kafka不支持
- 支持结合RocketMQ的多个系统之间数据的最终一致性(多方事务,二方事务是前提)
- 支持延迟消息(rabbitmq(原生不支持,可以通过死信队列来达到延迟发送),kafka不支持)
- 支持指定次数和时间间隔的失败消息重发(kafka不支持,rabbitmq需要手动确认)
- 支持consumer端tag过滤,减少不必要的网络传输(rabbitmq和kafka不支持)
- 支持消息重复消费(rabbitmq不支持,kafka不支持)
内容
集群结构
Name Server
- Name Server是一个无状态的节点,可以集群部署,节点之间也无任何信息同步
Broker
- Broker可以分为Master和Slave,一个Master可以对应多个Slave。但是一个Slave只能对应一个Master,Master与Slave的对应关系通过制定相同的Broker id, 不同的Broker id来定义,Broker id为0表示是Master,非0表示Slave
- 每个Broker与Name Server集群中的所有节点建立了长连接,定时(默认每个30s)注册Topic信息到所有Name Server。Name Server定时(每个10s)扫描所有存货Broker的连接,如果Name Server超过2分钟没有收到心跳,则Name Server断开与Broker的连接
Producer
- Producer和Name Server集群中的其中一个节点(随机选择一个)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务服务的Master建立长连接,而且定时向Master发送心跳。Product完全无状态,可以集群部署
- Producer每间隔30s(可以由ClientConfig的pollNameServerInterval配置)从Name Server获取所有的topic队列
- Broker每隔10s扫描所有存活的连接,如果Broker在2分钟之内没有收到心跳,则关闭与Producer的连接
Consumer
- Consumer与Name Server集群中的其中一个节点(随机选择一个)建立长连接,定期从Name Server取Topic路由信息,并向提供Topic服务的Master,Slave建立长连接,并且定向向Master,Slave发送心跳。
- Consumer可以从Master订阅消息,也可以从Slave订阅消息,订阅规则是由Broker指定
- Consumer每隔30s从Name Server获取topic的最新情况,这意味着Broker不可用时,Consumer最多需要30s才能感知
- Consumer每隔30s向所有关联的broker发送心跳,Broker每隔10s扫描所有存活的连接,若某个连接2分钟内没有发送心跳数据,则关闭连接;并向该Consumer Group的所有Consumer发送通知,Group内的Consumer重新分配队列,然后继续消费。当Consumer得到master宕机通知后,转向slave消费,slave不能保证master的消息100%都同步过来,因此会有少量的消息丢失。但是一旦master恢复,未同步过去的消息会被最终消费掉。
- 消费者队列是消费者连接之后(或者之前有 连接过)才创建的。我们将原生的消费者标识由**{ip}@{消费者group}扩展为{IP}@{消费者group}{topic}{tag}**,(比如:xxx.xxx.xxx.xxx@mqtest_producergroup_2m2sTest_tag-test)。任何一个元素不同,都认为是不同的消费端,每个消费端会拥有一份自已消费队列(默认是broker队列数量*broker数量)
centos安装RocketMQ
- 下载
[下载地址](http://rocketmq.apache.org/release_notes/release-n%20otes-4.7.1/)
- 上传到指定的linux目录
- 解压缩
unzip rocketmq-all-4.7.1-bin-release.zip
- 启动NameServer
nohup ./bin/mqnamesrv &
- 检查是否启动成功
netstat -an | grep 9876
-
启动broker
启动之前需要编辑配置文件,修改JVM内存的设置,默认是内存是4GB(如果启动中有问题,可以改成默认)- 修改配置
cd bin vim runserver.sh
vim runbroker.sh
-
启动Broker
nohup ./mqbroker -n localhost:9876 &
查看Broker是否启动
tail -f ~/logs/rocketmqlogs/broker.log
- 测试
- 消息发送
-消息接收cd bin export NAMESRV_ADDR=localhost:9876 ./tools.sh org.apache.rocketmq.example.quickstart.Prod ucer
cd bin
export NAMESRV_ADDR=localhost:9876
./tools.sh org.apache.rocketmq.example.quickstart.Cons umer
```
- 关闭RocketMQ
cd bin
./mqshutdown broker
./mqshutdown namesrv
安装RocketMQ控制台
-
解压缩,修改配置,打包(其实是一个前端应用)
-
打包命令
mvn clean package -Dmaven.test.skip=true
- 进入target启动jar
java -jar rocketmq-console-ng-1.0.0.jar
- 如果是报错
(存在原因:JDK9以上版本缺少jar,需要在项目pom中手动导入某些jar包)
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId> <artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
- 重新构建maven
//TEST错误可忽略,
mvn clean install
- 浏览器访问localhost:9877,如果报错
说明在RocketMQ安装中linux的端口号没有打开,需要开放10909和9876端口
firewall-cmd --zone=public --add- port=10909/tcp --permanent
firewall-cmd --zone=public --add- port=9876/tcp --permanent
systemctl restart firewalld.service
firewall-cmd --reload
- 重新启动控制台项目
项目测试消息发送
- pom.xml引入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-spring-boot-
starter</artifactId>
<version>2.1.0</version>
</dependency>
- 生产消息
public static void main(String[] args) throws Exception {
//创建消息生产者
DefaultMQProducer producer = new
DefaultMQProducer("myproducer-group");
//设置NameServer
producer.setNamesrvAddr("192.168.248.129:9 876");
//启动生产者
producer.start();
//构建消息对象
Message message = new Message("myTopic", "myTag", ("TestMQ").getBytes());
//发送消息
SendResult result = producer.send(message, 1000);
System.out.println(result);
//关闭生产者
producer.shutdown();
}
如果运行报错sendDefaultImpl call timeout,可以手动关闭Linux防火墙
# 关闭防火墙
systemctl stop firewalld # 查看防火墙状态
firewall-cmd --state
//或者开放10911端口
firewall-cmd --zone=public --add- port=10911/tcp --permanent
systemctl restart firewalld.service firewall-cmd --reload
可以从RocketMQ控制台查看消息
- 消费端消费消息
public static void main(String[] args) throws MQClientException {
//创建消息消费者 DefaultMQPushConsumer consumer =
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myconsumer- group");
//设置NameServer
consumer.setNamesrvAddr("192.168.248.129:9 876");
//指定订阅的主题和标签
consumer.subscribe("myTopic", "*"); //回调函数
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus
consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
log.info("Message=> {}",list);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者
consumer.start();
}