MQ中间件介绍,请参考分布式理论相关介绍。
传送门:https://blog.csdn.net/u010355502/article/details/132307054
官网:https://rocketmq.apache.org/
RocketMQ是阿里巴巴开源的一个消息中间件,在阿里内部历经了双十一等很多高并发场景的考验,能够处理亿万级别的消息。2016年开源后捐赠给Apache,现在是Apache的一个顶级项目。
早期阿里使用ActiveMQ,但是,当消息开始逐渐增多后,ActiveMQ的IO性能很快达到了瓶颈。于是,阿里开始关注Kafka。但是Kafka是针对日志收集场景设计的,他的高级功能并不是很贴合阿里的业务场景。尤其当他的Topic过多时,导致Partition文件也会过多,这就会加大文件索引的耗时,会严重影响IO性能。于是阿里才决定自研中间件,最早叫做MetaQ,后来改名成为RocketMQ。最早他所希望解决的最大问题就是多Topic下的IO性能压力。但是产品在阿里内部的不断改进,RocketMQ开始体现出一些不一样的优势。
关于各个厂商的中间件比较,请参考如下文档对应MQ中间件部分。
传送门:https://blog.csdn.net/u010355502/article/details/132307054
RocketMQ安装及简单使用,请参考如下文章。
传送门:https://blog.csdn.net/u010355502/article/details/132493393
当前最新的版本是5.x,这是一个着眼于云原生的新版本,给RocketMQ带来了非常多很亮眼的新特性。但是目前来看,企业中用得还比较少。因此,我们这里采用的还是更为稳定的4.9.5
版本。
说明:
在2020年下半年,RocketMQ新推出了5.0的大版本,这对于RocketMQ来说,是一个里程碑式的大版本。在这个大版本中,RocketMQ对整体功能做了一次大的升级。增加了很多非常有用的新特性,也对已有功能重新做了升级。
比如在具体功能方面,在4.x版本中,对于定时消息,只能设定几个固定的延迟级别,而5.0版本中,已经可以指定具体的发送时间了。在客户端语言方面,4.x版本,RocketMQ原生只支持基于Netty框架的Java客户端。而在5.0版本中,增加了对Grpc协议的支持,这基本上就解除了对客户端语言的限制。在服务端架构方面,4.x版本只支持固定角色的普通集群和可以动态切换角色的Dledger集群,而在5.0版本中,增加了Dledger Controller混合集群模式,即可以混合使用Dledger的集群机制以及 Broker 本地的文件管理机制。
但是功能强大,同时也意味着问题会很多。所以目前来看,企业中直接用新版本的还比较少。小部分使用新版本的企业,也大都是使用内部的改造优化版本。
之前我们用一台Linux服务器,快速搭建起了一整套RocketMQ的服务。但是很明显,这样搭建的服务是无法放到生产环境上去用的。一旦nameserver服务或者broker服务出现了问题,整个RocketMQ就无法正常工作。而且更严重的是,如果服务器出现了问题,比如磁盘坏了,那么存储在磁盘上的数据就会丢失。这时RocketMQ暂存到磁盘上的消息也会跟着丢失,这个问题就非常严重了。因此,我们需要搭建一个分布式的RocketMQ服务集群,来防止单点故障问题。
RocketMQ的分布式集群基于主从架构搭建。在多个服务器组成的集群中,指定一部分节点作为Master节点,负责响应客户端的请求。指令另一部分节点作为Slave节点,负责备份Master节点上的数据,这样,当Master节点出现故障时,在Slave节点上可以保留有数据备份,至少保证数据不会丢失。
具体安装请参考如下文章。
传送门:https://blog.csdn.net/u010355502/article/details/132493393
主从架构的RocketMQ集群,由于给每个broker服务配置了一个或多个slave备份服务,可以保证当broker服务出现问题时,broker上的消息不会丢失。但是,这种主从架构的集群却也有一个不足的地方,那就是不具备服务高可用。
这里所说的服务高可用,并不是并不是指整个RocketMQ集群就不能对外提供服务了,而是指集群中的消息就不完整了。实际上,当RocketMQ集群中的broker宕机后,整个集群会自动进行broker状态感知。后续客户端的各种请求,依然可以转发到其他正常的broker上。只不过,原本保存在当前broker上的消息,就无法正常读取了,需要等到当前broker服务重启后,才能重新被消息消费者读取。
当一个broker上的服务宕机后,我们可以从对应的slave服务上找到broker上所有的消息。但是很可惜,主从架构中各个服务的角色都是固定了的,slave服务虽然拥有全部的数据,但是它没办法升级成为master服务去响应客户端的请求,依然只是傻傻等待master服务重启后,继续做它的数据备份工作。
这时,我们自然就希望这个slave服务可以升级成为master服务,继续响应客户端的各种请求,这样整个集群的消息服务就不会有任何中断。而RocketMQ提供的Dledger集群,就是具备角色自动转换功能的高可用集群。
在Dledger集群中,就不再单独指定各个broker的服务,而是由这些broker服务自行进行选举,产生一个Leader角色的服务,响应客户端的各种请求。而其他的broker服务,就作为Follower角色,负责对Leader上的数据进行备份。当然,Follower所要负责的事情,比主从架构中的SLAVE角色会要复杂一点,因为这种节点选举是在后端不断进行的,他们需要随时做好升级成Leader的准备。
Dledger集群的选举是通过Raft协议进行的,Raft协议是一种多数同意机制。也就是每次选举需要有集群中超过半数的节点确认,才能形成整个集群的共同决定。同时,这也意味着在Dledger集群中,只要有超过半数的节点能够正常工作,那么整个集群就能正常工作。因此,在部署Dledger集群时,通常都是部署奇数台服务,这样可以让集群的容错性达到最大。
具体安装请参考如下文章。
传送门:https://blog.csdn.net/u010355502/article/details/132493393
Dledger集群机制是RocketMQ自4.5版本开始支持的一个重要特性。他其实是由OpenMessage组织带入RocketMQ的一个系列框架。他是一个为高可用、高性能、高可靠的分布式存储系统提供基础支持的组件。他做的事情主要有两个,一是在集群中选举产生master节点。RocketMQ集群需要用这个master节点响应客户端的各种请求。二是在各种复杂的分布式场景下,保证CommitLog日志文件在集群中的强一致性。
DLedger集群通过raft协议来保证数据的一致性。在集群中,每个节点都维护一个相同的数据副本,以确保当某个节点出现故障时,数据不会丢失。
DLedger集群具有很高的容错性。即使集群中的部分节点发生故障,只要集群中有大多数节点(即超过半数)仍在正常工作,整个集群将继续提供服务。
DLedger集群通过负载均衡和热备份等机制,确保在节点故障时能够快速切换到其他正常节点,提高整个系统的可用性。
DLedger集群提供分布式锁功能,可以解决分布式系统中的资源争用问题,实现跨节点的资源同步。
DLedger集群通过使用Raft一致性协议,确保在多个副本节点之间同步数据,保证数据的强一致性。
DLedger集群支持水平扩展,可以通过增加节点来提高系统的吞吐量和存储能力,以满足不断增长的业务需求。
DLedger集群提供了一系列管理和监控功能,使运维人员可以更方便地掌握系统的运行状况,及时发现和解决问题。
总之,RocketMQ的DLedger集群具有高可用、高性能、高可靠等特点,为分布式消息队列系统提供了坚实的基础。
RocketMQ强一致性核心就是Raft协议。这是一种强大的分布式选举算法,其核心是只要集群中超过半数的节点作出的共同决议,就认为是集群最终的共同决议。
Raft协议通过投票机制保持数据一致性,解决分布式系统的中脑裂问题。
关于脑裂问题,这是在集群选举过程中一个出现概率不高,但是让很多人头疼的问题。在分布式集群内,有可能会由于网络波动或者其他一些不稳定因素,造成集群内节点之间短时间通信不畅通。这时就容易在集群内形成多个包含多个节点的小集合。这些集合就会独立进行选举,各自产生新的Master节点。当网络恢复畅通后,集群中就有了多个Master节点。当集群中出现多个Master节点后,其他节点就不知道要听从谁的指令了,从而造成集群整体工作瘫痪。也就是俗话说的“一山不容二虎”。脑裂问题在以Zookeeper为代表的早前一代分布式一致性产品中,是一个非常头疼的问题。而Raft协议对于脑裂问题,会采用随机休眠的机制,彻底解决脑裂问题。RocketMQ是Raft协议的一个重要的成功示例。Kafka也在之后基于Raft协议,自行实现了Kraft集群机制。
Dledger集群如何防止集群脑裂问题?
DLedger集群通过使用Raft协议来防止集群脑裂(split-brain)问题。脑裂问题是指在分布式系统中,由于网络分区或其他原因导致集群被分割成两个或多个子集群,各自独立运行且无法感知到其他子集群的存在。这可能导致数据不一致和错误决策。Raft协议采用了一系列措施来避免脑裂问题的发生:
Raft协议的基础是选举出一个领导者(Leader),其他节点(Follower)都从领导者获取数据。选举过程要求候选人必须获得集群中大多数节点的支持才能成为领导者。这确保了集群中只能有一个领导者,从而避免了脑裂问题。
Raft协议为每个选举周期设置了一个递增的任期编号。任期编号用于标识当前的领导者,确保旧的领导者不会再次被选为领导者。如果一个节点发现自己的任期小于其他节点,那么它会停止当前的工作并更新自己的任期。
领导者会定期向其他节点发送心跳消息,以保持与Follower节点的连接。当一个节点长时间未收到领导者的心跳时,它会认为当前领导者失效,并启动新一轮选举。这确保了当领导者出现故障时,系统能够快速地选出新的领导者。
领导者负责将数据更新(日志条目)复制到其他节点。Follower节点只有在收到领导者的日志条目并将其写入本地日志后,才会响应客户端的请求。这确保了在发生脑裂情况下,不会出现多个节点试图同时修改同一份数据的情况。
通过以上措施,DLedger集群利用Raft协议避免了脑裂问题的发生,保证了系统的高可用性和数据一致性。
Dledger集群由于会接管RocketMQ原生的文件写入,所以,Dledger集群的文件写入速度比RocketMQ的原生写入方式是要慢一点的。这会对RocketMQ的性能产生一些影响。所以,当前版本的Dledger集群在企业中用得并不是太多。5.0版本对Dledger集群抽出了一种Dledger Controller模式,也就是只用Dledger集群的选举功能,而不用他的Commit文件写入功能,这样性能可以得到一定的提升。
在我们之前的实验过程中,你会发现,nameServer不依赖于任何其他的服务,自己独立就能启动。并且,不管是broker还是客户端,都需要明确指定nameServer的服务地址。以一台电脑为例,nameServer可以理解为是整个RocketMQ的CPU,整个RocketMQ集群都要在CPU的协调下才能正常工作。
从之前的实验过程中你会发现,broker是RocketMQ中最为娇贵的一个组件。RockeMQ提供了各种各样的重要设计来保护broker的安全。同时broker也是RocketMQ中配置最为繁琐的部分。同样以电脑为例,broker就是整个RocketMQ中的硬盘、显卡这一类的核心硬件。RocketMQ最核心的消息存储、传递、查询等功能都要由broker提供。
Client包括消息生产者和消息消费者。同样以电脑为例,Client可以认为是RocketMQ中的键盘、鼠标、显示器这类的输入输出设备。鼠标、键盘输入的数据需要传输到硬盘、显卡等硬件才能进行处理。但是键盘、鼠标是不能直接将数据输入到硬盘、显卡的,这就需要CPU进行协调。通过CPU,鼠标、键盘就可以将输入的数据最终传输到核心的硬件设备中。经过硬件设备处理完成后,再通过CPU协调,显示器这样的输出设备就能最终从核心硬件设备中获取到输出的数据。
通过测试程序往RocketMQ发送消息:
bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
生产消息日志举例:
SendResult [sendStatus=SEND_OK, msgId=7F000001426E28A418FC6545DFD803E7, offsetMsgId=C0A8E88100002A9F0000000000B4F6E5, messageQueue=MessageQueue [topic=TopicTest, brokerName=broker-a, queueId=2], queueOffset=124]
在Dashboard中可以看到有生成主题TopicTest:
这个TopicTest就是我们之前运行的Producer创建的主题。点击“状态”按钮,可以看到TopicTest上的消息分布:
从这里可以看到,TopicTest这个话题下,分配了八个MessageQueue。这里的MessageQueue就是一个典型的具有FIFO(先进先出)特性的消息集合。这八个MessageQueue均匀的分布在了集群中的两个Broker服务上。每个MesasgeQueue都记录了一个最小位点和最大位点。这里的位点代表每个MessageQueue上存储的消息的索引,也称为offset(偏移量)。每一条新记录的消息,都按照当前最大位点往后分配一个新的位点。这个位点就记录了这一条消息的存储位置。
从DashBoard就能看到,每一个MessageQueue,当前都记录了125条消息。也就是说,我们之前使用Producer示例往RocketMQ中发送的一千条消息,就被均匀的分配到了这八个MessageQueue上。
这时,再回头来看之前日志中打印的SendResult的信息。日志中的MessageQueue就代表这一条消息存在哪个队列上了。而queueOffset就表示这条消息记录在MessageQueue的哪个位置。
启动一个消费者:
tools.sh org.apache.rocketmq.example.quickstart.Consumer
消费消息日志举例:
ConsumeMessageThread_3 Receive New Messages: [MessageExt [brokerName=broker-b, queueId=0, storeSize=194, queueOffset=95, sysFlag=0, bornTimestamp=1666252677571, bornHost=/192.168.232.128:38414, storeTimestamp=1666252678510, storeHost=/192.168.232.130:10911, msgId=C0A8E88200002A9F0000000000B4ADD2, commitLogOffset=11840978, bodyCRC=634652396, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='TopicTest', flag=0, properties={MIN_OFFSET=0, MAX_OFFSET=125, CONSUME_START_TIME=1666257428525, UNIQ_KEY=7F000001426E28A418FC6545DDC302F9, CLUSTER=rocketmq-cluster, TAGS=TagA}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 55, 54, 49], transactionId='null'}]]
其中queueOffset属性就表示这一条消息在MessageQueue上的存储位点。通过记录每一个消息的Offset偏移量,RocketMQ就可以快速的定位到这一条消息具体的存储位置,继而正确读取到消息的内容。
在DashBoard的“主题”页面,选择对应主题后的“CONSUMER管理”功能,就能看到消费者的消费情况。
从这里可以看到,刚才的Comsumer示例启动了一个叫做please_rename_unique_group_name_4的消费者组。然后这个消费者从八个队列中都消费了数据。后面的代理者位点记录的是当前MessageQueue上记录的最大消息偏移量。而消费者位点记录的是当前消费者组在MessageQueue上消费的最大消息偏移量。其中的差值就表示当前消费者组没有处理完的消息。
并且,从这里还可以看出,RocketMQ记录消费者的消费进度时,都是以“订阅组”为单位的。我们也可以使用上一章节的示例,自己另外定义一个新的消费者组来消费TopicTest上的消息。这时,RocketMQ就会单独记录新消费者组的消费进度。而新的消费者组,也能消费到TopicTest下的所有消息。
消费方式总结:
生产者和消费者都可以指定一个Topic发送消息或者拉取消息。而Topic是一个逻辑概念。Topic中的消息会分布在后面多个MessageQueue当中。这些MessageQueue会分布到一个或者多个broker中。
在RocketMQ的这个消息模型当中,最为核心的就是Topic。对于客户端,Topic代表了一类有相同业务规则的消息。对于Broker,Topic则代表了系统中一系列存储消息的资源。所以,RocketMQ对于Topic是需要做严格管理的。如果任由客户端随意创建Topic,那么服务端的资源管理压力就会非常大。默认情况下,Topic都需要由管理员在RocketMQ的服务端手动进行创建,然后才能给客户端使用的。而我们之前在broker.conf中手动添加的autoCreateTopic=true,就是表示可以由客户端自行创建Topic。这种配置方式显然只适用于测试环境,在生产环境不建议打开这个配置项。如果需要创建 Topic,可以交由运维人员提前创建 Topic。
而对于业务来说,最为重要的就是消息Message了。生产者发送到某一个Topic下的消息,最终会保存在Topic下的某一个MessageQueue中。而消费者来消费消息时,RocketMQ会在Broker端给每个消费者组记录一个消息的消费位点Offset。通过Offset控制每个消费者组的消息处理进度。这样,每一条消息,在一个消费者组当中只被处理一次。