RocketMq
MQ选型对比
1、ActiveMQ
- ActiveMQ采用消息推送方式,所以最适合的场景是默认消息都可在短时间内被消费。数据量越大,查找和消费消息就越慢,消息积压程度与消息速度成反比。
- 吞吐量低。由于ActiveMQ需要建立索引,导致吞吐量下降。这是无法克服的缺点,只要使用完全符合JMS规范的消息中间件,就要接受这个级别的TPS。
- 无分片功能。这是一个功能缺失,JMS并没有规定消息中间件的集群、分片机制。而由于ActiveMQ是为企业级开发设计的消息中间件,初衷并不是为了处理海量消息和高并发请求。如果一台服务器不能承受更多消息,则需要横向拆分
2、Kafka
- 性能较高,基本发送消息给kafka都是毫秒级性能【每秒十几万ops】,可用性很高,支持集群部署,部分宕机仍然可继续运行
- 存在数据丢失问题,由于存储在磁盘缓冲区,没有录到物理磁盘,机器故障会导致数据丢失
- 功能单一,主要支持发送消费消息,没有额外高级功能
- 主要用在日志采集和传输上
3、RabbitMQ
- 保证数据不丢失,高可用性
- 支持部分高级功能,比如死信队列,消息重试
- 吞度量比较低,每秒几万级别,高并发情况,支撑困难,且集群扩展比较麻烦
- 其语言是erlang,导致无法更改源代码
- 适用于中小型企业
4、RocketMQ
- 吞吐量高,可达10万qps以上,而且可保证高可用性,性能很高
- 可通过配置保证数据不丢失,可部署大规模集群
- 支持高级功能,延迟消息,事务消息,消息回溯,死信队列,消息积压等
- java语言开发,可修改源码,阅读源码 ,唯一不足就是官方文档简单
rocket架构组成
整体的架构设计主要分为四大部分,分别是:Producer、Consumer、Broker、
NameServer。
1)Producer:就是消息生产者,可以集群部署。它会先和 NameServer 集群中的随机一台建立长连接,得知当前要发送的 Topic 存在哪台 Broker Master上,然后再与其建立长连接,支持多种负载平衡模式发送消息。
2)Consumer:消息消费者,也可以集群部署。它也会先和 NameServer 集群中的随机一台建立长连接,得知当前要消息的 Topic 存在哪台 Broker Master、Slave上,然后它们建立长连接,支持集群消费和广播消费消息。
3)Broker:主要负责消息的存储、查询消费,支持主从部署,一个 Master 可以对应多个 Slave,Master 支持读写,Slave 只支持读。Broker 会向集群中的每一台 NameServer 注册自己的路由信息。
4)NameServer:是一个很简单的 Topic 路由注册中心,支持 Broker 的动态注册和发现,保存 Topic 和 Borker 之间的关系。通常也是集群部署,但是各 NameServer 之间不会互相通信, 各 NameServer 都有完整的路由信息,即无状态。
rocketmq工作原理
nameServer核心工作原理
-
nameServer集群部署【多台,防止单台宕机导致整个mq挂掉】,
-
每个broker启动时向所有的nameServer进行注册,
-
生产者消费者每隔一段时间主动去nameServer拉取broker信息
-
broker与nameserver如何通信?
① 采用tcp长连接进行通信,Broker会跟每个NameServer都建立一个TCP长连接,然后定时通过TCP长连接发送心跳请求过去
② broker与nameserver之间通过心跳机制确认broker是否宕机,broker会每个30秒向所有nameServer发送心跳告知nameserver自己仍然存活,每个nameser每个10s检查一次有没有哪个broker超过120s没发送心跳,如果有就说明broker党纪,从路由摘除broker
broker工作原理
-
主broker如何同步消息给从?主从模式采用pull模式,从broker不停的发送请求到主broker去拉取消息
-
rocketmq实现读写分离了么?主broker主要接受系统消息输入,然后同步给从broker,消费者的系统获取消息时,可能会从主或者从上获取
主节点在返回消息到消息系统时,会根据主broker负责情况和从的同步情况,建议消息系统下次是从主还是从拉取数据
-
master 挂掉,从broker是否可以自动切换?不能,只能手动修改从节点配置,重新启动调整为master,而且中间一段时间不可用
-
mq4.5之后,dledger可以实现从节点切换为新的master【至少一个master,两个slaver,都会注册到nameserver】,三个broker组成一个group,保证一旦master党纪,可以从剩余的两个slave选举出一个新master对外提供服务
生产者发送broker原理
- Topic作为一个数据集合是怎么在Broker集群里存储的 ?分布式存储
- 生产者系统是如何将消息发送到Broker的呢?
消息发送前创建topic,发送消息时指定发送哪个topic,由于知道消息发送的topc,就可以与nameServer建立一个tcp长连接,定时从他那拉取最新的路由信息,包括及群里的哪些broker,哪些topic,每个topic存储在哪个broker上,生产者此时通过路由信息找到自己要投递的topic存在哪几台broker上,根据负载均很算法【轮询/hash】选择一台broker,选择后,就可以合对应的broker建立tcp长连接,即可通过长连接向broker发送消息
注:生产者一定是投递消息到master,master同步slave,实现备份,保证master故障数据不丢失,且slave可自动切换master提供服务
消费者拉取broker原理
原理同生产者类似,与nameserver建立长连接,从nameserver拉取路由数据,找到topic存储在哪个broker,与broker建立长连接,拉取消息
注:可从master/slave拉取消息
整体架构特点
高可用:nameserver集群化部署,保证路由信息完整
dledger技术实现slave自动切换maser,随便一台机器挂了没有太大影响
生产消费者集群化部署
高并发/海量消息:多个master部署方式,加上topic分散在多台broke中,可以抗下高并发和海量消息存储
可伸缩:集群中加入更多broker机器就可以实现线性扩展集群了
RocketMQ消息发送分类
RocketMQ 支持 3 种消息发送方式 :同 步(sync ) 、 异步(async)、单向(o neway )
同步发送消息
发送者向 MQ 执行发送消息 API 时,同步等待, 直到消息服务器返回发送结果 。
@PostConstruct
private void init() {
log.info("======开始启动rocketmq producer=====");
log.info("======nameserver地址:" + environment.getProperty("rocketmq.nameServer"));
log.info("======groupName:" + groupName);
sender = new DefaultMQProducer(groupName);
sender.setNamesrvAddr(environment.getProperty("rocketmq.nameServer"));
sender.setSendMsgTimeout(sendMsgTimeout);
sender.setRetryTimesWhenSendFailed(retryTimesWhenSendFailed);
try {
//启动
sender.start();
} catch (MQClientException e) {
log.error("====rocketmq producer启动失败======", e);
}
log.info("rocketmq producer启动成功");
}
private SendStatus setMsgBody(String message, String tag) {
SendStatus status = null;
if (sender != null) {
Message msg = new Message();
msg.setTags(tag);
msg.setTopic(String.format(topic));
try {
//设置消息内容
msg.setBody(message.getBytes(StandardCharsets.UTF_8));
SendResult result = sender.send(msg);
status = result.getSendStatus();
log.info("消息发送完成,返回status:{}", status);
} catch (Exception e) {
log.error(groupName + "producer消息发送失败 :", e);
}
}
return status;
}
异步 发送
发送者向 MQ 执行发送消息 API 时,指定消息发送成功后的回掉函数,然后调用消息发送 API 后,立即返回,消息发送者线程不阻塞 ,直到运行结束,消息发送成功或失败的回调任务在一个新的线程中执行 。
//设置异步发送失败次数时候重试次数为0
defaultMQProducer.setRetryTimesWhenSendAsyncFailed(0);
defaultMQProducer.send(sendMsg,new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
}
@Override
public void onException(Throwable e) {
}
});
单向发送
消息发送者向 MQ 执行发送消息 API 时,直接返回,不等待消息服务器的结果,也不注册回调函数,简单地说,就是只管发,不在乎消息是否成功存储在消息服务器上。
defaultMQProducer.sendOneway(msg);
RocketMQ消息消费分类
对于任何⼀款消息中间件⽽⾔,消费者客户端⼀般有两种⽅式从消息中间件获取消息并消费:
(1)Push⽅式:由消息中间件主动地将消息推送给消费者;采⽤Push⽅式,可以尽可能实时地将消息发送给消费者进⾏消费。但是,在消费者的处理消息的能⼒较弱的时候(⽐如,消费者端的业务系统处理⼀条消息的流程⽐较复杂,其中的调⽤链路
⽐较多导致消费时间⽐较久。概括起来地说就是“慢消费问题”),⽽MQ不断地向消费者Push消息,消费者端的缓冲区可能会溢出,导致异常;
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
2) Pull⽅式:由消费者客户端主动向消息中间件拉取消息;采⽤Pull⽅式,如何设置Pull消息的频率需要重点去考虑,举个例⼦来说,可能1分钟内连续来了1000条消息,然后2⼩时内没有新消息产⽣(概括起来说就是“消息延迟与忙等待”)。如果每次Pull的时间间隔⽐较久,会增加消息的延迟,即消息到达消费者的时间加长,MQ中消息的堆积量变⼤;若每次Pull的时间间隔较短,但是在⼀段时间内MQ中并没有任何消息可以消费,那么会产⽣很多⽆效的Pull请求的RPC开销,影响MQ整体的⽹络性能;
DefaultMQPullConsumer consumer = new DefaultMQPullhConsumer(groupName);
3) 集群模式:默认情况就是集群模式,一个消费组获取到一条消息,只会交给组内的一台机器去处理,不是每台机器都可以获取到
这条消息的。
4)广播模式:consumer.setMessageModel(MessageModel.BROADCASTING);改为广播模式,那么对于消费组获取到的一条消息,组内每台机器都可以获取到这条消息。 【相对用的少】
Rocket底层原理
1、Topic、MessageQueue以及Broker之间到底是什么关系?
每个topic数据分布式存储在多个broker,如何确定能个topic分布在哪个broker,此刻引入messagequeue概念,本质上是数据分片机制,通过messagequeue将一个topic数据拆分成多个数据分片,然后每个broker机器上存储一些messagequeue,这样实现了topic的分布式存储
2、生产者发送消息的时候写入哪个MessageQueue
生产者从nameserver拉取topic元数据,通过topic就可以知道topic有哪几个messagequeque,每个messagequeque在哪台数据,这样就可以将数据存入到各自的brokerr对应的messagequeque
3、如果某个Broker出现故障该怎么办?
Producer中开启一个开关,就是sendLatencyFaultEnable,就可以避免一个Broker故障之后,短时间内生产者频繁的发送消息到这个故障的Broker上去,出现较多次数的异常
4、看看Broker对于接收到的消息,到底是如何存储到磁盘上去的?
1) 消息直接写入磁盘上的一个日志文件,叫做CommitLog【每个文件想定1GB,超出1Gb,会创建新的commitLog】,其实他同时会将这条消息在CommitLog中的物理位置,也就是一个文件偏移量,就是一个offset,写入到这条消息所属的MessageQueue对应的ConsumeQueue文件中去。
2)对Topic下的每个MessageQueue都会有一系列的ConsumeQueue文件 ,就是broker的磁盘上格式为:【$HOME/store/consumequeue/{topic}/{queueId}/{fileName}】
实际上在ConsumeQueue中存储的每条数据不只是消息在CommitLog中的offset偏移量,还包含了消息的长度,以及tag
hashcode,一条数据是20个字节,每个ConsumeQueue文件保存30万条数据,大概每个文件是5.72MB。
注: 一个Broker机器而言,存储在他上面的所有Topic以及MessageQueue的消息数据都是写入一个统一的CommitLog的
5、如何让消息写入CommitLog文件近乎内存写性能的?
Broker是基于OS操作系统的PageCache和顺序写两个机制,来提升写入CommitLog文件的性能的。
1)broker以顺序方式将消息写入comitLog磁盘文件,比文件随机写性能提升很多
2) 数据写入commitlog时,先进入os的pageCache内存缓冲中 ,然后有os的后台选择时间异步化将os pageCache内从缓冲中数据刷入底层磁盘文件【pagecache写入+os异步刷盘策略】,
注意,broker写如pagecache就返回ack确认消息给生产者,而此刻broker宕机,数据还未写入磁盘,则存在数据丢失问题
6 同步刷盘
生产这发送消息,broker收到消息,强制消息刷入底层物理磁盘,才返回ack给producer,此时消息写入成功,如果broker还没来得及写入,就宕机,由于生产者并未烧到ack消息,所以可以感知消息发送失败,只要重试发送既可以了,保证了数据不丢失
注:同步步刷盘和异步刷盘各自的优缺点:高吞吐写入+丢失数据风险,写入吞吐量下降+数据不丢失
调整broker的配置文件,将其中的flushDiskType配置设置
为:SYNC_FLUSH,默认他的值是ASYNC_FLUSH,即默认是异步刷盘的。
7 基于DLedger技术部署的Broker高可用集群,到底如何进行数据同步的?
DLedger:有自己的commitlog机制,数据交给他,会写入commitlog磁盘文件,dledeger技术实现高可用架构实际上就是用DLedger先替换掉原来Broker自己管理的CommitLog,由DLedger来管理CommitLog
DLedger是基于Raft协议来互相投票进行Leader Broker选举,只要有(n台机器 / 2) + 1个人投票给某个人,就会选举他当Leader
Dledger采用Raft协议进行多副本同步,数据同步分为uncommitted和commited两个阶段,首先leader上的dleger收到一条消息,标记状态为uncommiteed,通过dleger组件把uncommited状态数据同步给fllower deledger服务,fllower deledger服务收到消息后,返回ack给leader,如果leder超过半数以上就将状态标记为committed,然后leader发送给fllower,flower将消息标记为commiteed
注意:如果leaderbroker宕机,可以基于Dledger技术和raft协议重新选举leader
8 消费者是如何从Broker拉取消息回来,进行处理以及ACK的?如果消费者故障了会如何处理
1)根据要消费的MessageQueue以及开始消费的位置,去找到对应的ConsumeQueue读取里面对
应位置的消息在CommitLog中的物理offset偏移量,然后到CommitLog中根据offset读取消息数据,返回给消费者机器。
2)消费者机器拉取到一批消息之后,就会将这批消息回调我们注册的一个函数
@Autowired
private MessageListenerConcurrently mqMessageListenerProcessor;
consumer.registerMessageListener(mqMessageListenerProcessor);
public class MQConsumeMsgListenerProcessor implements MessageListenerConcurrently {
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
//处理消息
//标记该消息已经成功被消费
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}}
3)消息处理完,消费者机器会提交消费进度到broker,broker会存储我们的消费进度,标记ConsumeOffset,下次该消费组就可以继续从这个位置继续拉取
4)出现宕机,则重新给各个消费机器分配他们要处理的MessageQueue。
一个 MessageQueue只能被一个消费机器去处理,但是一台消费者机器可以负责多个MessageQueue的消息处理
9 消费者到底是基于什么策略选择Master或Slave拉取数据的?
1)ConsumeQueue文件读取:基于os cache读取,在消费者机器拉取消息的时候,第一步大量的频繁读取ConsumeQueue文件,几乎可以说就是跟读内存里的数据的性能是一样的,通过这个就可以保证数据消费的高性能以及高吞吐
2)CommitLog文件读取:基于os cache+磁盘一起读取
- 如果读取刚写入的commitlog数据,大概率时从os cache读取的commitlog数据,由于操作内存性能高
- 如果读取比较早写入的commitlog数据,则是从磁盘读取,性能比较差些
3)对比你当前没有拉取消息的数量和大小,以及最多可以存放在os cache内存里的消息的大小,如
果你没拉取的消息超过了最大能使用的内存的量,那么说明你后续会频繁从磁盘加载数据,此时就让你从slave broker去加载数据了!