优势
劣势
概念
生产者——broker——消费者
broker就是部署了mq的机器就叫broker
工作流程
· 不论是 broker还是 生产者还是消费者 ,在启动的时候都需要把自己注册到 nameserver上,这个nameserver其实就是一个数据管理中心,只不过记录的都是一些ip等重要数据,具体的消息还是存在broker中的
· 当生产者要往broker中发消息的时候,就会首先问nameserver,应该往哪个broker中发,nameserver则会返回一个broker的IP,此时生产者才可以真正的去发消息
· 消费者也是,消费要要去监听某一个broker,也会去问nameserver,ip是多少,nameserver返回一个ip,此时消费者才能真正的去监听
· broker和消费者之间建立长连接,每当有消息来的时候,broker就会推送给消费者
· 心跳机制,每隔一段时间发送消息给nameserver,让nameserver知道存货与否,不存活的话则踢掉这个ip
· topic和tag的关系,类似于 裤子 和 长裤、短裤的关系
安装
所以说 ,准备工作是,需要 准备
nameserver
broker
生产者
消费者
配置文件
rocketmq.name-server=192.168.222.128:9876
rocketmq.producer.group=group1
这里要替换成自己的 rocketmq 所在机器的 ip 和端口
如果是在自己windows上安装的rocketmq,那基本上就是localhost:9876
如果是在linux上安装的rocketmq,那需要通过 ifconfig 查看自己的ip ,同时关闭linux的防火墙
这里的rocketmq.producer.group=group1 就是说生产者的组名是 group1
生产者
package com.example.controller;
import com.example.controller.domain.User;
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.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("demo")
public class MyController {
@Autowired
private RocketMQTemplate template; //直接拿
/*简单的发送*/
@GetMapping("/send")
public String send() {
/*同步发送————》发送给topic2,内容是syncSend*/
SendResult sendResult = template.syncSend("topic2", "syncSend");
System.out.println(sendResult);
/*异步发送————》需要写回调函数,也就是这个匿名内部类,onSuccess就是发送成功的话xxxxx,onException就是发送失败的话xxxx*/
template.asyncSend("topic2", "asyncSend", new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(sendResult);
}
@Override
public void onException(Throwable throwable) {
System.out.println(throwable);
}
});
/*单向发送*/
template.sendOneWay("topic2", "sendOneWay");
/*批量发送,只能同步发送————》template.syncSend("topic2", stringList, 3000);这里的3000指的是超时时间*/
List<String> stringList = new ArrayList<>();
stringList.add("syncSendBatch1");
stringList.add("syncSendBatch2");
stringList.add("syncSendBatch3");
stringList.add("syncSendBatch4");
template.syncSend("topic2", stringList, 3000);
return "success";
}
/*顺序发送*/
@GetMapping("/sendOrder")
public String sendOrder() {
/*
public class User{
private Integer id;
private String name;
*/
/*现在的场景是,想要id一样的都在一个队列中*/
List<User> users = new ArrayList<>();
users.add(new User(1, "zhangsan1"));
users.add(new User(2, "wangwu1"));
users.add(new User(3, "lisi1"));
users.add(new User(1, "zhangsan2"));
users.add(new User(2, "wangwu2"));
users.add(new User(3, "lisi2"));
users.add(new User(1, "zhangsan3"));
users.add(new User(2, "wangwu3"));
users.add(new User(3, "lisi3"));
users.add(new User(1, "zhangsan4"));
users.add(new User(2, "wangwu4"));
users.add(new User(3, "lisi4"));
/*不是顺序发送*/
for (User user : users) {
template.asyncSend("topic2", user.toString(),
new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("不是顺序发送--"+user.getName()+"的信息被发送到了队列——》"+sendResult.getMessageQueue().getQueueId());
}
@Override
public void onException(Throwable e) {
System.out.println(e);
}
});
}
/*
template.asyncSendOrderly(主题,消息内容,关键字,回调函数);
关键字是去哪个队列的关键,如果关键字一样,那么就代表着这个消息会被发送到同一个队列中去,
原理应该是:用关键字去取 hash 值然后再对队列的数量取模,所以一样的关键字就能够 被送往同一个队列
*/
/*顺序发送*/
for (User user : users) {
template.asyncSendOrderly(
"topic2",
user.toString(),
String.valueOf(user.getId()),//关键字
new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("顺序发送--"+user.getName()+"的信息被发送到了队列——》"+sendResult.getMessageQueue().getQueueId());
}
@Override
public void onException(Throwable e) {
System.out.println(e);
}
});
}
return "success";
}
@RequestMapping("/sendInTran")
public void sendInTran(){
User user=new User(1,"zhangSan");
Message message=MessageBuilder.withPayload(user.toString()).build();
template.sendMessageInTransaction("topic2",message,null);
System.out.println("fa song cheng gong");
}
}
消费者
package com.example.consumer;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
@Component
@RocketMQMessageListener(topic = "topic2",consumerGroup = "group1")//监听topic2的所有tag,消费者组是group1,这里和生产者组是两套体系,不要误认为生产者是group1,消费者这里就必须是group1,他俩不相关
//@RocketMQMessageListener(topic = "topic1",consumerGroup = "group1",
// selectorExpression = "tag1||tag2",
// messageModel = MessageModel.BROADCASTING )
public class Consumer implements RocketMQListener<String> {
@Override
public void onMessage(String s) {
System.out.println("接收到消息--》"+s);
}
}
事务类
package com.example.consumer.listener;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.messaging.Message;
@RocketMQTransactionListener//记得加这个注解,在之前的版本可以设置具体的去监听哪一个topic,但是新的版本中删除了这个设置
public class MyListener implements RocketMQLocalTransactionListener {
/*事务执行*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
/*一般来说就是 把要发送的消息存储到数据库,做一次备份*/
/*就比如一个 insert()
* 这里只做演示*/
int i=100/20;
if (i<2){
/*COMMIT 就是通知 broker可以把这条数据放到队列里面了*/
System.out.println("message是"+message);
System.out.println("RocketMQLocalTransactionState.COMMIT --- 提交");
return RocketMQLocalTransactionState.COMMIT;
}else {
System.out.println("message是"+message);
System.out.println("RocketMQLocalTransactionState.UNKNOWN --- 未知");
return RocketMQLocalTransactionState.UNKNOWN;
/*UNKNOWN 就是不知道对数据库的操作有没有成功 就会执行下面的事务补偿*/
/*ROLLBACK 就是通知 broker 把消息不要放到队列里面了,直接丢弃*/
}
}
/*事务补偿*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
/*那么就是说,生产者会去数据库中查询,刚才的插入操作到底成功没有 也就是
* select 操作 有的话就返回 commit
* 没有的话就是 rollback
* 如果事务补偿里面还是 Unkown的话 就需要 人工干预 了*/
if (true){
System.out.println("事务补偿---message是"+message);
System.out.println("RocketMQLocalTransactionState.COMMIT --- 事务补偿-- 提交");
return RocketMQLocalTransactionState.COMMIT;
}else {
System.out.println("message是"+message);
System.out.println("RocketMQLocalTransactionState.ROLLBACK --- 事务补偿-- 回滚");
return RocketMQLocalTransactionState.ROLLBACK;
}
}
}
这里有几个需要注意的点:
消费者加了一个 Component 注解,意思是在程序启动的时候就随之启动了,因为消费者会一直监听某一个topic,而生产者我们采用调用的模式,调用一次发送一次消息
启动查看结果
1. 简单的发送
结果
接收到消息--》convertAndSend
SendResult [sendStatus=SEND_OK, msgId=7F000001468018B4AAC26924AE060019, offsetMsgId=C0A8DE8000002A9F000000000007AD7A, messageQueue=MessageQueue [topic=topic2, brokerName=localhost.localdomain, queueId=0], queueOffset=15]
接收到消息--》syncSend
SendResult [sendStatus=SEND_OK, msgId=7F000001468018B4AAC26924AE09001A, offsetMsgId=C0A8DE8000002A9F000000000007AE88, messageQueue=MessageQueue [topic=topic2, brokerName=localhost.localdomain, queueId=0], queueOffset=16]
接收到消息--》asyncSend
接收到消息--》sendOneWay
接收到消息--》["syncSendBatch1","syncSendBatch2","syncSendBatch3","syncSendBatch4"]
2.顺序发送
结果——》可以看到zhangsan的信息在不是顺序发送的时候,可能会被放到各个队列,顺序发送的话,zhangsan的信息只会在一个队列中。
不是顺序发送--zhangsan1的信息被发送到了队列——》3
不是顺序发送--wangwu1的信息被发送到了队列——》2
不是顺序发送--lisi1的信息被发送到了队列——》1
不是顺序发送--zhangsan2的信息被发送到了队列——》3
不是顺序发送--wangwu2的信息被发送到了队列——》0
不是顺序发送--lisi2的信息被发送到了队列——》1
不是顺序发送--zhangsan3的信息被发送到了队列——》0
不是顺序发送--wangwu3的信息被发送到了队列——》1
不是顺序发送--lisi3的信息被发送到了队列——》3
不是顺序发送--zhangsan4的信息被发送到了队列——》0
不是顺序发送--wangwu4的信息被发送到了队列——》3
不是顺序发送--lisi4的信息被发送到了队列——》0
顺序发送--zhangsan1的信息被发送到了队列——》1
顺序发送--wangwu1的信息被发送到了队列——》2
顺序发送--lisi1的信息被发送到了队列——》3
顺序发送--zhangsan2的信息被发送到了队列——》1
顺序发送--wangwu2的信息被发送到了队列——》2
顺序发送--lisi2的信息被发送到了队列——》3
顺序发送--zhangsan3的信息被发送到了队列——》1
顺序发送--wangwu3的信息被发送到了队列——》2
顺序发送--lisi3的信息被发送到了队列——》3
顺序发送--zhangsan4的信息被发送到了队列——》1
顺序发送--wangwu4的信息被发送到了队列——》2
顺序发送--lisi4的信息被发送到了队列——》3
接收到消息--》User{id=1, name='zhangsan1'}
接收到消息--》User{id=3, name='lisi3'}
接收到消息--》User{id=2, name='wangwu4'}
接收到消息--》User{id=2, name='wangwu3'}
接收到消息--》User{id=3, name='lisi2'}
接收到消息--》User{id=2, name='wangwu2'}
接收到消息--》User{id=3, name='lisi4'}
接收到消息--》User{id=3, name='lisi2'}
接收到消息--》User{id=2, name='wangwu1'}
接收到消息--》User{id=1, name='zhangsan1'}
接收到消息--》User{id=1, name='zhangsan2'}
接收到消息--》User{id=2, name='wangwu1'}
接收到消息--》User{id=2, name='wangwu2'}
接收到消息--》User{id=1, name='zhangsan3'}
接收到消息--》User{id=1, name='zhangsan2'}
接收到消息--》User{id=1, name='zhangsan4'}
接收到消息--》User{id=3, name='lisi4'}
接收到消息--》User{id=1, name='zhangsan3'}
接收到消息--》User{id=1, name='zhangsan4'}
接收到消息--》User{id=2, name='wangwu3'}
接收到消息--》User{id=3, name='lisi1'}
接收到消息--》User{id=3, name='lisi3'}
接收到消息--》User{id=2, name='wangwu4'}
接收到消息--》User{id=3, name='lisi1'}
3.事务
结果——》事务补偿机制是需要间隔一段时间的,事务执行返回Unknow之后,并不是马上执行事务补偿,而是等一会再去执行事务补偿。
原始的发送和消费模式
依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.8.0</version>
</dependency>
使用这一个就可以
消费者
package com.hyl.producer;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListener;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
public class Consumer1 {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("topic1","*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt messageExt : list) {
System.out.println("messageExt = " + messageExt);
System.out.println("new String(messageExt.getBody()) = " + new String(messageExt.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.printf("consumer start");
}
}
生产者
package com.hyl.producer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import java.time.LocalDateTime;
public class Producer1 {
public static void main(String[] args) throws Exception {
// 谁来发?
// 发给谁?
// 怎么发?
// 发什么?
// 发的结果是什么?
// 收尾
DefaultMQProducer produccer1 = new DefaultMQProducer("group1");
produccer1.setNamesrvAddr("localhost:9876");
produccer1.start();
SendResult sendResult = null;
for (int i = 0; i < 100; i++) {
sendResult = produccer1.send(new Message("topic1", "tag1", (LocalDateTime.now() + "_____helloworld").getBytes()));
}
System.out.println("sendResult = " + sendResult);
produccer1.shutdown();
}
}
如果生产者发送到的地方是 topic1 那么消费者需要监听topic1才行
如果有两个消费者 同属group1 而且都监听topic1 那么生产者发送10个消息,这两个消费者各拿五个
如果生产者生产10个消息,但是消费者属于不同的组,也就是一个属于 group1 一个属于group2,虽然都监听 topic1,但是这两个消费者都会拿到10条数据 , 这也就是 广播模式
那么如果说 两个消费者就是要在一个组里面,并且就是要都拿到10个消息,怎么弄?
可以 consumer.setMessageModel(MessageModel.BROADCASTING); 即可