1、为什么用mq

优势

主要有3个:

应用解耦(降低微服务之间的关联)、

异步提速(微服务拿到mq消息后同时工作)、

削峰填谷(可以消息堆积)

劣势

系统可用性降低(MQ一旦宕机整个系统不可用)

复杂度提高(需要解决系统消息一致性、重复消费...)

一致性问题(不同系统拿到mq中的消息后,部分系统处理失败怎么办)

2、rocketmq集群工作流程

由上图可以看出,rocketMQ集群=消息服务器集群+命名服务器集群,其中消息服务器集群=生产者集群+broker集群+消费者集群。

命名服务器集群(nameserver cluster)

● 命名服务器集群是管理生产者、broker、消费者的纽带,哪个生产者/broker/消费者可用都是通过命名服务器得知其信息,所以生产者/broker/消费者都需要定时发送心跳给命名服务器

● 命名服务器与生产者的关系:命名服务器记录有许多broker的ip地址,每个生产者发送消息到broker前都需要先去命名服务器获取某个broker的ip,然后再发送消息到broker

● 命名服务器和消息者的关系:命名服务器记录有许多broker的ip地址,消费者想监听broker中的消息,需要先去命名服务器获取某个broker的ip,然后再监听broker中的消息

生产者集群(producer cluster)

● 每个生产者部署在不同的IP上形成了集群

● 生产者的消息=topic+tag,topic用来区分消息类型,一种topic类型的消息可以分布在多个不同的broker中,同类型的消息就用tag区分,如我们系统里的佣金宝的topic是"topic-yjb",然后佣金宝下面可以划分多个tag

消费者集群(consumer cluster)

● 每个消费者部署在不同的IP上形成了集群

● 消费者获取某个broker中的消息理论上有两种方法:

○ pull拉取模式:消费者开启线程定时访问broker,如有消息存在则拉取,缺点是太消耗消费者的资源了,不管有没有消息都会去访问broker

○ push推送模式:消费者起一个监听器监听broker(与broker建立一个长链接),若broker中有消息,则broker会自动推送消息给消费者,一般用这种。其中push模式的底层也是通过消费者主动拉取的方式来实现的,只不过它的名字叫push而已,意思是Broker尽可能实时的推送消息给消费者,和pull模式相比,push模式都帮我们封装了底层,而pull模式就要自己写代码去手动拉取消息,所以pull模式更像拉取,而封装好的push更像是推送。

3、消息类型

同步/异步/单向消息

同步:发送消息是按顺序发送

异步:发送消息是异步的,生产者发送完消息就干其他事情,消费者稍后会在生产者的回调函数中返回消费结果【new SendCallback(){} 】,及时性差

单项消息:生产者只管发出去,并不接收返回值

批量消息

特点:

● 同一批消息的topic应该相同;

● 消息内容大小=(topic+body+其他key/value属性+日志固定20字节)<4M

● 不是延时消息

tag过滤消息

消费者指定特定的tag,则只接收该tag的消息

sql过滤消息

消费者支持类似于sql查询语法那样的消息过滤

//生产者
String msg="hello,小明同学";
Message message=new Message("topic",msg.getBytes("UTF-8"));
message.putUserProperty("name","xiaoming");
message.putUserProperty("age","27");
SendResult sendResult1 =defaultMQProducer.send(message); //消费者用sql语法过滤出age>26岁的消息,即只接受age>26岁的消息
DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer("group1");
defaultMQPushConsumer.subscribe("topic", MessageSelector.bySql("age>26"));

4、消息的特殊处理

顺序消息

场景:在某些业务系统中,一些业务流程处理的顺序必须是按顺序的,比如客户下单:创建订单 -> 付款 -> 推送消息 -> 订单完成,在并发环境下,不可能只有一个客户下单,当多个客户下单时,他们的这四种消息有可能是混乱的。

解决方案:rocketmq默认每个topic在broker中都会有四个队列存放该类数据,队列是FIFO性质的,我们可以利用队列去按序存放这些消息以达到按序消费的目的。

生产者主要代码:

