Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

介绍一下 RocketMQ

说起消息队列一般Web后端做过一段时间开发的肯定会用过,在前司的时候用的是改良版的 NSQ,有点像 NOSQL 的简写版🙄,其实是个go 语言写的消息队列,nsq 看代码提交感觉最近更新的不是很勤,不过因为前司有专门的中间件团队,所以还是挺好用的,而且中间件团队的大牛也很厉害,一次都没碰到过丢消息之类的错误,然后现在公司用的是 RocketMQ,本着总还是要了解下的,并且消息队列也是服务端开发中一个很重要的中间件,因为不太有不需要用消息队列的后端团队了吧,原来对 nsq 也不是特别了解原理,就打算了解下 RocketMQ。

还是像我这样的小白专属,消息队列用来干啥,很多都是标准答案,用来削峰填谷的,这个完全对,只是我想结合场景说给像我这样的小白同学听,想想一个电商的下单功能,除了 AT 两家之外应该大部分都是接入的支付,那么下单支付完成后一般都是等支付回调,告诉你支付完成了(也有可能是失败了,或者超时了咱们主动去查),然后这个回调里我们自己的业务代码干点啥,首先比如是把订单状态改掉了,然后会有各类的操作,比如把优惠券核销了,把其他金钱相关的也核销了,把购物车里对应的商品给删了,还有更次要的,比如发个客服消息,让用户确认下地址的,给用户加积分的等等等等,想象下如果这些都是回调里一股脑儿做掉了,那可能你的代码健壮性跟相关服务的稳定性还有性能要达到一个非常高的水平才能让业务不出现异常,并且万一流量打起来了,这些重要的不重要的操作都会阻塞着,所以需要用一个消息队列,在接到回调后只处理极少的几个核心操作,完了就把这个消息丢进消息队列里,让各个业务方去消费这个消息,把客服消息发一下,给用户加个积分等等,这样子主要的业务流程需要处理的事情就少了,速度也加快了,这个例子呢不能严格算是削峰填谷的例子,不过也算是消息队列的比较典型的使用场景了,要说真实的削峰填谷的话其实可以这么理解,假如短时间内有 1w 个请求进来,系统能支持的 QPS 才 1000,那么正常情况下服务就挂了,或者被限流了,为了让服务正常,那么可以把这些请求先放进消息队列里,我服务端以拉的模式按我的处理能力来消费,这样就没啥问题了

扯了这么多来聊聊 RocketMQ 长啥样

6073827-a998e005dd13967c

总共有四大部分:NameServer,Broker,Producer,Consumer。

NameServer

NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。主要包括两个功能:Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以动态感知Broker的路由的信息。

NameServer压力不会太大,正常情况主要负责维持心跳和提供Topic-Broker的关系数据。但有一点需要注意,Broker向Namesr发心跳时,会带上当前自己所负责的所有Topic信息,如果Topic个数太多,会导致一次心跳中,光Topic的数据就非常大,网络情况差的话,网络传输失败,心跳失败,导致Namesrv误认为Broker心跳失败。

Broker

Broker主要负责消息的存储、投递和查询以及服务高可用保证,为了实现这些功能,Broker包含了以下几个重要子模块。

  • Remoting Module:整个Broker的实体,负责处理来自clients端的请求。
  • Client Manager:负责管理客户端(Producer/Consumer)和维护Consumer的Topic订阅信息
  • Store Service:提供方便简单的API接口处理消息存储到物理硬盘和查询功能。
  • HA Service:高可用服务,提供Master Broker 和 Slave Broker之间的数据同步功能。
  • Index Service:根据特定的Message key对投递到Broker的消息进行索引服务,以提供消息的快速查询。
Broker的特点

1.负载均衡:Broker上存Topic信息,Topic由多个队列组成,队列会平均分散在多个Broker上,而Producer的发送机制保证消息尽量平均分布到所有队列中,最终效果就是所有消息都平均落在每个Broker上。

2.动态伸缩能力(非顺序消息):Broker的伸缩性体现在两个维度:Topic, Broker。

Topic维度:假如一个Topic的消息量特别大,但集群水位压力还是很低,就可以扩大该Topic的队列数,Topic的队列数跟发送、消费速度成正比。
Broker维度:如果集群水位很高了,需要扩容,直接加机器部署Broker就可以。Broker起来后想NameServer注册,Producer、Consumer通过NameServer发现新Broker,立即跟该Broker直连,收发消息。

