这里使用的不是虚拟机,是阿里云的ECS服务器,系统版本为CentOs7。
上传RocketMQ
RocketMQ的下载包:密码6666
将RocketMQ的zip包上传到阿里云服务器,使用命令解压。
unzip /usr/soft/rocketmq-all-4.4.0-bin-release.zip # 这个目录要改为自己的RocketMQ
mv /usr/soft/rocketmq-all-4.4.0-bin-release/ /usr/soft/rocketmq-4.4/ # 重命名方便管理
修改配置文件
进入RocketMQ的conf目录,修改broker.conf,新增三行(去掉我注释的中文)。
enablePropertyFilter=true # 支持标签顾过滤消息
namesrvAddr=127.0.0.1:9876 # 你的mqnamesrv地址
brokerIP1=11.11.11.11 # 你的阿里云服务器的公网ip
修改启动参数
因为默认的RocketMQ启动的时候需要的1JVM内存很多,会报错,所以我们将他变小一点即可。默认是1G,修改为512m。
vi /usr/soft/rocketmq-4.4/bin/runbroker.sh
vi /usr/soft/rocketmq-4.4/bin/runserver.sh
防火墙和安全组开放几个端口
开放9876、10911、10912、10909以及你指定的可视化工具启动的端口,这里步骤不再赘述,有疑问可以在评论区回复或者翻我的Linux的学习文章进行学习。
启动服务
nohup sh /usr/soft/rocketmq-4.4/bin/mqnamesrv &
nohup sh mqbroker -n localhost:9876 -c …/conf/broker.conf &
查看日志
在RocketMQ的根目录的bin目录中,有一个nohup.out用于存放启动的日志信息。无报错即成功,日志信息和1Windows的差不多。
2.5、RocketMQ的架构
2.6、RocketMQ的核心概念
-
名字服务Name Server(邮局):消息队列的协调者,Broker向它注册路由信息,同时Producer和Consumer向其获取路由信息,名称服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群,但相互独立,没有信息交换。
-
代理服务器Broker Server(邮递员):Broker是RocketMQ的核心,消息中转角色,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。
-
生产者Producer(寄件人):负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器。RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息,单向发送不需要。
-
消费者Consumer(收件人):负责消费消息,一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式:拉取式消费、推动式消费。
-
Topic(省份):用来区分不同类型的消息,表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。
-
标签Tag:为消息设置的标志,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。
-
消息队列MessageQueue(邮件):为了提高性能和吞吐量,引入了Message Queue,一个Topic可以设置一个或多个Message Queue,这样消息就可以并行往各个Message Queue发送消息,消费者也可以并行的从多个 Message Queue读取消息
-
消息内容Message:息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。
-
生产者组Producer Group:生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组。
-
消费者组Consumer Group:消费者组,消费同一类消息的多个 consumer 实例组成一个消费者组。
三、入门使用
3.1、导入依赖
org.apache.rocketmq
rocketmq-client
4.4.0
3.2、发送消息
发送消息的步骤1:
-
创建消息生产者, 指定生产者所属的组名。
-
指定Nameserver地址。
-
启动生产者。
-
创建消息对象,指定主题、标签和消息体。
-
发送消息。
-
关闭生产者。
public class Producer {
public static void main(String[] args) throws Exception {
// 创建一个生产者对象,并且指定一个生产者组
DefaultMQProducer producer = new DefaultMQProducer(“xiaolin-producer”);
// 指定名字服务器地址
producer.setNamesrvAddr(“127.0.0.1:9876”);
// 启动生产者
producer.start();
// 创建一个消息,参数分别为:主题、标签、消息体
Message message = new Message(“hello_demo1”,“tag1”,“你好,我是消息1”.getBytes(“utf-8”));
// 发送消息
producer.send(message);
// 关闭资源
producer.shutdown();
}
}
3.3、消费消息(接收消息)
消费消息的步骤:
1. 创建消息消费者, 指定消费者所属的组名 2. 指定Nameserver地址 3. 指定消费者订阅的主题和标签 4. 设置回调函数,编写处理消息的方法 5. 启动消息消费者
public class ConsumerDemo {
public static void main(String[] args) throws MQClientException {
// 创建一个拉取消息对象,并指定所属组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(“consumer-demo”);
// 指定名字服务器地址
producer.setNamesrvAddr(“127.0.0.1:9876”);
// 指定消费者订阅的主题和标签,第二个参数用于过滤条件用于指定接收什么tag,什么都接收
consumer.subscribe(“hello_demo1”, “*”);
// 注册一个消息监听器
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List msgs,
ConsumeConcurrentlyContext consumeConcurrentlyContext) {
// 可能有多个消息
for (MessageExt msg : msgs) {
// 打印消息
System.out.println(new String(msg.getBody()));
}
// 返回发送成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动消费者
consumer.start();
}
}
四、消息的类型
4.1、普通消息
RocketMQ提供三种方式来发送普通消息:可靠同步发送、可靠异步发送和单向发送。
4.1.1、可靠同步发送
同步发送是指消息发送方发出数据后,会在收到接收方发回响应之后才发下一个数据包的通讯方式。这种方式应用场景非常广泛,例如重要通知邮件、报名短信通知、营销短信系统等。
public class SyncProducer {
public static void main(String[] args) throws Exception {
// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer(“xiaolin-producer”);
// 设置NameServer的地址
producer.setNamesrvAddr(“127.0.0.1:9876”);
// 启动Producer实例
producer.start();
for (int i = 0; i < 100; i++) {
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message(“04-producer-type” /* Topic */,
“TagA” /* Tag */,
("这是一条同步消息 " + i).getBytes(“utf-8”) /* Message body */);
//发送同步消息到一个Broker
SendResult sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.println(JSON.toJSONString(sendResult));
}
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
4.1.2、异步消息
异步发送是指发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。发送方通过回调接口接收服务器响应,并对响应结果进行处理。
异步发送一般用于链路耗时较长,对 RT 响应时间较为敏感的业务场景,例如用户视频上传后通知启动转码服务,转码完成后通知推送转码结果等。
public class ASyncProducer {
public static void main(String[] args) throws Exception {
// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer(“xiaolin-producer”);
// 设置NameServer的地址
producer.setNamesrvAddr(“127.0.0.1:9876”);
// 启动Producer实例
producer.start();
for (int i = 0; i < 100; i++) {
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message(“04-producer-type” /* Topic */,
“TagA” /* Tag */,
(“我是异步消息” + i).getBytes(“utf-8”) /* Message body */
);
//发送同步消息到一个Broker
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(“消息发送成功”);
System.out.println(JSON.toJSONString(sendResult));
}
@Override
public void onException(Throwable e) {
System.out.println(“消息发送失败”+e.getMessage());
System.out.println(“处理失败消息”);
}
});
}
// 让线程不要终止,否则会报错
Thread.sleep(30000000);
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
4.1.3、单向消息
单向发送是指发送方只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答。适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。
public class OneWayProducer {
public static void main(String[] args) throws Exception {
// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer(“xiaolin-producer”);
// 设置NameServer的地址
producer.setNamesrvAddr(“127.0.0.1:9876”);
// 启动Producer实例
producer.start();
for (int i = 0; i < 100; i++) {
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message(“04-producer-type” /* Topic */,
“TagA” /* Tag */,
(“我是单向消息” + i).getBytes(“utf-8”) /* Message body */
);
//发送单向消息到一个Broker
producer.sendOneway(msg);
}
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
}
}
4.1.4、三种发送方式的对比
发送方式 | 发送的时间 | 发送反馈结果 | 是否丢失数据 |
同步发送 | 快 | 有 | 不丢失 |
异步发送 | 快 | 有 | 不丢失 |
单向消息 | 较快 | 无 | 可能丢失 |
4.2、顺序消息
虽然RocketMQ的数据结构是队列,看起来天生支持顺序消息,当只有一个队列的时候,他就天生支持顺序消息,但是Brocket内部有多个队列,发送多条消息的时候,Broker会按照轮询的方式将多个消息放在不同的队列,消费者采用多线程的方式去消费消息,所以无法保证消费消息的方式和发送消息的方式一样的。解决方式是将消息全部发送到一个队列里面。
比如一个订单的流程是:创建、付款、推送、完成。订单号相同的
顺序消息是消息队列提供的一种严格按照顺序来发布和消费的消息类型。
/**
- 订单构建者
*/
public class OrderStep {
private long orderId;
private String desc;
public long getOrderId() {
return orderId;
}
public void setOrderId(long orderId) {
this.orderId = orderId;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return “OrderStep{” +
“orderId=” + orderId +
“, desc='” + desc + ‘’’ +
‘}’;
}
public static List buildOrders() {
// 1039L : 创建 付款 推送 完成
// 1065L : 创建 付款
// 7235L :创建 付款
List orderList = new ArrayList();
OrderStep orderDemo = new OrderStep();
orderDemo.setOrderId(1039L);
orderDemo.setDesc(“创建”);
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(1065L);
orderDemo.setDesc(“创建”);
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(1039L);
orderDemo.setDesc(“付款”);
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(7235L);
orderDemo.setDesc(“创建”);
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(1065L);
orderDemo.setDesc(“付款”);
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(7235L);
orderDemo.setDesc(“付款”);
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(1065L);
orderDemo.setDesc(“完成”);
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(1039L);
orderDemo.setDesc(“推送”);
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(7235L);
orderDemo.setDesc(“完成”);
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(1039L);
orderDemo.setDesc(“完成”);
orderList.add(orderDemo);
return orderList;
}
}
public class Producer {
public static void main(String[] args) throws Exception {
//1.创建消息生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer(“group1”);
//2.指定Nameserver地址
producer.setNamesrvAddr(“127.0.0.1:9876”);
//3.启动producer
producer.start();
//构建消息集合
List orderSteps = OrderStep.buildOrders();
//发送消息
for (int i = 0; i < orderSteps.size(); i++) {
String body = orderSteps.get(i) + “”;
Message message = new Message(“OrderTopic”, “Order”, “i” + i, body.getBytes());
/**
-
参数一:消息对象
-
参数二:消息队列的选择器
-
参数三:选择队列的业务标识(订单ID)
*/
SendResult sendResult = producer.send(message, new MessageQueueSelector() {
/**
-
@param mqs:队列集合
-
@param msg:消息对象
-
@param arg:业务标识的参数
-
@return
*/
@Override
public MessageQueue select(List mqs, Message msg, Object arg) {
long orderId = (long) arg;
long index = orderId % mqs.size();
return mqs.get((int) index);
}
}, orderSteps.get(i).getOrderId());
System.out.println(“发送结果:” + sendResult);
}
producer.shutdown();
}
}
public class Consumer {
public static void main(String[] args) throws MQClientException {
//1.创建消费者Consumer,制定消费者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(“group1”);
//2.指定Nameserver地址
consumer.setNamesrvAddr(“192.168.25.135:9876;192.168.25.138:9876”);
//3.订阅主题Topic和Tag
consumer.subscribe(“OrderTopic”, “*”);
//4.注册消息监听器
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.println(“线程名称:【” + Thread.currentThread().getName() + “】:” + new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
//5.启动消费者
consumer.start();
System.out.println(“消费者启动”);
}
}
4.3、事务消息
RocketMQ提供了事务消息,通过事务消息就能达到分布式事务的最终一致。事务消息交互的过程如下:
事务消息的基本概念:
-
半事务消息:暂不能投递的消息,发送方已经成功地将消息发送到了RocketMQ服务端,但是服务端未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,处于该种状态下的消息即半事务消息。
-
消息回查:由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,RocketMQ服务端通过扫描发现某条消息长期处于“半事务消息”时,需要主动向消息生产者询问该消息的最终状态(Commit 或是 Rollback),该询问过程即消息回查。
事务消息的发送步骤:
-
发送方将半事务消息发送到RocketMQ服务端。
-
RocketMQ服务端将消息持久化后,向发送方返回确认消息已经发送成功,此时消息为半事务消息。
-
发送方开始执行本地事务逻辑。
-
发送方根据本地事务执行结果向服务端提交二次确认(Commit或者是Rollback),服务端收到Commit状态则将事务消息标记为可投递,订阅方最终将收到该消息。服务端如果收到的是Rollback状态则删除半事务消息,订阅方将不会接收该消息。
事务消息回查步骤:
-
在断网或者是应用重启的特殊情况下,上述步骤4提交的二次确认最终未到达服务端,经过固定时间后服务端将对该消息发起消息回查。
-
发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
-
发送方根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行操作。
4.4、延时消息
比如电商里,提交了一个订单就可以发送一个延时消息,1h后去检查这个订单的状态,如果还是未付款就取消订单释放库存。我们就可以使用延时消息来完成这个功能。
延时消息的使用限制,现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18。
private String messageDelayLevel = “1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”;
消息消费失败会进入延时消息队列,消息发送时间与设置的延时等级和重试次数有关。
4.4.1、生产者
public class ScheduledMessageProducer {
public static void main(String[] args) throws Exception {
// 实例化一个生产者来产生延时消息
DefaultMQProducer producer = new DefaultMQProducer(“wolfcode-producer”);
producer.setNamesrvAddr(“127.0.0.1:9876”);
// 启动生产者
producer.start();
Message message = new Message(“06-delay”, (“delay message”).getBytes());
// 设置延时等级3,这个消息将在10s之后发送(现在只支持固定的几个时间,详看delayTimeLevel)
message.setDelayTimeLevel(3);
// 发送消息
producer.send(message);
// 关闭生产者
producer.shutdown();
}
}
4.4.2、消费者
public class ScheduledMessageConsumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(“wolfcode_consumer”);
consumer.setNamesrvAddr(“127.0.0.1:9876”);
// 订阅Topics
consumer.subscribe(“06-delay”, “*”);
// 注册消息监听者
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List messages, ConsumeConcurrentlyContext context) {
for (MessageExt message : messages) {
System.out.println(“Receive message[msgId=” + message.getMsgId() + "] " );
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动消费者
consumer.start();
}
}
4.5、消息的过滤
在消费消息的时候,我们可以指定消费哪些消息,这个时候就需要用到消息的过滤,他分为1两种过滤:
-
通过标签过滤。
-
通过SQL语句的方式过滤。
4.5.1、通过标签过滤
4.5.1.1、生产者
public class Producer {
public static void main(String[] args) throws Exception {
//1.创建消息生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer(“group1”);
//2.指定Nameserver地址
producer.setNamesrvAddr(“127.0.0.1:9876”);
//3.启动producer
producer.start();
for (int i = 0; i < 3; i++) {
//4.创建消息对象,指定主题Topic、Tag和消息体
/**
-
参数一:消息主题Topic
-
参数二:消息Tag
-
参数三:消息内容
*/
Message msg = new Message(“FilterTagTopic”, “Tag2”, (“消息的过滤” + i).getBytes());
//5.发送消息
SendResult result = producer.send(msg);
//发送状态
SendStatus status = result.getSendStatus();
System.out.println(“发送结果:” + result);
//线程睡1秒
TimeUnit.SECONDS.sleep(1);
}
//6.关闭生产者producer
producer.shutdown();
}
}
4.5.1.2、消费者
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要系统化学习资料的朋友,可以戳这里获取
.CONSUME_SUCCESS;
}
});
// 启动消费者
consumer.start();
}
}
4.5、消息的过滤
在消费消息的时候,我们可以指定消费哪些消息,这个时候就需要用到消息的过滤,他分为1两种过滤:
-
通过标签过滤。
-
通过SQL语句的方式过滤。
4.5.1、通过标签过滤
4.5.1.1、生产者
public class Producer {
public static void main(String[] args) throws Exception {
//1.创建消息生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer(“group1”);
//2.指定Nameserver地址
producer.setNamesrvAddr(“127.0.0.1:9876”);
//3.启动producer
producer.start();
for (int i = 0; i < 3; i++) {
//4.创建消息对象,指定主题Topic、Tag和消息体
/**
-
参数一:消息主题Topic
-
参数二:消息Tag
-
参数三:消息内容
*/
Message msg = new Message(“FilterTagTopic”, “Tag2”, (“消息的过滤” + i).getBytes());
//5.发送消息
SendResult result = producer.send(msg);
//发送状态
SendStatus status = result.getSendStatus();
System.out.println(“发送结果:” + result);
//线程睡1秒
TimeUnit.SECONDS.sleep(1);
}
//6.关闭生产者producer
producer.shutdown();
}
}
4.5.1.2、消费者
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。