rocketmq学习笔记
mq理解
普通消息发送场景:
请求方------>响应方(容错性低,可能因为网络问题导致两个服务之间通讯出现发送不了或接受不了的问题)
mq消息发送场景:
请求方(生产者)---->MQ----->响应方(消费者)(mq就是消息队列,是在信息传输过程中保存消息的容器,多用于分布式系统之间进行通信,具有先进先出的特点)
为什么使用mq
优势
- 应用解耦(耦合越高,容错性越低,可维护性越低):消费方是否存活不影响生产者,解耦的目的就是生产方发完消息,就可以继续下一步业务逻辑。
例子:目前有一个订单系统,用户下单之后,库存系统,支付系统,物流系统都需要操作,此时用到mq之后,订单系统向mq发完消息就可以进行其他操作了,对应的消息由mq进行分发,库存系统,支付系统,物流系统解耦,各自收到各自的消息进行响应操作,互不影响。 - 异步提速:生产者发完消息就可以进行下一步业务逻辑。
- 削峰填谷:相当于一个暂存区,将十万个请求分十秒发送给只能并发处理一万个请求的消费者,将瞬时大量请求分散。
劣势
-
系统可用性降低:系统引入了更多外部依赖,稳定性变差,一旦MQ宕机,将会对业务造成影响(如何保证MQ的高可用)
-
系统复杂度提高:可能出现重复消费,消息丢失,消息传递顺序的问题
重复消费:
消息丢失:
消息传递顺序: -
一致性问题:订单系统处理完业务,通过MQ向物流,库存,支付系统发送消息数据,如果其中有处理失败的,便会造成消息数据处理的一致性(因为订单系统已经接着做其他业务了,已经告诉客户端购买成功了)
如何保证消息数据处理的一致性
常见MQ中间件
ActiveMQ:
java语言实现的,万级数据吞吐量,处理速度毫秒级,主从架构,成熟度高(Apache产品,最早)
RabbitMQ:
erlang语言实现(比较底层的语言),万级数据吞吐量,处理速度非常快,主从架构。
RocketMQ:
(火箭)十万级数据吞吐量,处理速度毫秒级,分布式架构,功能强大,扩展性强
kafka:
scala语言实现,十万级吞吐量,处理速度毫秒级,分布式架构,功能较少,多用于大数据较多
注意:集群与分布式的区别:集群是将一个服务部署到多个服务器上,分布式是将不同的服务分别部署到不同的服务器,都完成不同的业务。
rocketmq(需要被当消息传递的实体类需要实现序列化接口)
基础概念(rocketmq运行原理)
生产者(集群):发送消息,broker返回接收结果。
broker(集群):推送消息到消费者(监听模式)
消费者(集群):拉取消息,broker返回消息(不建议使用)
nameserver(命名服务器集群):存的是broker的IPs,前面三者只要已启动就会将自己注册到nameserver,然后nameserver进行管理,namesrv通过一种心跳检测机制检测各个服务是否正常(和nacos心跳检测机制相似)
大白话:生产者问nameserver,那么多broker集群,我应该发送到哪个,nameserver回复后,生产者在拿着broker的IP向broker发消息,消费者是先问nameserver我因该监听哪个集群的队列,然后在拿着IP去监听是否有消息;
生产者发送消息的组成
消息:Message
主题:Topic(消息中的一级分类)
标题:Tag(消息中的二级分类)
安装
消息发送
单生产者对应单消费者(基础发送与基础接收)
引入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
简单生产者书写
//发送消息
public class Producer {
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
/**
* 谁来发
* 发给谁
* 发什么
* 发的结果是什么
* 打扫战场
*/
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("localhost:9876");
producer.start();
Message message = new Message();
message.setTopic("topic1");
message.setTags("tag1");
String msg = "hello world";
message.setBody(msg.getBytes());
SendResult send = producer.send(message);
System.out.println(send);
producer.shutdown();
}
简单消费者书写
//消费者
public class Consumer {
public static void main(String[] args) throws MQClientException {
//谁来收
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 message : list) {
System.out.println(message);
byte[] body = message.getBody();
System.out.println(new String(body));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("消费者已启动");
//消费者不能关,因为消费者建立了一个长连接监听消息
}
}
多消费者模式(单生产者对应多消费者,负载均衡模式与广播模式)
生产者
//发送消息
public class Producer {
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
/**
* 谁来发
* 发给谁
* 发什么
* 发的结果是什么
* 打扫战场
*/
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("localhost:9876");
producer.start();
for (int i = 0; i < 10; i++) {
String msg = "hello world"+i;
Message message = new Message();
message.setTopic("topic2");
message.setTags("tag1");
message.setBody(msg.getBytes());
SendResult send = producer.send(message);
System.out.println(send);
}
producer.shutdown();
}
}
消费者
//消费者
public class Consumer {
public static void main(String[] args) throws MQClientException {
//谁来收
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
//从哪里收消息
consumer.setNamesrvAddr("localhost:9876");
//监听哪个消息队列
consumer.subscribe("topic2","*");
//处理业务流程,注册一个监听器
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//写业务逻辑
for (MessageExt message : list) {
System.out.println(message);
byte[] body = message.getBody();
System.out.println(new String(body));
System.out.println("===============================");
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("消费者已启动");
//消费者不能管,因为消费者建立了一个长连接监听消息
}
}
消费者1
消费者2
通常情况下是十条消息分别各被两个消费者接收到5条(负载均衡)
消息的消费模式
广播和负载均衡,这里默认是负载均衡
//设置消息的消费模式
consumer.setMessageModel(MessageModel.CLUSTERING);//将消费模式设置为广播,这样同一个group当中的consumer也能收到所有的消息
正常情况下,同一个topic中不同组可以收到所有的消息,同一个topic,同一个group会负载均衡,将消费模式设置为广播模式后不分组里面的每个消费者也能收到所有消息。
多生产者模式
消息类型
同步消息(即时性较强,重要的消息,且必须有回执的消息,例如短信,通知)
上面的例子就是同步消息类型,只有消息发送成功了之后才会返回SendResult.
异步消息(即时性较弱,但需要有回执的消息,例如订单中的某些消息)
//发送消息
public class Producer {
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
/**
* 谁来发
* 发给谁
* 发什么
* 发的结果是什么
* 打扫战场
*/
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("localhost:9876");
producer.start();
//同步消息
// for (int i = 0; i < 10; i++) {
//
// String msg = "hello world22222"+i;
// Message message = new Message();
// message.setTopic("topic3");
// message.setTags("tag1");
// message.setBody(msg.getBytes());
// SendResult send = producer.send(message);
// System.out.println(send);
// }
//异步消息
for (int i = 0; i < 10; i++) {
String msg = "hello rocketmq yibu"+i;
Message message = new Message("topic6", "tag1", msg.getBytes());
producer.send(message, new SendCallback() {
//发送成功的回调方法
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(sendResult);
}
//发送失败的回调方法
@Override
public void onException(Throwable e) {
System.out.println(e);
}
});
System.out.println("异步发送完成");
}
//异步发送时不能关闭生产者
// producer.shutdown();
}
}
单向消息(不需要有回执消息,例如日志类消息)
//发送消息
public class Producer {
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
/**
* 谁来发
* 发给谁
* 发什么
* 发的结果是什么
* 打扫战场
*/
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("localhost:9876");
producer.start();
//同步消息
// for (int i = 0; i < 10; i++) {
//
// String msg = "hello world22222"+i;
// Message message = new Message();
// message.setTopic("topic3");
// message.setTags("tag1");
// message.setBody(msg.getBytes());
// SendResult send = producer.send(message);
// System.out.println(send);
// }
// //异步消息
// for (int i = 0; i < 10; i++) {
// String msg = "hello rocketmq yibu"+i;
// Message message = new Message("topic6", "tag1", msg.getBytes());
//
// producer.send(message, new SendCallback() {
// //发送成功的回调方法
// @Override
// public void onSuccess(SendResult sendResult) {
// System.out.println(sendResult);
// }
//
// //发送失败的回调方法
// @Override
// public void onException(Throwable e) {
// System.out.println(e);
// }
// });
// System.out.println("异步发送完成");
// }
//单向消息
for (int i = 0; i < 10; i++) {
String msg = "单向消息" + i;
Message message = new Message("topic7", "tag1", msg.getBytes());
producer.sendOneway(message);
System.out.println("单向发送完成");
}
//异步发送时不能关闭生产者
// producer.shutdown();
}
}
延时消息(消息发送时不能直接发送到broker,而是根据设定的等待时间到达,起到延时到达的缓冲作用)
//延时消息,能设置判断条件分别延时不同的消息
for (int i = 0; i < 10; i++) {
String msg = "延时消息" + i;
Message message = new Message("topic7", "tag1", msg.getBytes());
message.setDelayTimeLevel(5);//延时等级
producer.sendOneway(message);
System.out.println("延时消息发送完成");
}
批量消息(一次发送多条消息,节约网络开销)
//发送消息
public class ProducerBatch {
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
/**
* 谁来发
* 发给谁
* 发什么
* 发的结果是什么
* 打扫战场
*/
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("localhost:9876");
producer.start();
ArrayList<Message> list = new ArrayList<>();
String msg1 = "批量消息1";
Message message1 = new Message("topic7","tag1",msg1.getBytes());
String msg2 = "批量消息2";
Message message2 = new Message("topic7","tag1",msg2.getBytes());
String msg3 = "批量消息3";
Message message3 = new Message("topic7","tag1",msg3.getBytes());
list.add(message1);
list.add(message2);
list.add(message3);
SendResult send = producer.send(list);
System.out.println(send);
//异步发送时不能关闭生产者
// producer.shutdown();
}
}
消息过滤
通过tag过滤
consumer.subscribe("TagFilterTest", "TagA || TagC");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
通过sql方式过滤(rocketmq默认为开启,要使用需要先配置)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_1");
// 只有订阅的消息有这个属性a, a >=0 and a <= 3
consumer.subscribe("TopicTest", MessageSelector.bySql("a between 0 and 3");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
springboot整合rocketmq
- 引入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
- 添加配置
rocketmq:
name-server: localhost:9876
producer:
group: group1
- 依赖注入(@Autowired)
@Autowired
private RocketmqTemplate rocketmq;
生产者:
public class Controller {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/")
public String send(){
User user = new User("zhangsan",18);
rocketMQTemplate.convertAndSend("topic10",user);
//同步消息
SendResult topic10 = rocketMQTemplate.syncSend("topic10", user);
//异步消息
rocketMQTemplate.asyncSend("topic10", user, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(sendResult);
}
@Override
public void onException(Throwable throwable) {
System.out.println(throwable);
}
},1000);
//单向消息
rocketMQTemplate.sendOneWay("topic10",user);
//延时消息
rocketMQTemplate.syncSend("topic", MessageBuilder.createMessage(user).build(),2000,3);
//批量消息
ArrayList<User> list = new ArrayList<>();
list.add(user)
rocketMQTemplate.syncSend("topic10",list,2000);
return "success";
}
消费者(需要实现对应接口,通过@RocketMQMessageListener注解可指定topic,tag,group)
@Service
@RocketMQMessageListener(topic = "topic10",consumerGroup = "group1",
selectorType = SelectorType.SQL92,selectorExpression = "age>12",
messageModel = MessageModel.BROADCASTING//广播模式
)
public class Consumer implements RocketMQListener<User> {
@Override
public void onMessage(User user) {
System.out.println(user);
}
}
消息的特殊处理
消息顺序(目的:队列内有序,队列外无序)
事务消息
- 正常事务过程:生产者集群发送半消息到消息服务器集群(broker),broker返回状态ok,然后执行本地事务,事务的成功与否
(提交和回滚)影响消息完整发送还是broker删除半消息;
返回状态:
- 事务补偿过程:broker响应生产者之后生产者集群未给出响应,此时broker就会主动询问生产者,生产者检测本地事务状态,根据事务状态提交或回滚然后broker执行操作;
//发送消息
public class Producer {
public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
/**
* 谁来发
* 发给谁
* 发什么
* 发的结果是什么
* 打扫战场
*/
TransactionMQProducer producer = new TransactionMQProducer("group1");
producer.setNamesrvAddr("localhost:9876");
//设置事务监听
producer.setTransactionListener(new TransactionListener() {
//正常事务过程
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
System.out.println("事务正常");
// return LocalTransactionState.COMMIT_MESSAGE;
return LocalTransactionState.UNKNOW;
}
//事务补偿过程
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
System.out.println("事务补偿过程");
// return LocalTransactionState.UNKNOW;
return LocalTransactionState.COMMIT_MESSAGE;
}
});
producer.start();
String msg = "事务消息";
Message message = new Message("topic13","tag1",msg.getBytes());
SendResult send = producer.sendMessageInTransaction(message,null);
System.out.println(send);
// producer.shutdown();
}
}
- 事务消息的状态:
- 提交状态:此消息正常发送
- 回滚状态:相当于未提交此消息
- 中间状态:完成了半消息的发送,未对broker进行二次状态确认
注意:事务消息仅与生产者有关,与消费者无关。
RocketMQ的高级特性
消息的存储
- 消息生产者发送消息到MQ
- MQ收到消息,将消息进行持久化,存储该消息
- MQ返回ACK给生产者
- MQpush消息给对应的消费者(监听器)
- 消费者返回ACK给MQ
- MQ删除消息