1、为什么要用MQ?
(1)解耦:如果多个模块或者系统中,互相调用很复杂,维护起来比较麻烦,但是这个调用又不是同步调用,就可以运用MQ到这个业务中。以电商应用为例,用户创建订单后,如果耦合调用 库存系统、物流系统、支付系统,任何一个子系统出了故障或者因为升级等原因暂时不可用,都会造成下单操作异常,影响用户使用体验。
(2)异步:这个很好理解,比如用户的操作日志的维护,可以不用同步处理,节约响应时间。
(3)削峰:在高峰期的时候,系统每秒的请求量达到 5000,那么调用 MySQL 的请求也是 5000,一般情况下 MySQL 的请求大概在 2000 左右, 那么在高峰期的时候,数据库就被打垮了,那系统就不可用了。此时引入MQ,在系统 A 前面加个 MQ,用户请求先到 MQ,系统 A 从 MQ 中每秒消费2000 条数据,这样就把本来 5000 的请求变为 MySQL 可以接受的请求数量了,可以保证系统不挂掉,可以继续提供服务。MQ 里的数据可以慢慢的把它消费掉。
2、使用了MQ会有什么问题?
(1)降低了系统可用性 :系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务造成影响。
(2)增加了系统的复杂性:如何保证消息没有被重复消费?
3、为什么要用Rocketmq?
单机吞吐量:十万级;
可用性:非常高,分布式架构,多主多从;
消息可靠性:经过参数优化配置,消息可以做到0丢失
功能支持:MQ功能较为完善,支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。
支持10亿级别的消息堆积,不会因为堆积导致性能下降。
源码是java,比较容易做二次开发,进行定制。
在阿里双11已经经历了多次考验,稳定性好。
4、RocketMQ集群部署结构
1) Name Server
NameServer是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。
2) Broker
Broker部署相对复杂,Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。每个Broker与NameServer集群中的所有节点建立长连接,定时(每隔30s)注册Topic信息到所有NameServer。NameServer定时(每隔10s)扫描所有存活broker的连接,如果NameServer超过2分钟没有收到心跳,则NameServer断开与Broker的连接。
3) Producer
Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。Producer每隔30s(由ClientConfig的pollNameServerInterval)从Nameserver获取所有topic队列的最新情况,这意味着如果Broker不可用,Producer最多30s能够感知,在此期间内发往Broker的所有消息都会失败。Producer每隔30s(由ClientConfig中heartbeatBrokerInterval决定)向所有关联的broker发送心跳,Broker每隔10s中扫描所有存活的连接,如果Broker在2分钟内没有收到心跳数据,则关闭与Producer的连接。
4) Consumer
Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。Consumer每隔30s从NameServer获取topic的最新队列情况,这意味着Broker不可用时,Consumer最多最需要30s才能感知。Consumer每隔30s(由ClientConfig中heartbeatBrokerInterval决定)向所有关联的broker发送心跳,Broker每隔10s扫描所有存活的连接,若某个连接2分钟内没有发送心跳数据,则关闭连接;并向该ConsumerGroup的所有Consumer发出通知,Group内的Consumer重新分配队列,然后继续消费。
5、零拷贝原理
Linux操作系统分为【用户态】和【内核态】,文件操作、网络操作需要涉及这两种形态的切换,免不了进行数据复制。一台服务器 把本机磁盘文件的内容发送到客户端,一般分为两个步骤: 1)read;读取本地文件内容;
2)write;将读取的内容通过网络发送出去。 这两个看似简单的操作,实际进行了4 次数据复制,分别是:
1. 从磁盘复制数据到内核态内存;
2. 从内核态内存复制到用户态内存;
3. 然后从用户态 内存复制到网络驱动的内核态内存;
4. 最后是从网络驱动的内核态内存复 制到网卡中进行传输。
通过使用mmap的方式,可以省去向用户态的内存复制,提高速度。这种机制在Java中是通过MappedByteBuffer实现的,RocketMQ充分利用了上述特性,也就是所谓的“零拷贝”技术,提高消息存盘和网络发送的速度。 这里需要注意的是,采用MappedByteBuffer这种内存映射的方式有几个限制,其中之一是一次只能映射1.5~2G 的文件至用户态的虚拟内存,这也是为何RocketMQ 默认设置单个CommitLog日志数据文件为1G的原因了
6、消息存储结构
RocketMQ消息的存储是由ConsumeQueue和CommitLog配合完成 的,消息真正的物理存储文件是CommitLog,ConsumeQueue是消息的逻辑队列,
类似数据库的索引文件,存储的是指向物理存储的地址。每个Topic下的每个Message Queue都有一个对应的ConsumeQueue文件。
CommitLog:存储消息的元数据
ConsumerQueue:存储消息在CommitLog的索引
IndexFile:为了消息查询提供了一种通过key或时间区间来查询消息的方法,这种通 过IndexFile来查找消息的方法不影响发送与消费消息的主流程