2.软文推荐
3.软文推荐
目录: 1、常用的消息队列 2、消息队列之RabbitMQ-分布式部署 3、分布式、中间件和消息队列到底是怎么的一种工作模式? 4、消息队列原理及选型 5、消息队列之zeroMQ、rabbitMQ、kafka 常用的消息队列一、redis消息队列和kafka消息队列的比较
1、Redis作为消息队列
Redis的pub-sub模式非常像西式快餐一样,快产快消,全都是因为Redis是使用内存来做存取,所有你生产的消息立马会被消费者一次性全部处理掉,并且没有留下任何痕迹, 同时因为内存总是宝贵的,所以内存上会有限制,当生产者以及消费者上来的时候也会对redis的效率,还有Redis在处理发布和消费big size(10K+的文件)的数据的时候会表现出无法忍受的缓慢
如果有以下场景可以考虑使用Redis作为消息队列:
a、如果你的需求是快产快消的即时消费场景,并且生产的消息立即被消费者消费掉
b、如果速度是你十分看重的,比如慢了一秒好几千万这种
c、如果允许出现消息丢失的场景
d、如果你不需要系统保存你发送过的消息,做到来无影去无踪
e、需要处理的数据量并不是那么巨大
2、KafKa作为消息队列
KafKa的设计精妙,支持分布式,高可用的部署,并且对一个大的队列采用分成多个Partition(分区),来提高消息入队的吞吐量,分而治之的思想. 并且消费的时候支持group的概念,能够支持多个客户端消费同个队列,并且一个group中可以增加consumer的数量来扩展消费的处理量.
KafKa不熟生产者数量的影响,因为吞吐量足够支撑,即使在廉价的单机服务器上也可以有10万每秒的消息传输量,并且消费者是想什么时候消费都可以,消息它就在那里,十分灵活,不用担心来无影去无踪的恐慌.能把消息持久化,并以一定的策略(例如一定时间内删除,或者到达多大容量的时候清空)
当有一下场景的时候你可以考虑使用KafKa作为消息队列:
a、如果你想要稳定的消息队列
b、如果你想要你发送过的消息可以保留一定的时间,并不是无迹可寻的时候
c、如果你无法忍受数据的丢失
d、如果速度不需要那么的快
e、如果需要处理数据量巨大的时候
消息队列之RabbitMQ-分布式部署RabbitMQ分布式部署有3种方式:
Federation与Shovel都是以插件的形式来实现,复杂性相对高,而集群是RabbitMQ的自带属性,相对简单。
这三种方式并不是互斥的,可以根据需求选择相互组合来达到目的。
RabbitMQ本身是基于Erlang编写,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的magic cookie来实现)。
因此,RabbitMQ天然支持Clustering。这使得RabbitMQ本身不需要像ActiveMQ、Kafka那样通过ZooKeeper分别来实现HA方案和保存集群的元数据。集群是保证可靠性的一种方式,同时可以通过水平扩展以达到增加消息吞吐量能力的目的。
我们把部署RabbitMQ的机器称为节点,也就是broker。broker有2种类型节点: 磁盘节点 和 内存节点 。顾名思义,磁盘节点的broker把元数据存储在磁盘中,内存节点把元数据存储在内存中,很明显,磁盘节点的broker在重启后元数据可以通过读取磁盘进行重建,保证了元数据不丢失,内存节点的broker可以获得更高的性能,但在重启后元数据就都丢了。
元数据包含以下内容:
单节点系统必须是磁盘节点 ,否则每次你重启RabbitMQ之后所有的系统配置信息都会丢失。
集群中至少有一个磁盘节点 ,当节点加入和离开集群时,必须通知磁盘 节点。
如果集群中的唯一一个磁盘节点,结果这个磁盘节点还崩溃了,那会发生什么情况?集群依然可以继续路由消息(因为其他节点元数据在还存在),但无法做以下操作:
也就是说,如果唯一磁盘的磁盘节点崩溃, 集群是可以保持运行的,但不能更改任何东西 。为了增加可靠性,一般会在集群中设置两个磁盘节点,只要任何一个处于工作状态,就可以保障集群的正常服务。
RabbitMQ的集群模式分为两种: 普通模式 与 镜像模式 。
普通模式,也是默认的集群模式。
对于Queue来说, 消息实体只存在于其中一个节点 ,A、B两个节点仅有相同的元数据,即队列结构。当消息进入A节点的Queue中后,consumer从B节点拉取时,RabbitMQ会临时在A、B间进行消息传输,把A中的消息实体取出并经过B发送给consumer。所以consumer应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理Queue。否则无论consumer连A或B,出口总在A,会产生瓶颈。
队列所在的节点称为 宿主节点 。
队列创建时,只会在宿主节点创建队列的进程,宿主节点包含完整的队列信息,包括元数据、状态、内容等等。因此, 只有队列的宿主节点才能知道队列的所有信息 。
队列创建后,集群只会同步队列和交换器的元数据到集群中的其他节点,并不会同步队列本身,因此 非宿主节点就只知道队列的元数据和指向该队列宿主节点的指针 。
假如现在一个客户端需要对Queue A进行发布或者订阅,发起与集群的连接,有两种可能的场景:
由于节点之间存在路由转发的情况,对延迟非常敏感,应当只在本地局域网内使用,在广域网中不应该使用集群,而应该用Federation或者Shovel代替。
这样的设计,保证了不论从哪个broker中均可以消费所有队列的数据,并分担了负载,因此,增加broker可以线性提高服务的性能和吞吐量。
但该方案也有显著的缺陷,那就是 不能保证消息不会丢失 。当集群中某一节点崩溃时,崩溃节点所在的队列进程和关联的绑定都会消失,附加在那些队列上的消费者也会丢失其订阅信息,匹配该队列的新消息也会丢失。比如A为宿主节点,当A节点故障后,B节点无法取到A节点中还未消费的消息实体。如果做了消息持久化,那么得等A节点恢复,然后才可被消费;如果没有持久化的话,然后就没有然后了……
肯定有不少同学会问,想要实现HA方案,那将RabbitMQ集群中的所有Queue的完整数据在所有节点上都保存一份不就可以了吗?比如类似MySQL的主主模式,任何一个节点出现故障或者宕机不可用时,那么使用者的客户端只要能连接至其他节点,不就能够照常完成消息的发布和订阅吗?
RabbitMQ这么设计是基于性能和存储空间上来考虑:
引入 镜像队列 (Mirror Queue)的机制,可以将队列镜像到集群中的其他Broker节点之上,如果集群中的一个节点失效了,队列能够自动切换到镜像中的另一个节点上以保证服务的可用性。
一个镜像队列中包含有1个主节点master和若干个从节点slave。其主从节点包含如下几个特点:
该模式和普通模式不同之处在于,消息实体会主动在镜像节点间同步,而不是在consumer取数据时临时拉取。该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉。所以在对可靠性要求较高的场合中适用。
一个队列想做成镜像队列,需要先设置policy,然后客户端创建队列的时候,rabbitmq集群根据队列名称自动设置为普通队列还是镜像队列。
镜像队列的配置通过添加policy完成,policy添加的命令为:
例如,对队列名称以hello开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
通常队列由两部分组成:一部分是AMQQueue,负责AMQP协议相关的消息处理,即接收生产者发布的消息、向消费者投递消息、处理消息confirm、acknowledge等等;另一部分是BackingQueue,它提供了相关的接口供AMQQueue调用,完成消息的存储以及可能的持久化工作等。
镜像队列基本上就是一个特殊的BackingQueue,它内部包裹了一个普通的BackingQueue做本地消息持久化处理,在此基础上增加了将消息和ack复制到所有镜像的功能。所有对mirror_queue_master的操作,会通过组播GM(下面会讲到)的方式同步到各slave节点。GM负责消息的广播,mirror_queue_slave负责回调处理,而master上的回调处理是由coordinator负责完成。mirror_queue_slave中包含了普通的BackingQueue进行消息的存储,master节点中BackingQueue包含在mirror_queue_master中由AMQQueue进行调用。
消息的发布(除了Basic.Publish之外)与消费都是通过master节点完成。master节点对消息进行处理的同时将消息的处理动作通过GM广播给所有的slave节点,slave节点的GM收到消息后,通过回调交由mirror_queue_slave进行实际的处理。
GM(Guarenteed Multicast) 是一种可靠的组播通讯协议,该协议能够保证组播消息的原子性,即保证组中活着的节点要么都收到消息要么都收不到。它的实现大致如下:
将所有的节点形成一个循环链表,每个节点都会监控位于自己左右两边的节点,当有节点新增时,相邻的节点保证当前广播的消息会复制到新的节点上;当有节点失效时,相邻的节点会接管保证本次广播的消息会复制到所有的节点。在master节点和slave节点上的这些gm形成一个group,group(gm_group)的信息会记录在mnesia中。不同的镜像队列形成不同的group。消息从master节点对于的gm发出后,顺着链表依次传送到所有的节点,由于所有节点组成一个循环链表,master节点对应的gm最终会收到自己发送的消息,这个时候master节点就知道消息已经复制到所有的slave节点了。
slave节点先从gm_group中获取对应group的所有成员信息,然后随机选择一个节点并向这个节点发送请求,这个节点收到请求后,更新gm_group对应的信息,同时通知左右节点更新邻居信息(调整对左右节点的监控)及当前正在广播的消息,然后回复通知请求节点成功加入group。请求加入group的节点收到回复后再更新rabbit_queue中的相关信息,并根据需要进行消息的同步。
当slave节点失效时,仅仅是相邻节点感知,然后重新调整邻居节点信息、更新rabbit_queue、gm_group的记录等。如果是master节点失效,"资格最老"的slave节点被提升为master节点,slave节点会创建出新的coordinator,并告知gm修改回调处理为coordinator,原来的mirror_queue_slave充当amqqueue_process处理生产者发布的消息,向消费者投递消息等。
上面提到如果是slave节点失效,只有相邻的节点能感知到,那么master节点失效是不是也是只有相邻的节点能感知到?假如是这样的话,如果相邻的节点不是"资格最老"的节点,怎么通知"资格最老"的节点提升为新的master节点呢?
实际上,所有的slave节点在加入group时,mirror_queue_slave进程会对master节点的amqqueue_process进程(也可能是mirror_queue_slave进程)进行监控,如果master节点失效的话,mirror_queue_slave会感知,然后再通过gm进行广播,这样所有的节点最终都会知道master节点失效。当然,只有"资格最老"的节点会提升自己为新的master。
消息从master节点发出,顺着节点链表发送。在这期间,所有的slave节点都会对消息进行缓存,当master节点收到自己发送的消息后,会再次广播ack消息,同样ack消息会顺着节点链表经过所有的slave节点,其作用是通知slave节点可以清除缓存的消息,当ack消息回到master节点时对应广播消息的生命周期结束。
下图为一个简单的示意图,A节点为master节点,广播一条内容为"test"的消息。"1"表示消息为广播的第一条消息;"id=A"表示消息的发送者为节点A。右边是slave节点记录的状态信息。
为什么所有的节点都需要缓存一份发布的消息呢?
master发布的消息是依次经过所有slave节点,在这期间的任何时刻,有可能有节点失效,那么相邻的节点可能需要重新发送给新的节点。例如,A-B-C-D-A形成的循环链表,A为master节点,广播消息发送给节点B,B再发送给C,如果节点C收到B发送的消息还未发送给D时异常结束了,那么节点B感知后节点C失效后需要重新将消息发送给D。同样,如果B节点将消息发送给C后,B,C节点中新增了E节点,那么B节点需要再将消息发送给新增的E节点。
配置镜像队列的时候有个 ha-sync-mode 属性,这个有什么用呢?
新节点加入到group后,最多能从左边节点获取到当前正在广播的消息内容,加入group之前已经广播的消息则无法获取到。如果此时master节点不幸失效,而新节点有恰好成为了新的master,那么加入group之前已经广播的消息则会全部丢失。
注意:这里的消息具体是指新节点加入前已经发布并复制到所有slave节点的消息,并且这些消息还未被消费者消费或者未被消费者确认。如果新节点加入前,所有广播的消息被消费者消费并确认了,master节点删除消息的同时会通知slave节点完成相应动作。这种情况等同于新节点加入前没有发布任何消息。
避免这种问题的解决办法就是对新的slave节点进行消息同步。当 ha-sync-mode 配置为自动同步(automatic)时,新节点加入group时会自动进行消息的同步;如果配置为manually则需要手动操作完成同步。
Federation直译过来是联邦,它的设计目标是使 RabbitMQ 在不同的 Broker 节点之间进行消息传递而无须建
立集群。具有以下特点:
那么它到底有什么用呢?我们可以从一个实际场景入手:
有两个服务分别部署在国内和海外,它们之间需要通过消息队列来通讯。
很明显无论RabbitMQ部署在海外还是国内,另一方一定得忍受连接上的延迟。因此我们可以在海外和国内各部署一个MQ,这样一来海外连接海外的MQ,国内连接国内,就不会有连接上的延迟了。
但这样还会有问题,假设某生产者将消息存入海外MQ中的某个队列 queueB , 在国内的服务想要消费 queueB 消息,消息的流转及确认必然要忍受较大的网络延迟 ,内部编码逻辑也会因这一因素变得更加复杂。
此外,服务可能得维护两个MQ的配置,比如国内服务在生产消息时得使用国内MQ,消费消息时得监听海外MQ的队列,降低了系统的维护性。
可能有人想到可以用集群,但是RabbitMQ的集群对延迟非常敏感,一般部署在局域网内,如果部署在广域网可能会产生网络分区等等问题。
这时候,Federation就派上用场了。它被设计成能够容忍不稳定的网络连接情况,完全能够满足这样的场景。
那使用Federation之后是怎样的业务流程呢?
首先我们在海外MQ上定义exchangeA,它通过路由键“rkA”绑定着queueA。然后用Federation在exchangeA上建立一条 单向 连接到国内RabbitMQ,Federation则自动会在国内RabbitMQ建立一个exchangeA交换器(默认同名)。
这时候,如果部署在国内的client C在国内MQ上publish了一条消息,这条消息会通过 Federation link 转发到海外MQ的交换器exchangeA中,最终消息会存入与 exchangeA 绑定的队列 queueA 中,而client C也能立即得到返回。
实际上,Federation插件还会在国内MQ建立一个内部的交换器:exchangeA→ broker3 B(broker3是集群名),并通过路由键 "rkA"将它和国内MQ的exchangeA绑定起来。接下来还会在国内MQ上建立一个内部队列federation: exchangeA-broker3 B,并与内部exchange绑定。这些操作都是内部的,对客户端来说是透明的。
值得一提的是,Federation的连接是单向的,如果是在海外MQ的exchangeA上发送消息是不会转到国内的。
这种在exchange上建立连接进行联邦的,就叫做 联邦交换器 。一个联邦交换器接收上游(upstream)的信息,这里的上游指的是其他的MQ节点。
对比前面举的例子,国内MQ就是上游,联邦交换器能够将原本发送给上游交换器的消息路由到本地的某个队列中。
有联邦交换器自然也有联播队列,联邦队列则允许一个本地消费者接收到来自上游队列的消息 。
如图,海外MQ有队列A,给其设置一条链接,Federation则自动会在国内RabbitMQ建立一个队列A(默认同名)。
当有消费者 ClinetA连接海外MQ并消费 queueA 中的消息时,如果队列 queueA中本身有若干消息堆积,那么 ClientA直接消费这些消息,此时海外MQ中的queueA并不会拉取国内中的 queueA 的消息;如果队列 queueA中没有消息堆积或者消息被消费完了,那么它会通过 Federation link 拉取上游队列 queueA 中的消息(如果有消息),然后存储到本地,之后再被消费者 ClientA进行消费 。
首先开启Federation 功能:
值得注意的是,当需要在集群中使用 Federation 功能的时候,集群中所有的节点都应该开启 Federation 插件。
接下来我们要配置两个东西:upstreams和Policies。
每个 upstream 用于定义与其他 Broker 建立连接的信息。
通用参数如下:
然后定义一个 Policy, 用于匹配交换器:
^exchange 意思是将匹配所有以exchange名字开头的交换器,为它们在上游创建连接。这样就创建了一个 Federation link。
Shovel是RabbitMQ的一个插件, 能够可靠、持续地从一个Broker 中的队列(作为源端,即source )拉取数据并转发至另一个Broker 中的交换器(作为目的端,即destination )。作为源端的队列和作为目的端的交换器可以同时位于同一个 Broker 上,也可以位于不同的 Broker 上。
使用Shovel有以下优势:
使用Shovel时,通常源为队列,目的为交换器:
但是,也可以源为队列,目的为队列。实际也是由交换器转发,只不过这个交换器是默认交换器。配置交换器做为源也是可行的。实际上会在源端自动新建一个队列,消息先存在这个队列,再被Shovel移走。
使用Shovel插件命令:
Shovel 既可以部署在源端,也可以部署在目的端。有两种方式可以部署 Shovel:
其主要差异如下:
来看一个使用Shovel治理消息堆积的案例。
当某个队列中的消息堆积严重时,比如超过某个设定的阈值,就可以通过 Shovel 将队列中的消息移交给另一个集群。
分布式、中间件和消息队列到底是怎么的一种工作模式?分布式就是不部署在一个进程中,比如多台机器,甚至同台机器的不同进程中。
中间件除了自己写的代码和一些工具类库都可以叫中间件,比如数据库,开发框架,缓存,队列等
消息队列就是一个中间件,有生产的有消费的还有个消息暂存的,比如超市货架,超市往货架放东西,顾客取东西,货架就是暂存货物。
消息队列原理及选型消息队列(Message Queue)是一种进程间通信或同一进程的不同线程间的通信方式。
Broker(消息服务器)
Broker的概念来自与Apache ActiveMQ,通俗的讲就是MQ的服务器。
Producer(生产者)
业务的发起方,负责生产消息传输给broker
Consumer(消费者)
业务的处理方,负责从broker获取消息并进行业务逻辑处理
Topic(主题)
发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅 者,实现消息的广播
Queue(队列)
PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收。
Message(消息体)
根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输
点对点模型用于消息生产者和消息消费者之间点到点的通信。
点对点模式包含三个角色:
每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,可以放在内存 中也可以持久化,直到他们被消费或超时。
特点:
发布订阅模型包含三个角色:
多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。
特点:
AMQP即Advanced Message Queuing Protocol,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。AMQP 的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
优点:可靠、通用
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议。
优点:格式简洁、占用带宽小、移动端通信、PUSH、嵌入式系统
STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互。
优点:命令模式(非topicqueue模式)
XMPP(可扩展消息处理现场协议,Extensible Messaging and Presence Protocol)是基于可扩展标记语言(XML)的协议,多用于即时消息(IM)以及在线现场探测。适用于服务器之间的准即时操作。核心是基于XML流传输,这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。
优点:通用公开、兼容性强、可扩展、安全性高,但XML编码格式占用带宽大
RabbitMQ 是实现 AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 RabbitMQ 主要是为了实现系统之间的双向解耦而实现的。当生产者大量产生数据时,消费者无法快速消费,那么需要一个中间层。保存这个数据。
RabbitMQ 是一个开源的 AMQP 实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP 等,支持 AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
Channel(通道)
道是两个管理器之间的一种单向点对点的的通信连接,如果需要双向交流,可以建立一对通道。
Exchange(消息交换机)
Exchange类似于数据通信网络中的交换机,提供消息路由策略。
RabbitMq中,producer不是通过信道直接将消息发送给queue,而是先发送给Exchange。一个Exchange可以和多个Queue进行绑定,producer在传递消息的时候,会传递一个ROUTING_KEY,Exchange会根据这个ROUTING_KEY按照特定的路由算法,将消息路由给指定的queue。和Queue一样,Exchange也可设置为持久化,临时或者自动删除。
Exchange有4种类型:direct(默认),fanout, topic, 和headers。
不同类型的Exchange转发消息的策略有所区别:
Binding(绑定)
所谓绑定就是将一个特定的 Exchange 和一个特定的 Queue 绑定起来。Exchange 和Queue的绑定可以是多对多的关系。
Routing Key(路由关键字)
exchange根据这个关键字进行消息投递。
vhost(虚拟主机)
在RabbitMq server上可以创建多个虚拟的message broker,又叫做virtual hosts (vhosts)。每一个vhost本质上是一个mini-rabbitmq server,分别管理各自的exchange,和bindings。vhost相当于物理的server,可以为不同app提供边界隔离,使得应用安全的运行在不同的vhost实例上,相互之间不会干扰。producer和consumer连接rabbit server需要指定一个vhost。
假设P1和C1注册了相同的Broker,Exchange和Queue。P1发送的消息最终会被C1消费。
基本的通信流程大概如下所示:
Consumer收到消息时需要显式的向rabbit broker发送basic。ack消息或者consumer订阅消息时设置auto_ack参数为true。
在通信过程中,队列对ACK的处理有以下几种情况:
即消息的Ackownledge确认机制,为了保证消息不丢失,消息队列提供了消息Acknowledge机制,即ACK机制,当Consumer确认消息已经被消费处理,发送一个ACK给消息队列,此时消息队列便可以删除这个消息了。如果Consumer宕机/关闭,没有发送ACK,消息队列将认为这个消息没有被处理,会将这个消息重新发送给其他的Consumer重新消费处理。
消息的收发处理支持事务,例如:在任务中心场景中,一次处理可能涉及多个消息的接收、处理,这应该处于同一个事务范围内,如果一个消息处理失败,事务回滚,消息重新回到队列中。
消息的持久化,对于一些关键的核心业务来说是非常重要的,启用消息持久化后,消息队列宕机重启后,消息可以从持久化存储恢复,消息不丢失,可以继续消费处理。
fanout 模式
模式特点:
direct 模式
任何发送到Direct Exchange的消息都会被转发到routing_key中指定的Queue。
如果一个exchange 声明为direct,并且bind中指定了routing_key,那么发送消息时需要同时指明该exchange和routing_key。
简而言之就是:生产者生成消息发送给Exchange, Exchange根据Exchange类型和basic_publish中的routing_key进行消息发送 消费者:订阅Exchange并根据Exchange类型和binding key(bindings 中的routing key) ,如果生产者和订阅者的routing_key相同,Exchange就会路由到那个队列。
topic 模式
前面讲到direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。
topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同。
它约定:
以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。
RabbitMQ,部署分三种模式:单机模式,普通集群模式,镜像集群模式。
普通集群模式
多台机器部署,每个机器放一个rabbitmq实例,但是创建的queue只会放在一个rabbitmq实例上,每个实例同步queue的元数据。
如果消费时连的是其他实例,那个实例会从queue所在实例拉取数据。这就会导致拉取数据的开销,如果那个放queue的实例宕机了,那么其他实例就无法从那个实例拉取,即便开启了消息持久化,让rabbitmq落地存储消息的话,消息不一定会丢,但得等这个实例恢复了,然后才可以继续从这个queue拉取数据, 这就没什么高可用可言,主要是提供吞吐量 ,让集群中多个节点来服务某个queue的读写操作。
镜像集群模式
queue的元数据和消息都会存放在多个实例,每次写消息就自动同步到多个queue实例里。这样任何一个机器宕机,其他机器都可以顶上,但是性能开销太大,消息同步导致网络带宽压力和消耗很重,另外,没有扩展性可言,如果queue负载很重,加机器,新增的机器也包含了这个queue的所有数据,并没有办法线性扩展你的queue。此时,需要开启镜像集群模式,在rabbitmq管理控制台新增一个策略,将数据同步到指定数量的节点,然后你再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。
Kafka 是 Apache 的子项目,是一个高性能跨语言的分布式发布/订阅消息队列系统(没有严格实现 JMS 规范的点对点模型,但可以实现其效果),在企业开发中有广泛的应用。高性能是其最大优势,劣势是消息的可靠性(丢失或重复),这个劣势是为了换取高性能,开发者可以以稍降低性能,来换取消息的可靠性。
一个Topic可以认为是一类消息,每个topic将被分成多个partition(区),每个partition在存储层面是append log文件。任何发布到此partition的消息都会被直接追加到log文件的尾部,每条消息在文件中的位置称为offset(偏移量),offset为一个long型数字,它是唯一标记一条消息。它唯一的标记一条消息。kafka并没有提供其他额外的索引机制来存储offset,因为在kafka中几乎不允许对消息进行“随机读写”。
Kafka和JMS(Java Message Service)实现(activeMQ)不同的是:即使消息被消费,消息仍然不会被立即删除。日志文件将会根据broker中的配置要求,保留一定的时间之后删除;比如log文件保留2天,那么两天后,文件会被清除,无论其中的消息是否被消费。kafka通过这种简单的手段,来释放磁盘空间,以及减少消息消费之后对文件内容改动的磁盘IO开支。
对于consumer而言,它需要保存消费消息的offset,对于offset的保存和使用,有consumer来控制;当consumer正常消费消息时,offset将会"线性"的向前驱动,即消息将依次顺序被消费。事实上consumer可以使用任意顺序消费消息,它只需要将offset重置为任意值。(offset将会保存在zookeeper中,参见下文)
kafka集群几乎不需要维护任何consumer和producer状态信息,这些信息有zookeeper保存;因此producer和consumer的客户端实现非常轻量级,它们可以随意离开,而不会对集群造成额外的影响。
partitions的设计目的有多个。最根本原因是kafka基于文件存储。通过分区,可以将日志内容分散到多个server上,来避免文件尺寸达到单机磁盘的上限,每个partiton都会被当前server(kafka实例)保存;可以将一个topic切分多任意多个partitions,来消息保存/消费的效率。此外越多的partitions意味着可以容纳更多的consumer,有效提升并发消费的能力。(具体原理参见下文)。
一个Topic的多个partitions,被分布在kafka集群中的多个server上;每个server(kafka实例)负责partitions中消息的读写操作;此外kafka还可以配置partitions需要备份的个数(replicas),每个partition将会被备份到多台机器上,以提高可用性。
基于replicated方案,那么就意味着需要对多个备份进行调度;每个partition都有一个server为"leader";leader负责所有的读写操作,如果leader失效,那么将会有其他follower来接管(成为新的leader);follower只是单调的和leader跟进,同步消息即可。由此可见作为leader的server承载了全部的请求压力,因此从集群的整体考虑,有多少个partitions就意味着有多少个"leader",kafka会将"leader"均衡的分散在每个实例上,来确保整体的性能稳定。
Producers
Producer将消息发布到指定的Topic中,同时Producer也能决定将此消息归属于哪个partition;比如基于"round-robin"方式或者通过其他的一些算法等。
Consumers
本质上kafka只支持Topic。每个consumer属于一个consumer group;反过来说,每个group中可以有多个consumer。发送到Topic的消息,只会被订阅此Topic的每个group中的一个consumer消费。
如果所有的consumer都具有相同的group,这种情况和queue模式很像;消息将会在consumers之间负载均衡。
如果所有的consumer都具有不同的group,那这就是"发布-订阅";消息将会广播给所有的消费者。
在kafka中,一个partition中的消息只会被group中的一个consumer消费;每个group中consumer消息消费互相独立;我们可以认为一个group是一个"订阅"者,一个Topic中的每个partions,只会被一个"订阅者"中的一个consumer消费,不过一个consumer可以消费多个partitions中的消息。kafka只能保证一个partition中的消息被某个consumer消费时,消息是顺序的。事实上,从Topic角度来说,消息仍不是有序的。
Kafka的设计原理决定,对于一个topic,同一个group中不能有多于partitions个数的consumer同时消费,否则将意味着某些consumer将无法得到消息。
Guarantees
Kafka就比较适合高吞吐量并且允许少量数据丢失的场景,如果非要保证“消息可靠传输”,可以使用JMS。
Kafka Producer 消息发送有两种方式(配置参数 producer.type):
对于同步方式(producer.type=sync)?Kafka Producer 消息发送有三种确认方式(配置参数 acks):
kafka的设计初衷是希望作为一个统一的信息收集平台,能够实时的收集反馈信息,并需要能够支撑较大的数据量,且具备良好的容错能力。
持久性
kafka使用文件存储消息,这就直接决定kafka在性能上严重依赖文件系统的本身特性。且无论任何OS下,对文件系统本身的优化几乎没有可能。文件缓存/直接内存映射等是常用的手段。因为kafka是对日志文件进行append操作,因此磁盘检索的开支是较小的;同时为了减少磁盘写入的次数,broker会将消息暂时buffer起来,当消息的个数(或尺寸)达到一定阀值时,再flush到磁盘,这样减少了磁盘IO调用的次数。
性能
需要考虑的影响性能点很多,除磁盘IO之外,我们还需要考虑网络IO,这直接关系到kafka的吞吐量问题。kafka并没有提供太多高超的技巧;对于producer端,可以将消息buffer起来,当消息的条数达到一定阀值时,批量发送给broker;对于consumer端也是一样,批量fetch多条消息。不过消息量的大小可以通过配置文件来指定。对于kafka broker端,似乎有个sendfile系统调用可以潜在的提升网络IO的性能:将文件的数据映射到系统内存中,socket直接读取相应的内存区域即可,而无需进程再次copy和交换。 其实对于producer/consumer/broker三者而言,CPU的开支应该都不大,因此启用消息压缩机制是一个良好的策略;压缩需要消耗少量的CPU资源,不过对于kafka而言,网络IO更应该需要考虑。可以将任何在网络上传输的消息都经过压缩。kafka支持gzip/snappy等多种压缩方式。
生产者
负载均衡: producer将会和Topic下所有partition leader保持socket连接;消息由producer直接通过socket发送到broker,中间不会经过任何“路由层“。事实上,消息被路由到哪个partition上,有producer客户端决定。比如可以采用“random““key-hash““轮询“等,如果一个topic中有多个partitions,那么在producer端实现“消息均衡分发“是必要的。
其中partition leader的位置(host:port)注册在zookeeper中,producer作为zookeeper client,已经注册了watch用来监听partition leader的变更事件。
异步发送:将多条消息暂且在客户端buffer起来,并将他们批量的发送到broker,小数据IO太多,会拖慢整体的网络延迟,批量延迟发送事实上提升了网络效率。不过这也有一定的隐患,比如说当producer失效时,那些尚未发送的消息将会丢失。
消费者
consumer端向broker发送“fetch”请求,并告知其获取消息的offset;此后consumer将会获得一定条数的消息;consumer端也可以重置offset来重新消费消息。
在JMS实现中,Topic模型基于push方式,即broker将消息推送给consumer端。不过在kafka中,采用了pull方式,即consumer在和broker建立连接之后,主动去pull(或者说fetch)消息;这中模式有些优点,首先consumer端可以根据自己的消费能力适时的去fetch消息并处理,且可以控制消息消费的进度(offset);此外,消费者可以良好的控制消息消费的数量,batch fetch。
其他JMS实现,消息消费的位置是有prodiver保留,以便避免重复发送消息或者将没有消费成功的消息重发等,同时还要控制消息的状态。这就要求JMS broker需要太多额外的工作。在kafka中,partition中的消息只有一个consumer在消费,且不存在消息状态的控制,也没有复杂的消息确认机制,可见kafka broker端是相当轻量级的。当消息被consumer接收之后,consumer可以在本地保存最后消息的offset,并间歇性的向zookeeper注册offset。由此可见,consumer客户端也很轻量级。
对于JMS实现,消息传输担保非常直接:有且只有一次(exactly once)。
在kafka中稍有不同:
at most once: 消费者fetch消息,然后保存offset,然后处理消息;当client保存offset之后,但是在消息处理过程中出现了异常,导致部分消息未能继续处理。那么此后"未处理"的消息将不能被fetch到,这就是"at most once"。
at least once: 消费者fetch消息,然后处理消息,然后保存offset。如果消息处理成功之后,但是在保存offset阶段zookeeper异常导致保存操作未能执行成功,这就导致接下来再次fetch时可能获得上次已经处理过的消息,这就是"at least once",原因offset没有及时的提交给zookeeper,zookeeper恢复正常还是之前offset状态。
exactly once: kafka中并没有严格的去实现(基于2阶段提交,事务),我们认为这种策略在kafka中是没有必要的。
通常情况下“at-least-once”是我们首选。(相比at most once而言,重复接收数据总比丢失数据要好)。
kafka高可用由多个broker组成,每个broker是一个节点;
创建一个topic,这个topic会划分为多个partition,每个partition存在于不同的broker上,每个partition就放一部分数据。
kafka是一个分布式消息队列,就是说一个topic的数据,是分散放在不同的机器上,每个机器就放一部分数据。
在0.8版本以前,是没有HA机制的,就是任何一个broker宕机了,那个broker上的partition就废了,没法写也没法读,没有什么高可用性可言。
0.8版本以后,才提供了HA机制,也就是就是replica副本机制。每个partition的数据都会同步到其他的机器上,形成自己的多个replica副本。然后所有replica会选举一个leader出来,那么生产和消费都跟这个leader打交道,然后其他replica就是follower。
写的时候,leader会负责把数据同步到所有follower上去,读的时候就直接读leader上数据即可。
kafka会均匀的将一个partition的所有replica分布在不同的机器上,从而提高容错性。
如果某个broker宕机了也没事,它上面的partition在其他机器上都有副本的,如果这上面有某个partition的leader,那么此时会重新选举一个新的leader出来,大家继续读写那个新的leader即可。这就有所谓的高可用性了。
写数据的时候,生产者就写leader,然后leader将数据落地写本地磁盘,接着其他follower自己主动从leader来pull数据。一旦所有follower同步好数据了,就会发送ack给leader,leader收到所有follower的ack之后,就会返回写成功的消息给生产者。
消息丢失会出现在三个环节,分别是生产者、mq中间件、消费者:
RabbitMQ
Kafka
大体和RabbitMQ相同。
Rabbitmq
需要保证顺序的消息投递到同一个queue中,这个queue只能有一个consumer,如果需要提升性能,可以用内存队列做排队,然后分发给底层不同的worker来处理。
Kafka
写入一个partition中的数据一定是有序的。生产者在写的时候 ,可以指定一个key,比如指定订单id作为key,这个订单相关数据一定会被分发到一个partition中去。消费者从partition中取出数据的时候也一定是有序的,把每个数据放入对应的一个内存队列,一个partition中有几条相关数据就用几个内存队列,消费者开启多个线程,每个线程处理一个内存队列。
消息队列之zeroMQ、rabbitMQ、kafka首先消息是网络通讯的载体,队列可以理解是一种先进先出的数据结构,消息队列是存放消息的容器,是分布式系统中的重要组件。消息队列的优势在于:解耦、异步、削峰,把相关性不
强的模块独立分开视为解耦,异步就是非必要逻辑异步方式处理,加快响应速度,削峰是避免短期高并发导致系统问题进行缓冲队列处理。消息队列的缺点在于:加强系统复杂性、系统可用性降低,使
用了消息队列系统出现问题排查的范围就变大、需要考虑消息队列导致的问题。
本文说明主流的消息队列,针对使用过的zeroMQ和rabbitMQ、Kakfa:
zeroMQ :C语言开发,号称最快的消息队列,本着命名zero的含义,中油中间架构使用简单,表面上是基于socket的封装套接字API,在多个节点应用场景下非常灵活、架构的可扩展性很强,
实现N到M的协同处理;
zmq的socket模式: req、rep、push、pull、pub、sub、router、dealer。
(1)req和rep:请求回应模型,req和rep都可以请求和回答,不同的只是req是先send再rec,rep是先rec再send。支持N个请求端一个接收端,也支持N个接收端一个请求端。N个接收端采
用rr负载均衡。 哪个是“一”端,哪个就bind端口,“N”端就只能connect,所以,req+rep无论谁bind端口,肯定要有一个是“一”。
(2) router和dealer:随时可以发送和接收的req和rep,看起来router+dealer跟 req+rep属于同类功能。因为router和dealer可以随时发送接收,所以它们可以用来做路由。一个router用来响
应N个req,然后它在响应处理的时候,再通过另一个socket把请求扔出去,接收者是另外的M个rep,这就做到N:M。
(3)pub和sub :订阅和推送,对应发布者和订阅者。
(4)push和pull:就是管道,一个只推数据,一个只拉数据。
rabbitMQ :使用erlang语言开发,高并发特点,基于AMQP(即Advanced Message Queuing Protocol)的开源高级消费队列,AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/
订阅)、可靠性、安全),企业级适应性和稳定性,并且有WEB管理界面方便用户查看和管理。以下是rabbitMQ的结构图:
(1)Producer:数据发送方,一般一个Message有两个部分:payload(有效载荷)和label(标签),payload是数据实际载体,label是exchange的名字或者一个tag,决定发给哪个Consumer;
(2)Exchange: 内部 消息交换器,exchange从生产者那收到消息后,一般会指定一个Routing Key,来指定这个消息的路由规则,当然Routing Key需要与Exchange Type及Binding key联合使用
才能最终生效,根据路由规则,匹配查询表中的routing key,分发消息到queue中;
(3)binding:即绑定,绑定(Binding)Exchange与Queue的同时,一般会指定一个Binding key,但不一定会生效,依赖于Exchange Type;
(4)Queue:即队列是rabbitmq内部对象,用于存储消息,一个message可以被同时拷贝到多个queue中,queue对load balance的处理是完美的。对于多个Consumer来说,RabbitMQ 使用循
环的方式(round-robin)的方式均衡的发送给不同的Consumer;
(5)Connection与Channel: Connection 就是一个TCP的连接,Producer和Consumer都是通过TCP连接到RabbitMQ Server, Channel 是为了节省开销建立在上述的TCP连接中的接口,大部
分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等;
(6)Consumer:即数据的接收方,如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者;
(7)Broker: 即RabbitMQ Server,其作用是维护一条从Producer到Consumer的路线,保证数据能够按照指定的方式进行传输;
(8)Virtual host:即虚拟主机,当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange/queue;
rabbitMQ消息转发中的路由转发是重点,生产者Producer在发送消息时,都需要指定一个RoutingKey和Exchange,Exchange收到消息后可以看到消息中指定的RoutingKey,再根据当前
Exchange的ExchangeType,按一定的规则将消息转发到相应的queue中去。三种Exchage type:
(1)Direct exchange :直接转发路由,原理是通过消息中的routing key,与binding 中的binding-key 进行比对,若二者匹配,则将消息发送到这个消息队列;
比如:消息生成者生成一个message(payload是1,routing key为苹果),两个binding(binding key分别为苹果、香蕉);exchange比对消息的routing key和binding key后,将消息发给了queue1,消息消费者1获得queue1的消息;
(2)Topic exchange: 通配路由,是direct exchange的通配符模式,
比如:消息生成者生成一个message(payload是1,routing key为quick.orange.rabbit),两个binding(binding key分别为*.orange. 、 *.*.rabbit);exchange比对消息的routing key和binding key
后,exchange将消息分发给两个queue,两个消费者获得queue的消息;
(3)Fanout exchange: 复制分发路由,原理是不需要routkey,当exchange收到消息后,将消息复制多份转发给与自己绑定的消息队列,
比如:消息生成者生成一个message(payload是1,routing key为苹果),两个binding(binding key分别为苹果、香蕉);exchange将消息分发给两个queue,两个消费者获得queue的消息;
rabbiMQ如何保证消息的可靠性?
(1)Message durability:消息持久化,非持久化消息保存在内存中,持久化消息写入内存同时也写入磁盘;
(2)Message acknowledgment:消息确认机制,可以要求消费者在消费完消息后发送一个回执给RabbitMQ,RabbitMQ收到消息回执(Message acknowledgment)后才将该消息从Queue中移
除。通过ACK。每个Message都要被acknowledged(确认,ACK)。
(3)生产者消息确认机制:AMQP事务机制、生产者消息确认机制(publisher confirm)。
最后, 对比一下zeroMQ、rabbitMQ、kafka主流的消息队列的性能情况:
对比方向 概要
吞吐量 万级 RabbitMQ 的吞吐量要比 十万级甚至是百万级Kafka 低一个数量级。ZeroMQ号称最快的消息队列系统,尤其针对大吞吐量的需求场景。
可用性 都可以实现高可用。RabbitMQ 都是基于主从架构实现高可用性。 kafka 也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
时效性 RabbitMQ 基于erlang开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。其他两个个都是 ms 级。
功能支持 Kafka 功能较为简单,主要支持简单的MQ功能,在大数据领域实时计算以及日志采集被大规模使用;ZeroMQ能够 实现RabbitMQ不擅长的高级/复杂 的队列
消息丢失 RabbitMQ有ack模型,也有事务模型,保证至少不会丢数据, Kafka 理论上不会丢失,但不排除批量情况下。
开发环境 RabbitMQ需要erlang支持、kafka基于zookeeper管理部署、zeroMQ程序编译调用即可
封装库 基于c++开发,使用RabbitMQ-C,cppKafka,而zeroMQ基于C语言开发,无需封装

立即
返回
1
目录:1、阿里云香港云服务器是直连大陆吗2、阿里云买的域名,想解析到香港的服务器,线路怎么选?3、阿里云香港服务器怎么样,想做...