RocketMQ
顺序性与重复性
消息系统绕不开的两个问题:如何保证消息的顺序?如何保证消息不重复?
对于第一个问题,RocketMQ的策略是,你可以自己实现selector方法,保证同一个id的消息放到同一个队列中。
对于第二个问题,答案是RocketMQ不保证消息不重复。
需要消费端自己保证幂等。可以使用去重表,消息重复的问题应当是比较少的。
事务消息
RocketMQ除了支持普通消息,顺序消息,另外还支持事务消息。
如果一个大事务中涉及的数据分散到多台机器上,那么执行这个事务的时间将会成倍增加,因为会增加多次网络传输时间。
解决思路是 大事务拆成小事务 + 异步消息。尽可能的减少网络传输次数。
那么如何保证一个小事务成功了,另一个事务也一定成功?
RocketMQ的做法是:
- 开始事务A前,先发送Prepared消息。此时会拿到消息的地址。
- 开始执行事务A。
- 事务A完成,根据阶段1拿到的消息地址,修改消息状态。
有一个问题,如果阶段3失败怎么办?RocketMQ会定时扫描集群中的事务消息,如果发现Prepared消息,回想生产中确认。生产者需要配置策略,是回滚还是确认发送。
另一个问题,事务A成功了,事务B一直消费失败怎么办?RocketMQ的策略是不断重试。还是失败?人工介入解决。RocketMQ不会回滚臻哥大的流程,因为如果要实现这个功能,整个RocketMQ复杂度会大大提成,且很容易出bug,RocketMQ认为没有必要为了解决发生几率很小的问题,而花费巨大精力。
发送方负载均衡
Producer轮询某topic下的所有队列的方式来实现发送方的负载均衡
消息存储
RocketMQ的消息存储是由consume queue和commit log配合完成的。
consumerQueue中只存储很少的数据,消息主体都是通过CommitLog来进行读写。
consume queue 是逻辑队列。用来指定消息在物理文件commit log上的位置。
commit log 是物理文件。
每个topic下的每个queue都有一个对应的consume queue文件。
consume queue的组织结构:
${rocketmq.home}/store/consumequeue/${topicName}/${queueId}/${fileName}
${rocketmq.home}/store/consumequeue/${topicName}/${queueId}/
这是一个文件
下面的${fileName}
指向commit log。
除了正常的消息队列,还有重试队列和死信队列,都是按照分组groupName标记的。
在consume queue中存储的数据,包含三个信息:在commitLog中的偏移量,消息大小,tag标。
commitlog的存储位置
${user.home} \store\${commitlog}\${fileName}
消息订阅
RocketMQ消息订阅有两种模式,一种是Push模式,即MQServer主动向消费端推送;另外一种是Pull模式,即消费端在需要时,主动到MQServer拉取。但在具体实现时,Push和Pull模式都是采用消费端主动拉取的方式。
消费端负载均衡,消费端会通过RebalanceService线程,10秒钟做一次基于topic下的所有队列负载。
消费端的Push模式是通过长轮询的模式来实现的。
在rocketmq中,push方式的获取消息方式其实是基于对pull方式的封装。为什么这么做的主要原因是要降低broker的负载,因为传统的push方式的实现是broker主动将消息发给client,这样就给broker造成压力。而使用pull封装过的push方式,其实真正发出请求的仍是client端,只是巧妙的给用户造成一种push的假象。
RocketMq
长轮询
RocketMQ使用长轮询Pull方式,可保证消息非常实时,消息实时性不低于Push。
关于轮询
长轮询和短轮询本质上都是轮询。轮询表示客户端每经过固定的时间间隔就像服务器请求数据。当我们在讨论轮询的时候,往往是在讨论“请求应答”场景下的“数据请求”的问题。从”推拉”模型来看轮询的话,轮询这种方式显然属于拉模型(基于请求响应的方式)。长轮询和短轮询最主要的区别就是作为请求的响应方在没有数据的时候,是如何响应数据请求方的。以http为例来说明下短轮询和长轮询。但是注意,这种概念是通用的,并不是说只能用于http上。
短轮询:客户端每隔一段时间就向服务端发送一个请求,如果服务端没有数据,就返回没有数据,然后关闭请求。
长轮询:客户端发起请求,和服务端打开一条连接后保持,当服务器端有数据时,将数据push给客户端,然后关闭连接。
但是这整个过程还是属于pull方式。
长轮询重点注意以下几点即可:
长轮询必须使用长连接
长轮询是一种服务器推数据的技术
长轮询实现服务器推数据的方法是hold住一个请求(当然你可以设置超时时间),直到有数据了给客户端响应
至少投递一次
每个消息至少会被投递一次。
Consumer先pull消息到本地,消费消息后返回ack,如果没有被消费,一定不会ack响应,所以支持此特性。
仅被消费一次
要实现此特性,就要保证:1,发送消息阶段确保只发送一次。2,消费消息时确保不能重复消费。
在分布式环境下,要保证这两点,会带来巨大的开销。为了追求高性能,rocketMq不保证该特性。
由消费者自己保证幂等。
其实,正常情况下,极少会有重复消费的情况发生。
buffer
RocketMQ 没有内存 Buffer 概念,RocketMQ 的队列都是持久化磁盘,数据定期清除。
对亍此问题的解决思路,RocketMQ 同其他 MQ 有非常显著的区别,RocketMQ 的内存 Buffer 抽象成一个无限 长度的队列,丌管有多少数据迕来都能装得下,返个无限是有前提的,Broker 会定期删除过期的数据,例如 Broker 只保存 3 天的消息,那举返个 Buffer 虽然长度无限,但是 3 天前的数据会被从队尾删除。
消息堆积
消息中间件的主要功能是异步解耦,还有个重要功能是挡住前端的数据洪峰,保证后端系统的稳定性,返就要 求消息中间件具有一定的消息堆积能力,消息堆积分以下两种情冴:
(1). 消息堆积在内存 Buffer,一旦超过内存 Buffer,可以根据一定的丢弃策略来丢弃消息,如 CORBA Notification 规范中描述。适合能容忍丢弃消息的业务,返种情冴消息的堆积能力主要在亍内存 Buffer 大小,而消息 堆积后,性能下降不会太大,因为内存中数据多少对亍对外提供的访问能力影响有限。
(2). 消息堆积到持久化存储系统中,例如DB,KV存储,文件记录形式。
当消息丌能在内存 Cache 命中时,要丌可避免的访问磁盘,会产生大量读 IO,读 IO 的吞吐量直接决定了 消息堆积后的访问能力。
评估消息堆积能力主要有以下四点:
(1). 消息能堆积多少条,多少字节?即消息的堆积容量。
(2). 消息堆积后,収消息的吞吏量大小,是否会叐堆积影响?
(3). 消息堆积后,正常消费的Consumer是否会叐影响?
(4). 消息堆积后,访问堆积在磁盘的消息时,推图量有多大?
分布式事务
支持。
两阶段提交,那么第二阶段必定涉及到查询消息,修改状态。
是通过使用offset偏移量查找消息,修改消息状态的。缺点就是导致系统脏页过多。
定时消息
支持,但是不支持非常精确的定时。
网络部署
producer集群—-nameServer集群—-broker集群—-consumer集群
producer 与nameserver和broker之间建立长连接。
consumer 与nameserver和broker之间建立长连接。
零拷贝
consumer消费消息的过程使用了零拷贝。
http://www.linuxjournal.com/article/6345
使用 mmap + write 方式
优点:即使频繁调用,使用小块文件传输,效率也很高
缺点:丌能很好的利用 DMA 方式,会比 sendfile 多消耗 CPU,内存安全性控制复杂,需要避免 JVM Crash 问题。使用 sendfile 方式
优点:可以利用 DMA 方式,消耗 CPU 较少,大块文件传输效率高,无内存安全新问题。 缺点:小块文件效率低亍 mmap 方式,只能是 BIO 方式传输,丌能使用 NIO。
RocketMQ 选择了第一种方式,mmap+write 方式,因为有小块数据传输的需求,效果会比 sendfile 更好。
名词
rocketmq中的术语都比较好理解。就是consumer有两种。
- Consumer 消息消费者,负责消费消息,一般是后台系统负责异步消费。
- PushConsumer
Consumer 的一种,应用通常吐 Consumer 对象注册一个 Listener 接口,一旦收到消息,Consumer 对象立刻回调 Listener 接口方法。 - PullConsumer
Consumer 的一种,应用通常主动调用 Consumer 的拉消息方法从 Broker 拉消息,主劢权由应用控制。
一般使用push consumer比较多。