目录
RocketMQ的基本概念
RocketMQ架构图
RocketMQ 四种集群部署方式
broker如果配置集群:
RocketMQ的一些重要问题
broker内存和硬盘都满了怎么解决:
broker集群动态扩容:
topic拆分多个不同队列(rocketmq高吞吐的一个原因):
顺序消息的产生背景:
解决消息顺序的核心思想:
保证消息顺序图1:
保证消息顺序图2(高吞吐量):
单版本rocketMQ如果增加吞吐量:
存在顺序问题的代码:
代码解决顺序问题:
顺序消费完整代码:
RocketMQ的基本概念
NameServer
它是一个几乎无状态节点,可集群部署,节点之间无任何信息同步;
是专为RocketMQ设计的轻量级名称服务,代码小于1000行。
Broker
Broker分为Master与Slave,一个Master可以对应多个Slave;
Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave。
Broker以group分开,每个group只允许一个master,若干个slave。
只有master才能进行写入操作,slave不允许。
slave从master中同步数据。同步策略取决于master的配置,可以采用同步双写,异步复制两种。
客户端消费可以从master和slave消费。在默认情况下,消费者都从master消费,在master挂后,客户端由于从Name Server中感知到Broker挂机,就会从slave消费。
Broker向所有的NameServer结点建立长连接,注册Topic信息。
Producer
Producer 与Name Server集群中的其中一个节点建立长连接,定期(默认30秒)从Name Server取Topic路由信息,
如果某master宕机,因为默认30秒才能从nameServer发现可能导致消息投递失败。
Consumer
Consumer与Name Server集群中的其中一个节点建立长连接,定期(默认30秒)从Name Server取Topic路由信息
Producer Group
一个Producer Group下包含多个Producer生产者实例,可以是多台机器,也可以是一台机器的多个进程,或者一个进程的多个Producer生产者对象,
一个Producer Group可以发送多个Topic消息。
Producer Group作用如下:
1.标识一类的Producer
2.可以通过运维工具查询这个发送消息应用下有多个Producer实例
3.发送分布式事务消息时,如果 Producer中途意外宕机,Broker会主动回调 Producer Group内的任意一台机器来确认事务状态。
Consumer Group
消费者组概念和kafka概念一样:不同消费组订阅同一topic的消息,相当于是广播订阅,topic中的每条消息会分别被每个消费组进行消费
用来表示一个消费消息应用,一个Consumer Group下包含多个Consumer消费者实例,可以是多台机器,也可以是多个进程,或者是一个进程的多个Consumer消费者对象,
一个Consumer Group下的多个Consumer消费者以均摊方式消费消息,如果设置为广播方式,那么这个 Consumer Group下的每个实例都消费全量数据。
tags
用于同一topic下区分不同的消息
发送消息注意事项:
一个应用尽可能用一个Topic,消息子类型用tags来标识,tags可以由应用自由设置。只有发送消息设置了tags,
消费方在订阅消息时,才可以利用tags在broker做消息过滤。message.setTags("TagA");
RocketMQ架构图
生产者连接主Broker,只会将消息投递到主Broker中;(主同步到备)
消费者连接主、备Broker进行消费;(加强消费速度)
RocketMQ 四种集群部署方式
- 单个Master节点, 缺点就是如果宕机之后可能整个服务不可用;
- 多个Master节点,分摊存放我们的消息,缺点:没有Slave节点,主的Master节点宕机之后消息数据可能会丢失的;
- 多个Master和Slave节点,之间数据同步采用异步形式,效率非常高,数据可能短暂产生延迟(毫秒级别的)
- 多个Master和Slave节点,采用同步形式,效率比较低、数据不会产生延迟。
broker如何配置集群:
多个broker(master)如果知道自己是集群:连接相同的nameServer。
如何设置主/备:BrokerId为0表示Master,非0表示Slave
修改broker.conf文件配置集群:
注意:nameServer默认端口为9876,broker默认监听端口10911
#集群名称,可以区分不同集群,不同的业务可以建多个集群 brokerClusterName=mayikt # Broker 的名称, Master 和Slave 通过使用相同的Broker 名称来表明相互关系,以说明某个Slave 是哪个Master 的Slave 。 brokerName=broker-a # 一个Master Barker 可以有多个Slave, 0 表示Master ,大于0 表示不同Slave 的ID 。 brokerId=0 #与fileReservedTim巳参数呼应,表明在几点做消息删除动作,默认值04 表示凌晨4 点。 deleteWhen=04 #namesrver集群地址 namesrvAddr=192.168.2.190:9876;192.168.2.191:9876 #topic默认创建的队列数 defaultTopicQueueNums=4 autoCreateTopicEnable=true #是否允许Broker自动创建订阅组,建议线下开启,线上关闭,默认【true】 autoCreateSubscriptionGroup=true #Broker 监听的端口号,如果一台机器上启动了多个Broker , 则要设置不同的端口号(每个端口间隔最少2,否则会报错),避免冲突。 listenPort=10911 brokerIp=192.168.1.1 # 注意brokerIp是内部通讯ip,内网能ping通192.168.1.1就行或者换别的。 |
配置完成后正常启动broker即可。
rocketMQ目录下执行:
.\bin\mqbroker.cmd -c .\conf\broker.conf -n 127.0.0.1:9876 &
broker集群启动第二个的时候报错:
broker的listenPort配置 间隔小于2 则会出现上面的bug
RocketMQ的一些重要问题
broker内存和硬盘都满了怎么解决:
1. broker支持把消息存放到mysql。
2. broker扩容
broker集群动态扩容:
直接加broker节点就行,因为生产者和消费者获取broker地址是动态获取的(从nameServer中获取)。
添加broker接口后,新broker节点会把自身信息注册到nameServer上。
集群下生产者投递消息:key % broker(主)集群数量 = 消息投递到那台具体的broker上。
topic拆分多个不同队列(rocketmq高吞吐的一个原因):
在rocktmq中(概念):topic是队列的集合。
rocketmq单机情况下,创建一个topic,默认会将该topic分成4个队列进行存放。(默认4个);
kafka中分区的概念 = rocketmq中的多队列。
将该topic分成4个队列进行存放图:
看完上图的流程后,发现这样就存在了顺序消费的问题!
顺序消息的产生背景:
生产者向同一个主题中投递的消息行为不同,就会产生顺序问题。
例如行为:insert、update、delete,需要先insert才能update。
- 如果消息行为相同就不需要关注顺序问题,如:发送邮件、短信。
解决消息顺序的核心思想:
同kafka解决思想相同:1. 把消息投递到一个topic队列中,由一个消费者进行消费!
1.2 相同的业务逻辑一定要放到同一个队列中,由一个消费者进行消费。
2. topic中的每个队列1:1均等匹配消费者。
MQ遵循先进先出原则。
保证消息顺序图1:
保证消息顺序图2(高吞吐量):
单版本rocketMQ如果增加吞吐量:
只需要增加topic的队列总数即可。 每个队列需要1:1的匹配消费者。
存在顺序问题的代码:
// 生产者
@RestController
public class ProducerController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@RequestMapping("/sendMsg")
public String sendMsg() {
OrderEntity orderEntity = new OrderEntity("insert","增加");
rocketMQTemplate.convertAndSend("mayikt", orderEntity);
OrderEntity orderEntityb = new OrderEntity("update","修改");
rocketMQTemplate.convertAndSend("mayikt", orderEntityb);
OrderEntity orderEntityc = new OrderEntity("delete","删除");
rocketMQTemplate.convertAndSend("mayikt", orderEntityc);
return "success";
}
}
// 消费者
@Service
@RocketMQMessageListener(topic = "mayikt", consumerGroup = "mayiktTopic")
public class OrdeConsumer implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt massage) {
System.out.println("consumer:" + "队列:"+massage.getQueueId() +
","+new String(massage.getBody()));
}
}
以上代码打印:
可以看到同一组业务消息是没顺序的,是因为生产者把消息均摊投递到了mayikt主题的队列中了。
代码解决顺序问题:
解决:生产者把消息投递到mayikt主题的同一个队列中,然后一个消费者进行消费。
看下图,注意:生产者投递到同一队列,但是消费数据还是错乱,是因为消费者默认为多线程消费队列,所以需要设置消费者线程数为1。
消费者消费队列默认会开20个线程,
消费者消费发生错乱是因为多线程消费的队列。
添加注解:设置消费者为有序消费和消费线程数设为1:
为了顺序:主题队列数量和消费者数量最好是1:1
顺序消费完整代码:
生产者:
package com.mayikt.producer;
import com.alibaba.fastjson.JSONObject;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/*** @title: ProducerController
* @description: 每特教育独创第五期互联网架构课程
* @date 2020/1/321:19
*/
@RestController
public class ProducerController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@RequestMapping("/sendMsg")
public String sendMsg() throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
Long orderId = System.currentTimeMillis();
String insertSql = getSqlMsg("insert", orderId);
String updateSql = getSqlMsg("update", orderId);
String deleteSql = getSqlMsg("delete", orderId);
Message insertMsg = new Message("yushengjun-topic", insertSql.getBytes());
Message updateMsg = new Message("yushengjun-topic", updateSql.getBytes());
Message deleteMsg = new Message("yushengjun-topic", deleteSql.getBytes());
DefaultMQProducer producer = rocketMQTemplate.getProducer();
rocketMQTemplate.getProducer().send(insertMsg
, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg,
Object arg) {
// 该消息存放到队列0中
return mqs.get(0);
}
}, orderId);
rocketMQTemplate.getProducer().send(updateMsg
, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg,
Object arg) {
// 该消息存放到队列0中
return mqs.get(0);
}
}, orderId);
rocketMQTemplate.getProducer().send(deleteMsg
, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg,
Object arg) {
// 该消息存放到队列0中
return mqs.get(0);
}
}, orderId);
return orderId + "";
}
public String getSqlMsg(String type, Long orderId) {
JSONObject dataObject = new JSONObject();
dataObject.put("type", type);
dataObject.put("orderId", orderId);
return dataObject.toJSONString();
}
}
消费者:
package com.mayikt.consumer;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
@Service
@RocketMQMessageListener(topic = "yushengjun-topic", consumerGroup = "mayiktTopic", consumeMode
= ConsumeMode.ORDERLY, consumeThreadMax = 1
)
public class OrdeConsumer implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt message) {
System.out.println(Thread.currentThread().getName() + "," +
"队列" + message.getQueueId() + "," + new String(message.getBody()));
}
}
订单实体类:
package com.mayikt.entity;
import java.util.Date;
import lombok.Data;
@Data
public class OrderEntity {
private Long id;
// 订单名称
private String name;
// 订单时间
private Date orderCreatetime;
// 下单金额
private Double orderMoney;
// 订单状态
private int orderState;
// 商品id
private Long commodityId;
// 订单id
private String orderId;
public OrderEntity(Long id, String name, Date orderCreatetime, Double orderMoney, int orderState, Long commodityId, String orderId) {
this.id = id;
this.name = name;
this.orderCreatetime = orderCreatetime;
this.orderMoney = orderMoney;
this.orderState = orderState;
this.commodityId = commodityId;
this.orderId = orderId;
}
public OrderEntity() {
}
}
运行:
package com.mayikt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AppRocketMQ {
public static void main(String[] args) {
SpringApplication.run(AppRocketMQ.class);
}
}
yml配置文件:
rocketmq:
###连接地址nameServer
name-server: 192.168.212.169:9876;
producer:
group: mayikt_producer
spring:
datasource:
url: jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
server:
port: 8088
pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mayikt</groupId>
<artifactId>mayikt_rocketmq</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<!-- springboot-web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 阿里巴巴数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.14</version>
</dependency>
</dependencies>
</project>