MQ

一:MQ简介

      1.MQ要解决的问题:将同步通信改为异步通信。

         同步通信:客户端发出调用后,必须等待服务器对象完成处理并返回结果后才能继续

                           执行;客户端和服务器端对象的生命周期紧密耦合,客户端进程和服务器

                           端进程都必须正常运行;点对点通信:客户端的一次调用只发送给某个单

                           独的目标对象。

                                  面向消息的中间件较好的解决了同步通信的问题,发送者将消息发送

                           给消息服务器,消息服务器将消息存放在若干队列中,在合适的时候在将

                           消息发送给接收者,这种模式下发送和接收是异步的,发送者无需等待,

                           二者的生命周期未必相同,发送消息的时候接收者不一定运行,接收消息

                           的时候发送者不一定运行。一对多通信,对于一个消息可以有多个接收者。

                         (此段大概看一下就行,知道MQ有个什么好处)。

      2.JMS两种的两种通信模型:Point to Point和Publish/Subscribe各自对应的目标对象

       (1)Point to Point:点对点的消息模型。在点对点通信模式中,应用程序由消息队列,

                                         发送方,接收方组成。每个消息都被发送到一个特定的队列,接

                                         收者从队列中获取消息。队列保留着消息,直到他们被消费或超

                                         时。

                特点:

             (a)每个消息只能有一个消费者。

             (b)发送者和接收者在时间上是没有时间的约束,也就是说发送者在发送完消息

                      之后,不管接收者有没有接受消息,都不会影响发送方再次发送消息到消息

                      队列中。发送方不管是否在发送消息,接收方都可以从消息队列中取到消息

                    (The receiver can fetch message whether it is running or not when the

                       sender sends the message)

            (c)接收方在接收完消息之后,需要向消息队列应答成功。

                    ​

     (2)  Publish/Subscribe:发布订阅的消息模型,对应的是Topic。发布者发布一个消

                                                 息,该消息通过topic传递给所有的客户端。该模式下,发

                                                 布者与订阅者都是匿名的,即发布者与订阅者都不知道对

                                                 方是谁。并且可以动态的发布与订阅Topic。Topic主要用于

                                                 保存和传递消息,且会一直保存消息直到消息被传递给客户

                                                 端。

                特点:

               (1)一个消息可以传递个多个订阅者(即:一个消息可以有多个接受方)

               (2)发布者与订阅者具有时间约束,针对某个主题(Topic)的订阅者,它必须

                        创建一个订阅者之后,才能消费发布者的消息,而且为了消费消息,订阅

                        者必须保持运行的状态。

              (3)为了缓和严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这

                       样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。

​                      

     3.JMS定义了5中不同的消息正文格式,以及调用的消息类型,允许你发送并接收一些不

        同形式的数据。

      (1)StreamMessage:java原始的数据流

      (2)MapMessage:一套名称-值对

      (3)TextMessage:一个字符串对象

      (4)ObjectMessage:一个序列化的java对象

      (5)BytesMessage:一个未解释的字节数据流

     4.使用MQ应该注意的地方

      (1)MQ使用的时候不能传大数据,比如传几M几十M的可以但是到了几百M的就不行

               了。

二:各种MQ产品介绍

       MQ各种产品的比较:

 

       1.ActiveMQ

          ActiveMQ支持的5种协议:cp、AMQP、MQTT、OpenWire、Stomp、ws。

          ActiveMQ的时候需要自己写一个系统实现分流和排队

          分流:他来确定某几个消息发送给哪些个消费者

          排队:例如把消息发送个C1,C1消费完后在把消息发送给C2,C2消费完后在把消

                     息发送给C3。

          ActiveMQ高可用,差用zookeeper做协调。