DefaultMQProducer defaultMQProducer=new DefaultMQProducer("group1");
defaultMQProducer.setNamesrvAddr("127.0.0.1:9876");
try {
defaultMQProducer.start();
List<Order> list=new ArrayList<>();
//模拟业务流程乱序提交:单个订单消息有序,多个订单间消息无序
Order order01=new Order(0,"创建订单");
Order order11=new Order(1,"创建订单");
Order order02=new Order(0,"付款"); Order order03=new Order(0,"推送");
Order order21=new Order(2,"创建订单");
Order order12=new Order(1,"付款"); Order order04=new Order(0,"完成");
Order order13=new Order(1,"推送");
Order order22=new Order(2,"付款"); Order order14=new Order(1,"完成");
Order order23=new Order(2,"推送");
Order order24=new Order(2,"完成"); list.addAll(new ArrayList<Order>(Arrays.asList(order01,order11,order02,order03,order21,order12,order13,order22,order23,order04,order14,order24)));
for(Order order:list){
Message message=new Message("topic-order",order.toString().getBytes());
/*
*每个topic默认创建4个队列,defaultMQProducer可以通过MessageQueueSelector的select方法设置
*当前Message发送到"topic-order"的哪个队列:通过订单的唯一属性值,如orderId,对topic中的queue队列数取模,
*这样同一个订单的不同消息就会被按序放进同一个queue中
*/
SendResult sendResult=defaultMQProducer.send(message, new MessageQueueSelector() { @Override
public MessageQueue select(List<MessageQueue> queueList, Message msg, Object o) {
System.out.println("队列数:"+queueList.size());
//获取队列下标
int size=order.getOrderId()%queueList.size();
//计算该message放在"topic-order"哪个队列中
MessageQueue mq=queueList.get(size);
return mq;
}
},null);
System.out.println(sendResult);
} } catch (Exception e) {
e.printStackTrace();
}

消费者主要代码:

//使用MessageListenerConcurrently则多个线程服务一个队列,而MessageListenerOrderly是一个线程服务一个队列(topic默认四个队列就是四个线程)
defaultMQPushConsumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
System.out.println("线程:"+Thread.currentThread().getName()+",队列:"+consumeOrderlyContext.getMessageQueue().getQueueId()+",该队列消息数量:"+list.size());
for(MessageExt messageExt:list){
System.out.println(new String(messageExt.getBody())); }
return ConsumeOrderlyStatus.SUCCESS;
}
});

执行结果:

可以看出一个线程服务一个队列,将同类业务的消息都推送到同一个队列中,是可以实现消息的顺序发送的.

事务消息

1. 为什么要用事务消息?

还是以用户下单为例,用户在producer中创建订单(但未提交事务到mysql),然后把下单消息发送给broker(即MQ服务器),MQ服务器再把该消息发给所有订阅了该类topic的消费者,可能出现如下情况:

(1)producer成功进行了数据库操作(即提交事务到mysql),且MQ服务器接收消息成功,然后被消费者消费 -->皆大欢喜

(2)producer成功进行了数据库操作(即提交事务到mysql),但发到MQ服务器失败,进而消费者不能消费该类消息 -->不正常

(3)producer进行数据库操作的时候发生了意外导致数据库操作失败(即提交事务到mysql),但发到MQ服务器成功,进而消费者会去消费该类消息 -->不正常

上面第2、3种情况都是不正常的,解决办法就是引入事务消息,事务消息的过程如下:

第一阶段producer先发送一条"half"型消息到MQ服务器,MQ服务器收到后随即返回一个发送成功标识 ->

producer进行数据库操作执行事务,执行成功则发送二次确认(Commit或Rollback)消息给服务器 ->

MQ服务器收到Commit则将第一阶段的"half"型消息标记为可投递,消费者若订阅了该topic则能收到该消息;MQ服务器收到Rollback则删除第一阶段的消息,消费者将接收不到该消息,就当什么事也没发生过 ->

MQ服务器会有一个事务补偿机制:若服务器很久都没有收到producer返回的二次确认commit/rollback,则会主动去调用producer的接口进行回查,然后producer再去数据库中查看事务是否执行成功 ,如成功/失败,则发送commit/rollback给MQ服务器,然后后面的操作同上一步

2. 事务消息和正常消息的区别

