作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
学习必须往深处挖,挖的越深,基础越扎实!
阶段1、深入多线程
阶段2、深入多线程设计模式
阶段3、深入juc源码解析
码哥源码部分
码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】
码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】
码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】
码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】
打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】
一、何谓有序性
消息有序性,就是说进入消息队列的消息都是有顺序的,消息应该按照FIFO的顺序被消费。这对消费者端的处理逻辑是有要求的,比如消息按照消息A、消息B、消息C的顺序先后进入队列,那么消费者程序的处理逻辑也应该是先执行消息A、再执行消息B、最后执行消息C。
我们在《分布式理论之高性能:读写分离》中介绍过MySQL的复制原理:整个复制过程实际上就是 Slave节点 从Master节点获取binlog日志,然后再顺序执行日志中所记录的各种操作。如果Slave中的SQL线程在消费数据时不保证顺序,那原来的[增加、修改、删除]可能就变成了[删除、修改、增加],就会全部乱套。
本章,我们依旧以RabbitMQ和Kafka为例,看下他俩是如何保证消息的有序性的。
二、RabbitMQ
在RabbitMQ中,当多个消费者对同一个queue进行消费时,是不能保证消息的有序执行的。比如消息按照消息A、消息B、消息C的顺序先后进入一个队列,但是当有多个消费者同时消费时,不能保证消费也按照A、B、C的顺序被依次执行。
所以RabbitMQ的解决方案就是为每一个消费者指定一个专门的队列,如下图:
上图中,只要消息按照A、B、C的顺序入队,那每个消费者获取并执行消息的顺序也一定是A、B、C。
注意,由于可能存在网络延迟的因素,生产者需要确保按顺序投递成功。
三、Kafka
首先, Kafka默认会保证同一个partition内的消息都是有序的 ,所以我们只要能够让生产者在发送消息时将需要保证顺序的几条消息都发送到同一个分区,那么消费者消费时,消息就是有序的。
生产者在发送消息时,会通过以下方式之一确定消息所属的partition:
- 显式指定partition
- 不指定partition,指定key:根据key的hash值与分区数进行运算,确定发送到哪个partition分区
- 不指定partition,不指定key:轮询各分区发送
所以,我们只要采用 指定分区 或 指定key 的方式就可保证消息的有序性,如下图:
这种情况下,如果消费者内部是单线程去依次执行每个消息,那没有问题;如果是多线程执行,就需要考虑consumer内的消费有序性,一般可以利用内存队列来解决。
消费者内部多线程情况下保证消息有序的方案如下:
我们一般需要先进行压测,看下如果消费在单一线程下处理消息的吞吐量,如果一秒钟只能处理几十个消息,那实在是太低了,得考虑多线程方案。
四、总结
本章,我们介绍如何保证消息队列的消息有序性,根本思路就是两点:
- 保证队列内的消息FIFO;
- 保证一个消费者对应单独的一个队列;
在实际业务中,需要消息有序性的场景其实并不多。