3.高可用&高可靠

高可用:集群部署时一般都为主备,备机实时从主机同步消息,如果其中一个主机宕机,备机提供消费服务,但不提供写服务。
高可靠:所有发往broker的消息,有同步刷盘和异步刷盘机制;同步刷盘时,消息写入物理文件才会返回成功,异步刷盘时,只有机器宕机,才会产生消息丢失,broker挂掉可能会发生,但是机器宕机崩溃是很少发生的,除非突然断电

Producer

Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic 服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。
RocketMQ提供三种发送方式:

同步:在广泛的场景中使用可靠的同步传输,如重要的通知信息、短信通知、短信营销系统等。
异步:异步发送通常用于响应时间敏感的业务场景,发送出去即刻返回,利用回调做后续处理。
一次性:一次性发送用于需要中等可靠性的情况,如日志收集,发送出去即完成,不用等待发送结果,回调等等。

生产者端的负载均衡

生产者发送时,会自动轮询当前所有可发送的broker,一条消息发送成功,下次换另外一个broker发送,以达到消息平均落到所有的broker上。

Consumer

Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer获取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,消费者在向Master拉取消息时,Master服务器会根据拉取偏移量与最大偏移量的距离(判断是否读老消息,产生读I/O),以及从服务器是否可读等因素建议下一次是从Master还是Slave拉取。

消费者端的负载均衡

先讨论消费者的消费模式,消费者有两种模式消费:集群消费,广播消费。

广播消费:每个消费者消费Topic下的所有队列。
集群消费:一个topic可以由同一个ID下所有消费者分担消费。
具体例子:假如TopicA有6个队列,某个消费者ID起了2个消费者实例,那么每个消费者负责消费3个队列。如果再增加一个消费者ID相同消费者实例,即当前共有3个消费者同时消费6个队列,那每个消费者负责2个队列的消费。

消费者端的负载均衡,就是集群消费模式下,同一个ID的所有消费者实例平均消费该Topic的所有队列。

消费者从用户角度来看有两种类型:

PullConsumer:主动从brokers处拉取消息。Consumer消费的一种类型,应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息,应用就会启动消费过程。
PushConsumer:Consumer消费的一种类型,该模式下Broker收到数据后会主动推送给消费端,该消费模式一般实时性较高。

补充一些概念

Topic:主题,表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。Topic与生产者和消费者都是非常松散的关系,一个topic可以有0个或者1个或者多个生产者向其发送消息,换句话说,一个生产者可以同时向不同和topic发送消息。从消费者的解度来说,一个topic可能被0个或者一个或者多个消费组订阅,类似的,一个消费组可以订阅一个或者多个主题只要这个消费组的实例保持他们的订阅一致。

Message:消息消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。。

Message Queue:消息队列,一个主题被化分为一个或者多个子主题(sub-topics),“消息队列”.

Tag:标签,为消息设置的标志,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。使用tag,同一业务模块不同目的的messages就可以用相同topic不同tag来标识。Tags有益于保持你的代码干净而条理清晰,同时促进使用RocketMQ提供的查询系统的效率。Topic:主题,是生产者发送的消息和消费者拉取的消息的归类。Topic与生产者和消费者都是非常松散的关系,一个topic可以有0个或者1个或者多个生产者向其发送消息,换句话说,一个生产者可以同时向不同和topic发送消息。从消费者的解度来说,一个topic可能被0个或者一个或者多个消费组订阅,类似的,一个消费组可以订阅一个或者多个主题只要这个消费组的实例保持他们的订阅一致。

Message Order:当使用DefaultMQPushConsumer时,你需要确定消费消息的方式:

Orderly:顺序地消费消息即表示消费的消息顺序同生产者发送的顺序一致。
Concurrently:并行消费。指定此方式消费,信息消费的最大并行数量仅受限于每个消费者客户端指定的线程池。

Consumer Group:消费组,同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。要注意的是,消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ 支持两种消息模式:集群消费(Clustering)和广播消费(Broadcasting)。
Producer Group:生产者组,同一类Producer的集合,这类Producer发送同一类消息且发送逻辑一致。如果发送的是事务消息且原始生产者在发送之后崩溃,则Broker服务器会联系同一生产者组的其他生产者实例以提交或回溯消费。

上面的这些我主要参考了 RocketMQ 的 GitHub 介绍和一些优秀网文的介绍,侵权请联系我删除。

请我喝杯咖啡