三  RocketMQ

     ”(037)RocketMQ实战案例讲解“这个文件夹下的视频没看,有空补了。

     1.简介

              RcoketMQ(matamq 3.x) 是一款低延迟、高可靠、可伸缩、易于使

        用的分布式的消息中间件(原生就是支持分布式的,没有单节点)。采用

        java语言实现,它并不支持JMS(java消息服务)规范,但参考了JMS规

        范和kafak等的思想,RocketMq只支持一种模式叫发布订阅在1.x和2.x采

        用ZK做协调,但是从3.x开始使用NameServer做协调,RocketMQ具有以

        下特性:

      (a)支持发布/订阅(Pub/Sub)和点对点(P2P)消息模型。

      (b)支持顺序消费,但需要在一个队列中,比如订单号相同的订单放在同

               一个队列里,例如订单创建微服务、库存扣减微服务、支付微服务、

               发货微服务,它们都是需要有顺序的,这些可以放在一个队列里。

      (c)支持拉(pull)和推(push)两种消息模式。

      (d)单一队列百万消息的堆积能力。

      (e)可以支持上万个队列(一个topic下还是所有?听老师讲课的意思是一

               个topic下面)。如果时ActiveMQ,如果队列开到上千个,它就会很

               慢(至于怎么优化,能不能优化,这就是以后研究的问题了,视频只

               讲了这句)

      (f)支持消息失败重试机制

      (g)未实现任何规范,包括JMS规范

      (h)支持多种消息协议,如 JMS、MQTT等。

      (i)分布式高可用的部署架构,满足至少一次消息传递语义。

      (j)提供 docker 镜像用于隔离测试和云集群部署。

      (k)提供配置、指标和监控等功能丰富的 Dashboard。

        RocketMQ有开源版本和非开源版本。

  2.RocketMQ设计理念和设计目标

      (1)设计理念

                      RocketMQ 设计基于主题的发布与订阅模式, 其核心功能包括消

               息发送、消息存储( Broker )、消息消费,整体设计追求简单与性能

               第一,主要体现在如下三个方面。

                             首先, 简单高效的NameServer。

                                    阿里摒弃了Zookeeper使用NameServer来实现元数据管

                             理(Topic 路由信息等)。把NameServer的设计的非常简单,

                             是因为从阿里自身需求出发,Topic 路由信息无须在集群之间

                             保持强一致,追求最终一致性,并且能容忍分钟级的不一致,

                             基于这种情况,RocketMQ 的NameServer 集群之间互不通

                             信,极大地降低了NameServer 实现的复杂程度, 对网络的

                             要求也降低了不少, 而且性能相比较Zookeeper有了极大的

                             提升。

                             其次,是高效的IO 存储机制。

                                    RocketMQ 追求消息发送的高吞吐量, RocketMQ 的消

                             息存储文件设计成文件组的概念,组内单个文件大小固定,

                             方便引人内存映射机制,所有主题的消息存储基于顺序写,

                             极大地提供了消息写性能, 同时为了兼顾消息消费与消息查

                             找,引入了消息消费队列文件与索引文件(CommitLog和

                             ConsumeQueue)。

                             最后是容忍RocketMQ存在设计缺陷。

                                    适当将某些工作下放给RocketMQ 使用者。例如,消息

                             中间件的实现者经常会遇到一个难题:如何保证消息一定能

                             被消息消费者消费,并且保证只消费一次?RocketMQ 的设

                             计者给出的解决办法是不解决这个难题,而是退而求其次,

                             只保证消息被消费者消费,但设计上允许消息被重复消费。

                             这样极大地简化了消息中间件的内核,使得实现消息发送高

                             可用变得简单与高效。

      (2)设计目标

               书上列了11条,我这里只挑重要的列一下

                      顺序消费

                             所谓顺序消息,就是消息消费者按照消息达到消息存储服务

                             器的顺序消费。RocketMQ可以严格保证消息有序。

                      消息过滤

                             消息过滤是指在消息消费时,消息消费者可以对同一Topic

                             的消息按照规则只消费自己需要的消息。RocketMQ 消息过

                             滤支持在服务端与消费端的消息过滤机制。

                             * 消息在Broker端过滤。

                                       Broker 只将消息消费者需要的消息发送给消息消费者。

                             * 消息在消息消费端过滤。

                                       消息过滤方式完全由消息消费者自定义,但缺点是有

                                       很多无用的消息会从Broker 传输到消费端。

                             * 消息存储

                                       消息中间件的一个核心实现是消息的存储。对消息存储

                                       一般有如下两个维度的考量:消息堆积能力和消息存储

                                       性能。

                                              RocketMQ追求消息存储的高性能,引人内存映射

                                       机制,所有主题的消息顺序存储在同一个文件中。同时

                                       为了避免消息无限在消息存储服务器中累积,引入了消

                                       息文件过期机制与文件存储空间报警机制。

                      消息堆积

                             消息中间件的主要功能是异步解锢,必须具备应对前端的数

                             据洪峰,提高后端系统的可用性,必然要求消息中间件具备一

                             定的消息堆积能力。RocketMQ 消息存储使用磁盘文件(内存

                             映射机制),并且在物理布局上为多个大小相等的文件组成逻

                             辑文件组,可以无限循环使用。RocketMQ 消息存储文件并不

                             是永久存储在消息服务器端,而是提供了过期机制,默认保留

                             3 天。

                     消息高可用性

                                    通常影响消息可靠性的有以下几种情况。

                                    1)Broker 正常关机。

                                    2)Broker 异常Crash 。

                                    3)OS Crash 。

                                    4)机器断电,但是能立即恢复供电情况。

                                    5)机器无法开机(可能是CPU 、主板、内存等关键设备

                                         损坏) 。

                                    6)磁盘设备损坏。

                                                 针对上述情况,情况1~4 的RocketMQ 在同步刷

                                          盘机制下可以确保不丢失消息,在异步刷盘模式下,

                                          会丢失少量消息。情况5-6 属于单点故障,一旦发生,

                                          该节点上的消息全部丢失,如果开启了异步复制机制,

                                          RoketMQ 能保证只丢失少量消息,RocketMQ 在后

                                          续版本中将引人双写机制,以满足消息可靠性要求极

                                          高的场合。

                     定时消息

                     消息重试

                                    消息重试是指消息在消费时,如果发送异常,消息中间

                                    件需要支持消息重新投递,RocketMQ 支持消息重试机

                                    制。

     2.RocketMq集群

                RocketMQ集群中包含4个模块:NameServer,Broker,Producer

     (Producer group),Consumer(Consumer Group)。原本RocketMQ只

       需要Broker、Producer、Consumer就可以完成消息中间件的功能,但是因

       为Broker集群部署,面临着单点故障、可伸缩性,为了能让生产者和消费

       者感知Broker的变化,所以就引入了注册中心Namesrv,用于注册和发现

       Broker。这有点像dubbo的Zookeeper,当然老版本使用的注册中心是

       Zookeeper(1.x和2.x使用的是Zookeeper,3.x开始使用的是NameServer)。

       Broker和NameServer可以在同一个机器上也可以在不同的机器上。

     (1)RocketMQ的组件介绍

            (a)Nameserver

                   (i)为什么要使用NameServer

                                  为了避免消息服务器的单点故障导致的整个系统瘫痪,通

                           常会部署多台消息服务器共同承担消息的存储。那消息生产者

                           如何知道消息要发往哪台消息服务器呢?如果某一台消息服务

                           器若机了,那么生产者如何在不重启服务的情况下感知呢?答

                           案是依靠NameServer。如下图:

                             Broker的注册中心。

                                     Namesrv的职责:

                                             管理Broker。包含以下内容

                                             - 管理路由信息,即Topic跟Broker的对应关系

                                             - 接受来自Broker集群发送的注册。

                                             - 供心跳机制来检查我们的Broker是否还存活。

                                             每一个NameServer都存储有路由信息和队列信息,

                                             提供给Producer和Consumer查询。在RocketMQ的

                                             老版本中,使用的是Zookeeper。

                                             注:Namesrv没有持久化功能

                                     Namesrv特点:

                                                    多个Namesrv之间相互没有通信,单台Namesrv

                                             宕机不影响其他Namesrv与集群。即使整个Namesrv

                                             集群宕机(因为Broker本身不需要强一致性和最终一

                                             致性),已经正常工作的Producer,Consumer,

                                             Broker仍然能正常工作,但新起的Producer,

                                             Consumer,Broker就无法工作。Namesrv压力不会

                                             太大,平时主要开销是在维持心跳和提供topic-Broker

                                             的关系数据。但有一点需要注意,Broker向Namesr发

                                             心跳时,会带上当前自己所负责的所有Topic信息,如

                                             果Topic个数太多(万级别),会导致一次心跳中,就

                                             Topic的数据就几十M,网络情况差的话,网络传输失

                                             败,心跳失败,导致Namesrv误认为Broker心跳失败。

                                             在1.x和2.x中是使用zookeeper来做协调了,到了3.x

                                             使用了自己实现的组建namesrv。activemq是通过

                                             zookeeper来实现集群和高可用的。namesrv比

                                             zookeeper更加的轻量级并且性能也好。集群中

                                             rocketmq心跳和同步消息就靠Namesrv。activemq的

                                             这种机制靠的是zookeeper。

                                     NameServer的路由注册、故障剔除

                                             -  路由注册

                                                        RocketMQ路由注册是通过Broker 与Name Server

                                                        的心跳功能实现的。Broker 启动时向集群中所有的

                                                        NameServer 发送心跳语句,每隔30s 向集群中所有

                                                        NameServer 发送心跳包, NameServer 收到Broker

                                                        心跳包时会更新brokerLiveTable 缓存中

                                                        BrokerLivelnfo 的lastUpdateTimestamp ,然后

                                                        NameServer 每隔10s 扫描brokerLiveTable ,如果

                                                        连续120s没有收到心跳包, NameServer将移除该

                                                        Broker 的路由信息同时关闭Socket 连接。

                                                        注:心跳包包含了Broker Id 、Broker 地址、Broker

                                                               名称、Broker 所属集群名称、Broker 关联的

                                                               FilterServer 列表。   

                                             -  路由删除

                                                       Name Server会每隔10s扫描brokerLiveTable状态表,

                                                       如果BrokerLive 的lastUpdateTimestamp 的时间戳距当

                                                       前时间超过120s ,则认为Broker 失效,移除该Broker

                                                       并关闭与Broker 连接,并同时更新topicQueueTable 、

                                                       brokerAddrTable 、brokerLiveTable 、

                                                       filterServerTable 。      

                                                             RocktMQ 有两个触发点来触发路由删除。

                                                             *  NameServer 定时扫描brokerLiveTable 检测上

                                                                次心跳包与当前系统时间的时间差,如果时间戳

                                                                大于120s ,则需要移除该Broker 信息。

                                                             *  Broker 在正常被关闭的情况下,会执行

                                                                unregisterBroker 指令。        

                                             -  路由发现

                                                       RocketMQ 路由发现是非实时的,当Topic 路由出现

                                                       变化后, NameServer 不主动推送给客户端, 而是由

                                                       客户端定时拉取主题最新的路由。    

            (b)Broker

                             集群最核心模块,主要负责Topic消息存储、消费者的消费

                     位点管理(消费进度)。

            (c)Producer(消息发送的高可用,靠重试和Broker规避

                             消息生产者,每个生产者都有一个ID(编号),多个生产者实

                     例可以共用同一个ID。同一个ID下所有实例组成一个生产者集群。

                     可以对多个Topic去发送消息,这种设计非常的灵活,而且可以

                     通过Tag去过滤一些简单的过滤,通常易经满足我们90%的需求

                     了,如果更复杂过滤的场景下,可能会使用filtersv组件。

                     有三种类型的Producer:

                             扩展,消息发送分同步、异步、单向

                                           同步发送:发送者向MQ 执行发送消息API 时,同

                                                             步等待, 直到消息服务器返回发送结果

                                           异步发送:发送者向MQ 执行发送消息API 时,指

                                                             定消息发送成功后的回掉函数,然后调

                                                             用消息发送API 后,立即返回,消息发

                                                             送者线程不阻塞,直到运行结束,消息

                                                             发送成功或失败的回调任务在一个新的

                                                             线程中执行。

                                          单向发送:消息发送者向MQ 执行发送消息API 时,

                                                            直接返回,不等待消息服务器的结果,

                                                            也不注册回调函数,简单地说,就是只

                                                            管发,不在乎消息是否成功存储在消息

                                                            服务器上。

                   (i)NormalProducer

                           产生的消息,消费者随机消费

                   (ii)OrderProducer

                                   产生的消息,消费者必须按顺序消费,顺序是指消费完

                            第一条才能消费第二条,该顺序是必须是同一队列,例如订

                            单创建、订单付款、订单完成这种业务上有现后顺序的可以

                            使用顺序消费,理论上RocketMQ单机支持上万队列,但是

                            有面试官说顺序消费的性能不好,需要有空去研磨。

                            顺序消费的原理:

                                   *  同一队列

                                   *  该队列只有一个消费者,该消费者注册消息监听器为

                                       MessageListenerOrderly。

                                   关于顺序消费的性能

                                          到这里可能有的人会问,一个队列只有一个消费者,

                                          那性能岂不是很低?关于这个情况,rocketmq的解决

                                          方法是,虽然同一个队列不能并行消费,但是可以并行

                                          消费不同的队列。就是上面说的同时多笔订单之间

                                          又是可以并行消费。

                                       顺序消费的原理并不完善,有空去查找一下资料。     

                                       顺序消费有普通顺序消费和严格顺序消费,有空完善一

                                       下这个理论。 

                            注意2:

                               是消费完第一条,而不是第一条发送到Consumer后就开始

                               消费第二条,否则有可能因为第二条执行的时间端而导致第

                               二条先于第一条消费完Consumer端可以多线程的去消费,

                               线程不用自己去实现,在配置参数中指定线程池的线程数就

                               可以了,具体例子看视频”20160316001.mp4“或是上网找。

​                                

                   (iii)TransactionProducer

                                事务消息有两种方式:

                                * 不太好,没记,有兴趣自己去翻视频(20160316004.mp4)

                                * 先看图,Bob给Smith转账

​                               

                                RocketMQ第一阶段发送Prepared消息时,会拿到消息的

                                地址,第二阶段执行本地事物,第三阶段通过第一阶段拿

                                到的地址去访问消息,并修改消息的状态。细心的你可能

                                又发现问题了,如果确认消息发送失败了怎么办?RocketMQ

                                会定期扫描消息集群中的事物消息,如果发现了Prepared

                                消息,它会向消息发送端(生产者)确认(老师说

                                RocketMQ3.0.0之后的版本把回查的机制删除了),Bob

                                的钱到底是减了还是没减呢?如果减了是回滚还是继续发

                                送确认消息呢?RocketMQ会根据发送端设置的策略来决

                                定是回滚还是继续发送确认消息。这样就保证了消息发送

                                与本地事务同时成功或同时失败。

                                时序图如下:

​                              

                                     重ConsumeQueue和CommitLog文件角度来解释其

                                      实现事务消息的原理:

                                               Prepared的消息不写入ConsumeQueue,只有

                                               comiit的消息才写入ConsumeQueue。

                                       代码例子翻视频(20160316004.mp4)、资料部分、

                                       或网上自己找。

            (d)Producer Group

                            一类 Producer 的集合名称,这类 Producer 通常发送一类

                     消息,且发送逻辑一致。在使用Producer的时候需要指定一个

                     Group,通常一个应用里只有一个Group,只有指定了Group才

                     可以实现事务。

                     注:无论是Producer Group还是Consumer Group,只能允许

                            一个Master和若干个Slaver。

            (e)Consumer

                             消息消费者,用于消费消息。天然就支持负载均衡的

                     消费。可以设置消费线程数。分为PushConsumer和

                     PullConsumer。

                     Push Consumer:Consumer 的一种,应用通常向 Consumer

                                                   对象注册一个Listener 接口,一旦收到消息,

                                                   Consumer 对象立刻回调Listener 接口方法。

                                                   Listener接口中,有用户提供消息处理的

                                                   CallBack,有未曾消费的消息时,会主动回

                                                   调这个CallBack来处理消息。PushConsumer

                                                   的底层实现也是有一个长连接主动去broker上

                                                   拉取未消费的消息,然后回调用户的callback

                                                   逻辑。

                                                   扩展:RocketMQ的push消费模式。

                                                            push消费模式本质上其实一个pull的过程,

                                                     consumer本质上在执行两个阶段的任务,阶段

                                                     一是负责从broker端pull消息到consumer端,

                                                     阶段二负责将拉取的消息push到consumer注

                                                     册的回调当中。

                                                            举例说明RocketMQ的拉取过程

                                                            -  阶段一的pull过程需要其实是一个多个维

                                                               度的过程,假设我们的broker集群包含3

                                                               个broker节点,在这个集群上我们创建了

                                                               一个topic并且topic指定的队列个数为6,

                                                               那么在这个场景下我们总共有3*6=18个队

                                                               列,其中3是3个broker,6是每个topic的队

                                                               列数。

                                                            -  在步骤2的基础上,假设我们的

                                                                consumerGroup只有一个consumer,那么

                                                                这个consumer就需要负责消费18个队列。

                                                            -  由于每个queue的拉取都是由单独任务在驱

                                                               动的,所以总共有18个拉取任务,由一个线

                                                               程的串行进行拉取,拉取完后再次重复提交

                                                               进行二次拉取过程,循环往复持续拉取数据。

                                                            -  拉取过程中如果发现消息数据条数超过

                                                               1000条,或者消息量超过100M,那么我们

                                                               就暂停消息拉取,延迟50ms后再次发起任

                                                               务拉取。

                     PullConsumer:用户需要主动调用相应的接口去拉取未消费的

                                               消息。

  pull push
实时性 取决于定时 较好
客户端状态 有状态,需要知道拉取点 无状态
服务端状态 无状态

有状态,需要了解每个客户端的

推送点

通常实现 短连接 长连接
难点 实时性和流量的取舍 客户端能力和push能力不匹配问题

 

 

 

 

 

 

 

            (f)Consumer Group

                    一类 Consumer 的集合名称,这类 Consumer 通常消费一类消息,

                    且消费逻辑一致。

                    Consumer Group的作用:通过指定相同的GroupName,才能实现

                                                              订阅同一个消息并且可以负载均衡(比

                                                              如我一个topic下有9条信息,有3个

                                                              Consumer,那么就可以通过相同的

                                                              Group Name,每个Consumer消费3条

                                                              记录)。

               (g)Pull Consumer

                        Consumer 的一种,应用通常主动调用Consumer 的拉消息方法

                        从Broker 拉消息,主动权由应用控制。

               (h)Broker

                        Broker是RocketMQ的核心组件,负责存储消息,转发消息,一

                        般也称为 Server。在JMS规范中称为 Provider。Broker通常以集

                        群方式启动,并可配置主从,每个Broker上提供对指定topic的服

                        务。理解了Broker的原理,以及和其他服务交互的方式就基本弄

                        懂了整个消息中间件的原理。   

                        Broker和Nameserver

                        单个Broker跟所有Namesrv保持心跳请求,心跳间隔为30秒,

                        心跳请求中包括当前Broker所有的Topic信息。Nameserver会反

                        查Broker的心跳信息,如果某个Broker在2分钟之内都没有心跳,

                        则认为该Broker下线,调整Topic跟Broker的对应关系。但此时

                        Nameserver不会主动通知Producer、Consumer有Broker宕机。

                        NameServer之间不会通信(因为Topic的信息不需要在集群中

                        保持强一致、追求强一致和最终一致性,所以NameServer中各

                        节点不通信)。

                        Producer和NameServer

                        每一个Producer与NameServer集群中的一台机器建立TCP连接,

                        从这台NameServer上拉取路由信息。

                        Producer和Broker

                        Producer和它要发送的topic相关的Master类型的Broker建立

                        TCP连接,用于发送消息以及定时的心跳信息。

                        Consumer和NameServer

                        每一个Consumer会和NameServer集群中的一台机器建立TCP

                        连接,会从这台NameServer上拉取路由信息,进行负载均衡。

                        Consumer和Broker

                        Consumer可以与Master或者Slave的Broker建立TCP连接来进

                        行消费消息,Consumer也会向它所消费的Broker发送心跳信

                        息,供Broker记录。

               (i)Topic

                              消息主题,是一个逻辑上的存储概念,里面包含队列(

                        默认4个,所以有时候发现发消息的时候是4个4个的发),

                        是消息队列的集。关于“逻辑上的存储概念”解释:

                        比如我可以一个Topic上设置为40个队列,20放在broker-A上,

                        20个放在broker-B上,由此可间其逻辑的意思。

                                使用例子,比如你有订单类的消息,也有库存类的消息,那

                        么就需要进行分类,一个是订单Topic存放订单相关的消息,

                        一个是库存Topic存储库存相关的消息。

               (k)Tag

                                标签可以被认为是对Topic进一步细化。一般在相同业务模块

                        中通过引入标签来标记不同用途的消息。说白了就是用来在Topic

                        下做进一步过滤,比如消费的时候我指定Topic为A,tag 为B,那

                        么我只能消费Topic A下tag为B的消息(该消息的Topic和Tag由

                        Productor指定)。有时候取到tag之后需要到对应的数据库里查

                        询以下才能确定这条消息该走if还是else。 

               (l)Message

                               Message 是消息的载体。一个 Message 必须指定topic,

                       相当于寄信的地址。Message还有一个可选的tag设置,以便

                       消费端可以基于 tag 进行过滤消息。也可以添加额外的键值

                       对,例如你需要一个业务 key 来查找 broker上的消息,方便

                       在发过程中诊断问题。

     (2)RocketMQ的工作流程与原理(重点)

                 集群部署架构如下:

​                   

                     结合部署结构图,描述集群工作流程:

                     -  启动Namesrv,Namesrv起来后监听端口,等待Broker、

                        Productor、Consumer连上来(都是长连接),相当于一个路

                        由控制中心。

                     -  Broker启动,跟所有的Namesrv保持长连接,定时发送心跳包。

                        心跳包中包含当前Broker信息(IP+端口等)以及存储所有Topic信

                        息。注册成功后,namesrv集群中就有Topic跟Broker的映射关

                        系。

                     -  收发消息前,先创建topic,创建topic时需要指定该topic要存储

                        在哪些Broker上。也可以在发送消息时自动创建Topic。

                     -  Producer发送消息,启动时先跟Namesrv集群中的其中台建

                        立长连接,并从Namesrv中获取当前发送的Topic存在那些

                        Broker上,然后和对应的Broker建立长连接(根据负载均衡算

                        法选择其中一台Broker),直接向Broker发消息。

                     -  Consumer跟Producer类似。跟其中台Namesrv建立长连接,

                        获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立

                        连接通道,开始消费消息。

                    注:Broker和namesrv可以部署到一台机器上,也可以部署到不同

                           的机器上。

                 RocketMQ原理介绍:

               (i)大体原理

                       上面所讲的工作流程就类似于RocketMQ的大体原理。在加上

                       3中的“发送逻辑底层和消费逻辑底层”

              (ii)消息堆积的原理

              (iii)顺序消费的原理

              (iv)事务消费的原理     

     (2)消费模式

              RocketMQ的消费分广播消费和集群消费

              首先声明RocketMQ只有一种模式就是“发布-订阅”

              RocketMQ只有集群模式和广播模式

              广播消费:一个消息被多个Consumer Group消费,即这几个

                               Consumer是同一个Consumer Group,消息会被每个

                               Consumer Group中的每个Consumer消费一次,广播

                               消费中的Consumer Group可以认为在消息划分中无意

                               义。 

              集群消费:一个Consumer Group中的Consumer实例平均分摊消

                               费消息,例如

                               某个Topic有9条消息,其中一个Consumer Group有3

                               个实例(可能是3个进程或者3台机器),那么每个实

                               例消费其中的3条消息。

                                      在CORBA Notification中,无此消费方式。

                                      在JMS规范中,JMS point-to-point model与之类

                               似,但是RocketMq集群消费功能大于PTP模型,因为

                               RocketMq的单个Consumer Group内的消费者类似于

                               PTP,但是一个Topic/Queue可以被多个Consumer

     (3)RocketMQ的高可用

            (a)消息发送的高可用

                     消息发送的高可用通过重试与Broker规避实现

                   (i)消息的发送分为同步、异步和单向发送

                           同步发送重试3次,单向和异步发送重试一次

                           同步发送:发送者向MQ 执行发送消息API 时,同步等待, 直到

                                             消息服务器返回发送结果

                           异步发送:发送者向MQ 执行发送消息API 时,指定消息发送成功

                                             后的回掉函数,然后调用消息发送API 后,立即返回,

                                             消息发送者线程不阻塞,直到运行结束,消息发送成功

                                             或失败的回调任务在一个新的线程中执行。

                           单向发送:消息发送者向MQ 执行发送消息API 时,直接返回,不

                                             等待消息服务器的结果,也不注册回调函数,简单地说,

                                             就是只管发,不在乎消息是否成功存储在消息服务器上。

                  (ii)Broker规避

                                   Broker规避就是在一次消息发送过程中发现错误,在某

                           一时间段内,消息生产者不会选择该Broker(消息服务器)

                           上的消息队列,提高发送消息的成功率。

            (b)消息存储的高可用(集群)              

                     工作不在淘宝、天猫等一般用多主模式。多主模式可以支持

              上万的并发,几十万上百万就需要写多Master多Slave模式。主

              从模式,开源版本从节点只能读不能写,当主节点宕机后,从

              节点不能升为  主节点;非开源版本,主节点宕机后,从节点可

              以升为主节点。 

            (i)RocketMQ HA原理

                    *  主服务器启动,并在特定端口上监昕从服务器的连接。

                    *  从服务器主动连接主服务器,主服务器接收客户端的连接,并建

                       立相关TCP 连接。

                    *  从服务器主动向主服务器发送待拉取消息偏移量,主服务器解析

                       请求并返回消息给从服务器。

                    *  从服务器保存消息并继续发送新的消息同步请求。    

            (i)集群模式

                    - 多主模式

                               一个集群无Slave,全是Master宕机或重启对应用无影响

                              优点:配置简单、性能高,单个Master宕机或重启对应用无影响,

                                         在磁盘配置为RAID10时,即使集器宕机不可恢复的情况

                                         下,优于RAID10非常可靠,消息也不会丢失(异步刷盘

                                         丢失少量消息,同步双写一条不丢)

                              缺点:单台集器宕机的期间,这台机器上未被消费的消息在机

                                        器恢复前不可订阅,消息的实时性会受到影响

                    - 多主多从异步复制模式

                                   每个Master配置一个Slave,有多对Master-Slave,HA采用异

                             步复制(异步刷盘?)的方式,主备有短暂的延迟,毫秒级。

                             优点:即使磁盘损坏,消息丢失非常少,且消息实时性不会受影响,

                                        因为Master宕机后,会自动切换到Slave,重出Slave上消费。

                             缺点:Master宕机后,会有少量的消息丢失。

                    - 多主多从同步双写

                             每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写

                    (同步刷盘?)的方式,主备都写成功了,才向应用返回成功。                       

                      优点:Master宕机后,消息无延迟,且不会丢失,服务可用性和数据可

                                 用性都非常高。

                      缺点:性能会比异步复制低,约10%;发送单个消息的RT会略高,目前

                                主机宕机后备机不能自动切换为主机,后续会支持自动切换功能。     

     (4)负载均衡

                     RocketMQ采用轮询所有队列的方式确定消息发送到哪一个队列,

              RocketMQ提供了两种消息队列轮询(MessageQueueSelector)方式,

              一种是根据Hash值进行轮询(即

              SelectMessageQueueByHash implements MessageQueueSelector),

              另一种是随机方式(即

              SelectMessageQueueByRandoom implementsMessageQueueSelector)。

              集群模式就是负载均衡的。

     3.RocketMQ的高并发读写与存储设计

        本节参考书和以下博客

        文章:https://blog.csdn.net/weixin_43267521/article/details/82825109

        如文章已删除,百度搜索:“RocketMQ高性能之底层存储设计”即可。

      (1)高并发读写设计,依靠Commit Log和Consume Queue

                      RocketMQ依靠文件来进行存储(速度上,文件存储>K,V>数据库),

               并利用了Linux的Page Cach机制,使得存储和读取速度都非常的快(文件

               顺序读写的速度几乎等于内存,Rocket MQ采用顺序写,随机读,随机读

               如果命中Page Cache则变成顺序读)。

             (a)先来了解CommitLog和CommitQueue

                      Commit Log

                             是保存消息元数据的地方,它一个文件集合,每个文件1G大小,

                             存储满后存下一个。

                             CommitLog的清理机制:

                                   -  按时间清理:rocketmq默认会清理3天前的commitLog文件;

                                   -  按磁盘水位清理:当磁盘使用量到达磁盘容量75%,开始清

                                             理最老的commitLog文件。    

                      ConsumeQueue

                             ConsumerQueue相当于CommitLog的索引文件,每个

                             ConsumerQueue代表一个逻辑队列(默认4个队列),消费者消

                             费时会先从ConsumerQueue中查找消息的在commitLog中的offset,

                             再去CommitLog中找元数据。果某个消息只在CommitLog中有

                             数据,没在ConsumerQueue中, 则消费者无法消费,Rocktet的

                             事务消息就是这个原理。

                             Consumequeue的数据结构包含3部分:

                                   -  消息在commitLog文件实际偏移量(commitLogOffset)

                                   -  消息大小

                                   -  消息tag的哈希值

                           偏移量的理论知识记录在:

                                  https://blog.csdn.net/jialanshun/article/details/87939905

             (6)RocketMQ高并发读写的原理

                    (a)消息顺序写和随机读

                             消息顺序写

                                           文件进行顺序写,速度几乎与写内存等同,原因是因为使

                                    用了Page Cache,数据写Catche中就返回,在加上RocketMQ

                                    默认数据满4k才重Page Cache刷到硬盘上,所以保证了高并

                                    发的写能力。

                             消息随机读(因整个RMQ只有一个CommitLog,虽然是随机读,

                                    但整体还是有序地读,只要那整块区域还在PageCache的范

                                    围内,还是可以充分利用Page Cache)

                                           MQ读取消息依赖系统PageCache,PageCache命中率

                                    越高,读性能越高,Linux平时也会尽量预读数据,使得应用

                                    直接访问磁盘的概率降低。

                                    当客户端向Broker拉取消息时,Broker上系统读文件过程如下:

                                    -  检查要读的数据是否在上次预读的cache中;

                                    -  若不在cache,操作系统从磁盘中读取对应的数据页,并且

                                       系统还会将该数据页之后的连续几页(一般三页)也一并读

                                       入到cache中,再将应用需要的数据返回给应用。此情况操作

                                       系统认为是跳跃读取,属于同步预读。

                                    -  若命中cache,相当于上次缓存的内容有效,操作系统认为顺

                                       序读盘,则继续扩大缓存的数据范围,将之前缓存的数据页往

                                       后的N页数据再读取到cache中,属于异步预读。

                             所以Broker的机器需要大内存,尽量缓存足够多的commitLog,让

                             Broker读写消息基本在PageCache中操作。在运行时,如果数据量

                             非常大,可以看到broker的进程占用内存比较多,其实大部分是被

                             缓存住的commitlog。

                                     确切的说,文件顺序读写快的原因就是因为Page Cache和文件

                             映射虚拟内存

                                    注:正式部署尽量将CommitLog和ConsumeQueue放在不同的

                                           物理SSD,避免多类文件进行IO竞争。

                                    扩展:PageCache介绍

                                                      虽然通常文件读写比较慢,但是如果对文件进行顺

                                               序读写,速度几乎是接近于内存的随机读写,为什么会

                                               这么快,原因就是Page Cache。

                                 ​

 

                                                       先来个直观的感受,如上图,整个OS有3.7G的物理内

                                               存,用掉了2.7G,应当还剩下1G空闲的内存,但OS给出的

                                               却是175M。当然这个数学题肯定不能这么算。

                                                      OS发现系统的物理内存有大量剩余时,为了提高IO的

                                               性能,就会使用多余的内存当做文件缓存,也就是图上的

                                               buff / cache,广义我们说的Page Cache就是这些内存的子

                                               集。

                                                      OS在读磁盘时会将当前区域的内容全部读到Cache中,

                                              以便下次读时能命中Cache,写磁盘时直接写到Cache中就

                                              写返回,由OS的pdflush以某些策略将Cache的数据Flush回

                                              磁盘。

                                                     但是系统上文件非常多,即使是多余的Page Cache也

                                               是非常宝贵的资源,OS不可能将Page Cache随机分配给任

                                               何文件,Linux底层就提供了mmap将一个程序指定的文件

                                               映射进虚拟内存(Virtual Memory),对文件的读写就变成

                                               了对内存的读写,能充分利用Page Cache。不过,文件IO

                                               仅仅用到了Page Cache还是不够的,如果对文件进行随机

                                               读写,会使虚拟内存产生很多缺页(Page Fault)中断。

                                             ​

                                                       每个用户空间的进程都有自己的虚拟内存,每个进程都

                                                认为自己所有的物理内存,但虚拟内存只是逻辑上的内存,

                                                要想访问内存的数据,还得通过内存管理单元(MMU)查找

                                                页表,将虚拟内存映射成物理内存。如果映射的文件非常大,

                                                程序访问局部映射不到物理内存的虚拟内存时,产生缺页中

                                                断,OS需要读写磁盘文件的真实数据再加载到内存。如同我

                                                们的应用程序没有Cache住某块数据,直接访问数据库要数

                                                据再把结果写到Cache一样,这个过程相对而言是非常慢的。

                                                       但是顺序IO时,读和写的区域都是被OS智能Cache过的

                                                热点区域,不会产生大量缺页中断,文件的IO几乎等同于内

                                                存的IO,性能当然就上去了。说了这么多Page Cache的优点,

                                                也得稍微提一下它的缺点,内核把可用的内存分配给

                                                Page Cache后,free的内存相对就会变少,如果程序有新的

                                                内存分配需求或者缺页中断,恰好free的内存不够,内核还需

                                                要花费一点时间将热度低的Page Cache的内存回收掉,对性

                                                能非常苛刻的系统会产生毛刺。  

                    (b)刷盘与Page Cache

                                    异步刷盘使用了Page Cache,同步刷盘并没有,这也是异步刷盘比

                             同步效率高的原因,由此可间,异步刷盘比同步刷盘效率高。

​                           

                             异步刷盘

                                    过程如下图

​                             

                                    异步刷盘,刷盘由程序和OS共同控制。写消息时,RocketMQ将消

                             息写进Page Cache而不时磁盘,然后由Page Cache将消息刷新到磁盘。

​                               

                                        脏页设置太小,Flush磁盘的次数就会增加,性能会下降;脏页

                                设置太大,性能会提高,但万一OS宕机,脏页来不及刷盘,消息就

                                丢了。

                               ​

                               上图为centos系统的默认配置,dirty_ratio为阻塞式flush的阈值,而

                               dirty_background_ratio是非阻塞式的flush。

                               ​

                             异步刷盘

                                           没有使用Page Cache。在消息真正落盘后,才返回成功给

                                    Producer,只要磁盘没有损坏,消息就不会丢。一般用于金融

                                    场景。

                            ​

                     (c)RocketMQ消息的消费与发送逻辑

                              发送逻辑

                                            发送时,Producer不直接与Consume Queue打交道。上

                                     文提到过,RMQ所有的消息都会存放在CommitLog中,为了

                                     使消息存储不发生混乱,对CommitLog进行写之前就会上锁。

                                     消息持久被锁串行化后,对Commit Log就是顺序写,也就是

                                     常说的Append操作。配合上Page Cache,RMQ在写

                                     CommitLog时效率会非常高。CommitLog持久后,会将里面

                                     的数据Dispatch到对应的ConsumeQueue上。

​                                 

                                     每一个Consume Queue代表一个逻辑队列,是由

                                     ReputMessageService在单个Thread Loop中Append,显然

                                     也是顺序写。

                              消费逻辑 

                                           消费时,Consumer不直接与CommitLog打交道,而是从

                                     ConsumeQueue中去拉取数据,然后在根据ConsumeQueue去拉取

                                     CommitLog中的数据

                                             RocketMQ对ConsumeQueue是顺序读(重旧到新),对

                                     CommitLog是随机读(因整个RMQ只有一个CommitLog,虽然是随

                                     机读,但整体还是有序地读,只要那整块区域还在PageCache的范

                                     围内,还是可以充分利用Page Cache)。对ConsumeQueue的顺

                                     序读如下:

                                   ​

                                            拉取的顺序从旧到新,在文件表示每一个Consume Queue都是

                                     顺序读,充分利用了Page Cache。

                                           因为ConsumeQueue是没有数据的,里面只有一个对CommitLog

                                    的引用,所以需要再次拉取CommitLog。

                                   ​

                                  重ConsumeQueue拉取CommitLog,如图:

​                                   

                                  CommitLog的顺序读,如下图:

                               ​

            (d)综上,对Rocket MQ补张内存模型。

                     其中第四幅图可以不看

​                 

​                

                        CommitLog的文件结构

                                ​

​                               

                         (e)对比kafka

                                         Kafka中关于消息的存储只有一种文件,叫做Partition(不考虑

                                  细化的Segment),它履行了RMQ中Commit Log和

                                  ConsumeQueue公共的职责,即它在逻辑上进行拆分存,以提高消

                                  费并行度,又在内部存储了真实的消息内容。 

                                 ​

                                                                         kafka的分区模型

                                 ​

                                                                       Partition顺序读写

                                         这样看上去非常完美,不管对于Producer还是Consumer,

                                  单个Partition文件在正常的发送和消费逻辑中都是顺序IO,充分利

                                  用Page Cache带来的巨大性能提升,但是,万一Topic很多,每个

                                  Topic又分了N个Partition,这时对于OS来说,这么多文件的顺序

                                   读写在并发时变成了随机读写。

                                          当然,思路很好的同学马上发现RMQ在队列非常多的情况下

                                   Consume Queue不也是和Kafka类似,虽然每一个文件是顺序IO,

                                   但整体是随机IO。不要忘记了,RMQ的Consume Queue是不会

                                   存储消息的内容,任何一个消息也就占用20 Byte,所以文件可以

                                   控制得非常小,绝大部分的访问还是Page Cache的访问,而不是

                                   磁盘访问。正式部署也可以将CommitLog和ConsumeQueue放在

                                   不同的物理SSD,避免多类文件进行IO竞争。

 

      (7)负载均衡

                 RocketMQ采用轮询所有队列的方式确定消息发送到哪一个队列,RocketMQ提供

                 了两种消息队列轮询(MessageQueueSelector)方式,一种是根据Hash值进行轮

                 询(即SelectMessageQueueByHash implements MessageQueueSelector),另一

                 种是随机方式(即SelectMessageQueueByRandoom implements

                 MessageQueueSelector)。集群模式就是负载均衡的。

posted @ 2019-05-07 20:39  jialanshun  阅读(1224)  评论(0编辑  收藏  举报