在RocketMq中有四个部分组成,分别是Producer,Consumer,Broker,以及NameServer,类比于生活中的邮局,分别是发信者,收信者,负责暂存,传输的邮局,以及协调各个地方邮局的管理机构。
NameServer:
主要是 Topic 和 Broker 注册中心,支持 Broker 动态注册和发现,主要保存 Topic的路由信息和Broker的状态信息。通常也是集群部署,但是各 NameServer 之间不会互相通信, 各 NameServer 都有完整的路由信息,即无状态(跟zk的区别是,zk为有状态的)。
Broker:
就是MQ本身,负责收发消息、持久化消息,每个broker负责管理一部分topic的消息;主要分为 Master Broker 和 Slave Broker,一个 Master Broker 可以对应多个 Slave Broker,但是一个 Slave Broker 只能对应一个 Master Broker。Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。每个Broker与Name Server集群中的所有节点建立长连接,定时注册Topic信息到所有Name Server。Broker 启动后需要完成一次将自己注册至 Name Server 的操作。
Producer:消息生产者
可以集群部署。它会先和 NameServer 集群中的随机一台建立长连接,得知当前要发送的 Topic 存在哪台 Broker Master上,然后再与其建立长连接,支持多种负载平衡模式发送消息。
Consumer:消息消费者
可以集群部署。它也会先和 NameServer 集群中的随机一台建立长连接,得知当前要消息的 Topic 存在哪台 Broker Master、Slave上,然后它们建立长连接,支持集群消费和广播消费消息。
Topic:
标识一类消息的逻辑名字,消息的逻辑管理单位。无论消息是生产还是消费都需要指定Topic;比如电商系统可以分为:交易消息、物流消息,每条消息都必须有一个topic,一个类型的消息可以定义一个topic,也可以定义多个,根据业务需求来定;
Tag:
可以看作子主题,它是消息的第二级类型,同一业务模块不同目的的消息就可以用相同 Topic 而不同的 Tag 来标识。比如交易消息又可以分为:交易创建消息、交易完成消息等,一条消息可以没有 Tag ;虽然同一个topic的管理逻辑一样,但是消费topic1的时候,如果你订阅指定的是tagA,那么tagB的消息不会投递。
Group:
分组,一个组可以订阅多个Topic,代表某一类的生产者和消费者,一般来说同一个服务可以做为Group,同一个Group一般来说发送和消费的消息都是一样的。
Queue:
队列其实就是对Topic的分片,在Kafka里面就是Partition。将Topic分片再切分为若干等分,其中的一份就是一个Queue。每个Topic分片等分的Queue的数量可以不同,由用户在创建Topic时指定。这些队列会被RocketMQ均衡的分布在不同Broker上,Producer在发送消息时会根据一定策略选择一个消息队列进行发送,这样就可以实现负载均衡和提高吞吐的效果。
Offset:
偏移,本质上有两种Offset,一种是写入时末尾的Offset,另一种是同一消费组读的Offset。RocketMq消息内容是分片存储,CommitLog 的大小默认是1G,当超过大小限制的时候需要准备新的文件,CommitLog 采用混合型存储,也就是所有 Topic 都存在一起,顺序追加写入,文件名用起始偏移量命名。其次RocketMq还存储了消息体与偏移的关系,用于快速随机读取和检索。
最开始的时候业务量小,直接单机能解决,后来业务量大,采用微服务的设计思想,分布式部署的方式,拆出了很多服务,场景也就越来越复杂,所以引用了消息队列。
削峰:在高并发场景下,系统的请求量可能会瞬间增加,给服务器带来巨大的压力。通过使用消息队列,可以将突发的大量请求进行缓冲和削峰,使其平滑地处理,从而避免服务器过载和崩溃;
异步:如果下单流程涉及多个系统,影响支付时间,所以支付同时完成其他工作去校验;
解耦:可以将系统的各个模块解耦,减少模块之间的直接依赖。这样可以使各个模块独立地进行开发和部署,提高系统的可扩展性和灵活性。同时,解耦也可以降低系统间的耦合度,提高系统的可维护性和稳定性;
限流:通过控制消息的生产者和消费者的速度,可以限制系统的流量,防止过多的请求对服务器造成过大的压力。
不能保证全局顺序消费,只能保证单个queue里的顺序,queue是典型的FIFO;
影响重复消费的主要原因是网络原如下:
解决办法:
RocketMQ重复消费的问题可以通过以下几种方式解决:
Producer端:
利用自带的事务处理机制,发送half信息,如果正常就处理,不正常就回滚;
Broker端:
-发送消息不会被及时消费,比如关闭超时未支付订单,下单30分钟后支付启动定时,隔几秒去扫描待支付的订单
消费模型由Consummer决定,消费维度为topic
集群消费:默认的模式
广播消费:同Topic下的消息会被多个实例共同消费。也就是说,如果一个Topic下有多个Consumer实例,那么每个消息会被所有的Consumer实例消费一次。这种模式适用于一些分发消息的场景,例如将消息同时发送给多个消费者进行处理。
没有真正意义的push,都是pull,虽然有push类,但是实际底层是长轮训机制,即拉取方式;
RocketMQ的Broker在处理拉取请求时,会根据负载均衡策略选择一个负责处理该请求的Consumer实例。Broker将消息队列中存储的消息按照提交偏移量的顺序发送给Consumer实例。Consumer实例接收到消息后,会根据消费策略对消息进行处理,并将处理结果返回给Broker。Broker收到Consumer的处理结果后,会更新消息的消费状态,以便下次拉取时能够正确地发送。
Producer端:发送端指定message queue发送消息到相应的broker,来达到写入时的负载均衡。同时,Producer还支持开启隔离机制,通过判断Broker是否被隔离以及是否是上一次选择的那个Broker,来避免将消息发送到不可用的Broker上。
Consumer端:默认情况下,Consumer会根据队列的平均值进行负载均衡,即将所有队列的平均值作为Broker的负载均衡值。如果某个Broker的负载均衡值高于其他Broker,那么Consumer会优先选择该Broker进行拉取请求。如果多个Broker的负载均衡值相同,则会根据优先级选择。
其他负载均衡算法:
- 环形分配策略
- 手动配置分配策略
- 机房分配策略
- 一致性哈希分配策略
- 靠近机房策略
RocketMQ的Broker采用主从架构和多副本策略来解决Broker突然宕机的问题。具体来说,Master收到消息后会同步给Slave,这样一条消息就不止一份了,Master宕机了还有Slave中的消息可用,保证了MQ的可靠性和高可用性。
如果Broker宕机,NameServer会感知到。Broker会定时向NameServer发送心跳,然后NameServer会定时运行一个任务,去检查一下各个Broker的最近一次心跳时间。如果某个Broker超过120s都没发送心跳了,那么就认为这个Broker已经挂掉了。
Broker启动的时候会向所有的NameServer节点进行注册,注意这里是向集群中所有的NameServer节点注册,而不是只向其中的某些节点注册,因为NameServer每个节点都是对等的,所以Broker需要向每一个节点进行注册,这样每一个节点都会有一份Broker的注册信息。
RocketMQ的自动伸缩扩容机制主要基于Broker的负载情况。当Broker的负载过高时,会自动触发扩容操作,增加新的Broker实例来分担负载。扩容过程不需要人工干预,系统会自动完成。同时,当流量回归正常后,为了防止资源的浪费,可以自动缩容,将一些不必要的Broker实例移除。
此外,RocketMQ还支持消息队列的动态扩容和缩容。在Topic的消息量特别大时,可以通过增加消息队列的数量来提高系统的处理能力。相反,如果消息队列的数量过多,也可以适当减少,以节约系统资源。
RocketMQ的自动伸缩扩容机制可以有效地提高系统的可用性和稳定性,同时避免了资源的浪费。在实际应用中,需要根据具体的需求和场景进行配置和调整,以达到最佳的效果。
设计一个分布式消息中间件需要考虑多个方面,包括消息的可靠性、一致性、性能、扩展性、容错性等。以下是一个可能的整体架构设计: