RocketMQ架构原理与消息幂等

集群部署架构

在这里插入图片描述
结合部署结构图,描述集群工作流程:

  1. 启动NameServer,NameServer启动后监听端口,等待Broker、Produer、Consumer连上来,相当于一个路由控制中心。
  2. Broker启动,跟所有的NameServer保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储的所有Topic信息。注册成功后,NameServer集群中就有Topic跟Broker的映射关系。
  3. 收发消息前,先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上。也可以在发送消息时自动创建Topic。
  4. Producer发送消息,启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic存在哪些Broker上,然后跟对应的Broker建长连接,直接向Broker发消息。
  5. Consumer跟Producer类似。跟其中一台NameServer建立长连接,获取当前订阅的Topic位于哪些Broker,然后直接跟Broker建立连接通道,开始消费消息。

模块功能特性

NameServer

NameServer就是一个注册中心,存储当前集群所有Broker信息、Topic跟Broker的对应关系。

  1. NameServer用于存储Topic、Broker关系信息,功能简单,稳定性高。多个NameServer之间相互没有通信,单台NameServer宕机不影响其他NameServer与集群;即使整个NameServer集群宕机,已经正常工作的Producer,Consumer,Broker仍然能正常工作,但新起的Producer, Consumer,Broker就无法工作。
  2. NameServer压力不会太大,平时主要开销是在维持心跳和提供Topic-Broker的关系数据。但有一点需要注意,Broker向NameServer发心跳时,会带上当前自己所负责的所有Topic信息,如果Topic个数太多(万级别),会导致一次心跳中,就Topic的数据就几十M,网络情况差的话,网络传输失败,心跳失败,导致NameServer误认为Broker心跳失败。
    NameServer是rocketmq自己实现的配置服务器,不是Zookeeper。

Broker

集群最核心模块,主要负责Topic消息存储、管理和分发等功能。是消息的摆渡者

  1. 高并发读写服务
    Broker的高并发读写主要是依靠以下两点:
  • 消息顺序写,所有Topic数据同时只会写一个文件,一个文件满1G,再写新文件,真正的顺序写盘,使得发消息TPS大幅提高。
  • 消息随机读,RocketMQ尽可能让读命中系统pagecache,因为操作系统访问pagecache时,即使只访问1K的消息,系统也会提前预读出更多的数据,在下次读时就可能命中pagecache,减少IO操作。
  1. 负载均衡与动态伸缩
    负载均衡:Broker上存Topic信息,Topic由多个队列组成,队列会平均分散在多个Broker上,而Producer的发送机制保证消息尽量平均分布到所有队列中,最终效果就是所有消息都平均落在每个Broker上。
    动态伸缩能力(非顺序消息):Broker的伸缩性体现在两个维度:Topic, Broker。
  • Topic维度:假如一个Topic的消息量特别大,但集群水位压力还是很低,就可以扩大该Topic的队列数,Topic的队列数跟发送、消费速度成正比。
  • Broker维度:如果集群水位很高了,需要扩容,直接加机器部署Broker就可以。Broker起来后向NameServer注册,Producer、Consumer通过NameServer发现新Broker,立即跟该Broker直连,收发消息。
  1. 高可用&高可靠
    高可用:集群部署时一般都为主备,备机实时从主机同步消息,如果其中一个主机宕机,备机提供消费服务,但不提供写服务。
    高可靠:所有发往broker的消息,有同步刷盘和异步刷盘机制;同步刷盘时,消息写入物理文件才会返回成功,异步刷盘时,只有机器宕机,才会产生消息丢失,broker挂掉可能会发生,但是机器宕机崩溃是很少发生的,除非突然断电
  2. Broker与NameServer的心跳机制
    单个Broker跟所有NameServer保持心跳请求,心跳间隔为30秒,心跳请求中包括当前Broker所有的Topic信息。NameServer会反查Broker的心跳信息,如果某个Broker在2分钟之内都没有心跳,则认为该Broker下线,调整Topic跟Broker的对应关系。但此时NameServer不会主动通知Producer、Consumer有Broker宕机。

消费者(Consumer)

消费者启动时需要指定NameServer地址,与其中一个NameServer建立长连接。消费者每隔30秒从NameServer获取所有topic的最新队列情况,这意味着某个Broker如果宕机,客户端最多要30秒才能感知。连接建立后,从NameServer中获取当前消费Topic所涉及的Broker,直连Broker。
Consumer跟Broker是长连接,会每隔30秒发心跳信息到Broker。Broker端每10秒检查一次当前存活的Consumer,若发现某个Consumer 2分钟内没有心跳,就断开与该Consumer的连接,并且向该消费组的其他实例发送通知,触发该消费者集群的负载均衡。
消费者端的负载均衡
先讨论消费者的消费模式,消费者有两种模式消费:集群消费,广播消费。
首先需要知道一个消费者ID可以启动多个消费者实例
广播消费:每个消费者消费Topic下的所有队列。
集群消费:一个Topic可以由同一个ID下所有消费者分担消费。具体例子:假如TopicA有6个队列,某个消费者ID起了2个消费者实例,那么每个消费者负责消费3个队列。如果再增加一个消费者ID相同的消费者实例,即当前共有3个消费者同时消费6个队列,那每个消费者负责2个队列的消费。
消费者端的负载均衡,就是集群消费模式下,同一个ID的所有消费者实例平均消费该Topic的所有队列。