(1)事务消息有三种状态:commit状态、回滚状态、中间状态(producer发送了half型消息但未发送commit给到服务器,即未对);commit状态的消息等价于正常消息(可以被消费者感知),但后两种状态的消息对于消费者是不可见的

(2)事务消息仅与生产者有关,仅当事务消息处于commit状态时与正常消息一样可以被消费者感知

3. 代码实现事务消息

生产者主要代码:

//事务消息使用的生产者是TransactionMQProducer
TransactionMQProducer transactionMQProducer=new TransactionMQProducer("group1");
transactionMQProducer.setNamesrvAddr("127.0.0.1:9876");
try {
//添加事务监听
transactionMQProducer.setTransactionListener(new TransactionListener(){ //事务消息过程中包括正常事务(数据库操作)、事务补偿,正常事务在该方法执行
@Override
@Transactional
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
try{ //模拟数据库操作事务正常提交:insert delete...
Long orderId= Long.valueOf(new String(message.getBody()));
insert(orderId);
System.out.println("本地数据库事务提交成功!");
//本地执行完数据库操作后(正常事务),事务消息有三种状态:COMMIT_MESSAGE/ROLLBACK_MESSAGE/UNKNOW
return LocalTransactionState.COMMIT_MESSAGE; }catch(Exception ex){ //模拟数据库操作事务提交失败:insert delete... System.out.println("本地数据库事务提交失败,事务消息rollback:"+ex);
//本地执行完数据库操作后(正常事务),事务消息有三种状态:COMMIT_MESSAGE/ROLLBACK_MESSAGE/UNKNOW
return LocalTransactionState.ROLLBACK_MESSAGE; } //业务成功后,也可以直接返回UNKNOW,避免极端的情况下数据库并没有保存成功
//return LocalTransactionState.UNKNOW; } //事务补偿在该方法执行:若executeLocalTransaction返回UNKNOW或长时间没有返回消息给服务器
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) { System.out.println("执行事务补偿...");
Long orderId= Long.valueOf(new String(messageExt.getBody()));
if(null!=selectOne(orderId)){
return LocalTransactionState.COMMIT_MESSAGE;
}else{
//若事务不成功可以返回UNKNOW而不是ROLLBACK_MESSAGE,因为服务器默认回查15次后都是UNKNOW,则会自动回滚haLf型消息
return LocalTransactionState.UNKNOW;
} }
}); transactionMQProducer.start();
Order order=new Order(0,"创建订单");
Message message=new Message("topic-transaction",String.valueOf(order.getOrderId()).getBytes());
SendResult sendResult=transactionMQProducer.sendMessageInTransaction(message,null);
System.out.println("sendResult:"+sendResult);

由事务消息的整个流程可知,先执行executeLocalTransaction,再打印发送结果:

如果executeLocalTransaction返回UNKNOW,则服务器不断尝试事务补偿:

消费者主要代码:

DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer("group1");
defaultMQPushConsumer.setNamesrvAddr("127.0.0.1:9876");
defaultMQPushConsumer.subscribe("topic-transaction", "*");
defaultMQPushConsumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
list.forEach(msg->{
System.out.println("收到消息:"+new String(msg.getBody()));
});
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
defaultMQPushConsumer.start();

执行结果如下:

rocketmq消息及流程的更多相关文章

  1. RocketMQ之九:RocketMQ消息发送流程解读

    在讨论这个问题之前,我们先看一下Client的整体架构. Producer与Consumer类体系 从下图可以看出以下几点:(1)Producer与Consumer的共同逻辑,封装在MQClientI ...

  2. RocketMQ消息发送流程和高可用设计

    (源码阅读先看主线 再看支线 先点到为止 后面再详细分解) 高可用的设计就是:当producer发送消息到broker上,broker却宕机,那下一次发送如何避免发送到这个broker上,就是采用La ...

  3. RocketMQ源码 — 八、 RocketMQ消息重试

    RocketMQ的消息重试包含了producer发送消息的重试和consumer消息消费的重试. producer发送消息重试 producer在发送消息的时候如果发送失败了,RocketMQ会自动重 ...

  4. 源码分析RocketMQ消息轨迹

    目录 1.发送消息轨迹流程 1.1 DefaultMQProducer构造函数 1.2 SendMessageTraceHookImpl钩子函数 1.3 TraceDispatcher实现原理 2. ...

  5. RocketMQ之十:RocketMQ消息接收源码

    1. 简介 1.1.接收消息 RebalanceService:均衡消息队列服务,负责通过MQClientInstance分配当前 Consumer 可消费的消息队列( MessageQueue ). ...

  6. RocketMQ(消息重发、重复消费、事务、消息模式)

    分布式开放消息系统(RocketMQ)的原理与实践 RocketMQ基础:https://github.com/apache/rocketmq/tree/rocketmq-all-4.5.1/docs ...

  7. 源码分析 Kafka 消息发送流程(文末附流程图)

    温馨提示:本文基于 Kafka 2.2.1 版本.本文主要是以源码的手段一步一步探究消息发送流程,如果对源码不感兴趣,可以直接跳到文末查看消息发送流程图与消息发送本地缓存存储结构. 从上文 初识 Ka ...

  8. 源码分析 Kafka 消息发送流程

    Futuresend(ProducerRecord<K, V> record) Futuresend(ProducerRecord<K, V> record, Callback ...

  9. RocketMQ消息丢失解决方案:同步刷盘+手动提交

    前言 之前我们一起了解了使用RocketMQ事务消息解决生产者发送消息时消息丢失的问题,但使用了事务消息后消息就一定不会丢失了吗,肯定是不能保证的. 因为虽然我们解决了生产者发送消息时候的消息丢失问题 ...

随机推荐

  1. Spring Boot 中的监视器是什么?

    Spring boot actuator 是 spring 启动框架中的重要功能之一.Spring boot 监视器可帮助您访问生产环境中正在运行的应用程序的当前状态.有几个指标必须在生产环境中进行检 ...

  2. Oracle的数据优化(经常被问到)?

    以Oracle数据库举例:(a-G要求掌握,H一般为DBA操作,了解就可以了) a. 建库:已知将保存海量数据的时候,因为Oracle是通过用户来管理数据的, 第一步我们先建一个tableaspace ...

  3. mac phpStrom 卸载

    cd ~/Library/Logs/cd ~/Library/Application\ Supportcd ~/Library/Preferences/cd ~/Library/Caches/

  4. Java 中 interrupted 和 isInterrupted 方法的区别?

    interrupt interrupt 方法用于中断线程.调用该方法的线程的状态为将被置为"中断"状态. 注意:线程中断仅仅是置线程的中断状态位,不会停止线程.需要用户自己去监 视 ...

  5. 五、关于mycat踩过的坑

    1.ER分表的从表无法批量插入,例如:insert into tab_a(c1,c2) values(v1,v2),(v11,v21)或者使用jdbctemplate进行batchUpdate操作会报 ...

  6. 有哪些类型的通知(Advice)?

    Before - 这些类型的 Advice 在 joinpoint 方法之前执行,并使用 @Before 注解标记进行配置. After Returning - 这些类型的 Advice 在连接点方法 ...

  7. java-第三方工具去做一些校验

    推荐大家使用第三方 jar 的工具类去做判空.比如:从 Map 中取一个 key 的值,可以用 MapUtils 这个类:对字符串判空使用 StringUtils 这个类:对集合进行判空使用 Coll ...

  8. Eureka server

    Eureka server使用的不是spring mvc的框架,而是使用Jersey. Eureka server ,启动的流程,追本溯源,是在 DiscoveryClient里面,使用这个构造方法 ...

  9. 摩拜单车微信小程序开发技术总结

    前言 摩拜单车小程序已于微信小程序上线第一天正式发布,刷爆微博媒体朋友圈.本文主要讲讲技术方向的总结,在段时间的开发周期内内如何一步步从学习到进阶. 思维转变 微信小程序没有HTML的常用标签,而是类 ...

  10. java中接口interface可以持有多个类的共享常量

    3.接口持有多个类的共享常量  接口另一主要功能,马克-to-win: 可以使用接口来引入多个类的共享常量.所有的这些变量名都将作为常量看待.所有定义在接口中的常量都默认为public.static和 ...