生产者(Producer)

Producer启动时,也需要指定NameServer的地址,从NameServer集群中选一台建立长连接。如果该NameServer宕机,会自动连其他NameServer。直到有可用的NameServer为止。
生产者每30秒从NameServer获取Topic跟Broker的映射关系,更新到本地内存中。再跟Topic涉及的所有Broker建立长连接,每隔30秒发一次心跳。在Broker端也会每10秒扫描一次当前注册的Producer,如果发现某个Producer超过2分钟都没有发心跳,则断开连接。
生产者端的负载均衡
生产者发送时,会自动轮询当前所有可发送的Broker,一条消息发送成功,下次换另外一个Broker发送,以达到消息平均落到所有的Broker上。
这里需要注意一点:假如某个Broker宕机,意味生产者最长需要30秒才能感知到。在这期间会向宕机的Broker发送消息。当一条消息发送到某个Broker失败后,会往该broker自动再重发2次,假如还是发送失败,则抛出发送失败异常。业务捕获异常,重新发送即可。客户端里会自动轮询另外一个Broker重新发送,这个对于用户是透明的。

关键概念

一.Topic

官方的定义:Topic是生产者在发送消息和消费者在拉取消息的类别。Topic与生产者和消费者之间的关系非常松散。具体来说,一个Topic可能有0个,一个或多个生产者向它发送消息;相反,一个生产者可以发送不同类型Topic的消息。类似的,消费者组可以订阅一个或多个主题,只要该组的实例保持其订阅一致即可。
Topic在Google翻译中解释为话题。我们可以理解为第一级消息类型,类比于书的标题。在应用系统中,一个Topic标识为一类消息类型,比如交易信息。
在Producer中使用Topic:

Message msg = new Message("TopicTest" /* Topic */,
                    "TagA",("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
    在Consumer中订阅Topic:
consumer.subscribe("TopicTest", "*");
二.Tag

同样,先看看官方怎么定义的:标签,换句话的意思就是子主题,为用户提供了额外的灵活性。有了标签,来自同一业务模块的具有不同目的的消息可以具有相同的主题和不同的标记。标签有助于保持代码的清晰和连贯,同时标签也方便RocketMQ提供的查询功能。
Tag为标签。我们可以理解为第二级消息类型,类比于书的目录,方便检索使用消息。在应用系统中,一个Tag标识为一类消息中的二级分类,比如交易信息下的交易创建、交易完成。
在Producer中使用Tag:

Message msg = new Message("TopicTest",
                    "TagA" /* Tag */,
                    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
    在Consumer中订阅Tag:
consumer.subscribe("TopicTest", "TagA||TagB");// * 代表订阅Topic下的所有消息
三.GroupName

和现实世界中一样,RocketMQ中也有组的概念。代表具有相同角色的生产者组合或消费者组合,称为生产者组或消费者组。作用是在集群HA的情况下,一个生产者down之后,本地事务回滚后,可以继续联系该组下的另外一个生产者实例,不至于导致业务走不下去。在消费者组中,可以实现消息消费的负载均衡和消息容错目标。
另外,有了GroupName,在集群下,动态扩展容量很方便。只需要在新加的机器中,配置相同的GroupName。启动后,就立即能加入到所在的群组中,参与消息生产或消费。
在Producer中使用GroupName:

DefaultMQProducer producer = new DefaultMQProducer("group_name_1");// 使用GroupName来初始化Producer,如果不指定,就会使用默认的名字:DEFAULT_PRODUCER
    在Consumer中使用GroupName: DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_name_1");// 使用GroupName来初始化Consumer,如果不指定,就会使用默认的名字:DEFAULT_CONSUMER

消息幂等

消息幂等指的是消息重复发送时消费一次消息和消费多次时,业务代码的执行结果相同。
实现
在消费者的回调方法中先检测缓存中是否有此条消息对应的键值对,没有的话说明是第一次消费此消息,下面可以执行业务代码,否则直接结束

当第一次执行完业务代码后在缓存中set此条消息的键值对,同一条消息重复发送时它的tag,key, id是不变的,因此可以将tag和key作为键,id作为值存入缓存以标识一条消息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值