从放弃到入门-消息队列

468 阅读8分钟

消息队列中队列模型与发布/订阅模式的区别

JMS规范目前支持两种消息模型:点对点(point to point, queue)和发布/订阅(publish/subscribe,topic)。

  • 队列模型 消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息。这里要注意:
    消息被消费以后,queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息。
    Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
  • 发布/订阅
    消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。
    queue实现了负载均衡,一个消息只能被一个消费者接受,当没有消费者可用时,这个消息会被保存直到有一个可用的消费者,一个queue可以有很多消费者,他们之间实现了负载均衡, 所以Queue实现了一个可靠的负载均衡。
    topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到一个消息的拷贝, 只有在消息代理收到消息时有一个有效订阅时的订阅者才能得到这个消息的拷贝。
  • RabbitMQ RabbitMQ实现了AQMP协议,AQMP协议定义了消息路由规则和方式。生产端通过路由规则发送消息到不同queue,消费端根据queue名称消费消息。此外RabbitMQ是向消费端推送消息,订阅关系和消费状态保存在服务端。
    在队列模式下,生产端发送一条消息通过路由投递到Queue,只有一个消费者能消费到。
    在发布订阅模式下,当RabbitMQ需要支持多订阅时,发布者发送的消息通过路由同时写到多个Queue,不同订阅组消费此消息。
    RabbitMQ既支持内存队列也支持持久化队列,消费端为推模型,消费状态和订阅关系由服务端负责维护,消息消费完后立即删除,不保留历史消息。所以支持多订阅时,消息会多个拷贝。
  • Kafka Kafka只支持消息持久化,消费端为拉模型,消费状态和订阅关系由客户端端负责维护,消息消费完后不会立即删除,会保留历史消息。因此支持多订阅时,消息只会存储一份就可以了。

延迟队列

rabbitmq两种实现方式:

  1. q里的每条消息都有不同的延迟时间complex: 设置message的x-delay header决定单条消息的延迟,精度是[0, 64]ms;
  2. 整个q里的所有消息延迟相同immutable : 设置queue的x-delay argument决定所有消息的延迟,精度是[0, 100]ms;

Delay Queue原理:

  1. complex模式
    这种策略是通过FunnelQueue(q/util/queue/funnelqueue.go) 这个结构体实现的,一个FunnelQueue分为N个(complex是20个) Level。其中每个Level有两个主要的属性:LevelID 和 Interval。每个消息进入FunnelQueue的时候会根据消息的延迟时间(Deadline)计算出它所对应的Level (Policy.GetLevel)。
    比如:一个消息延迟80s,那么它的Level是7。如果是刚好64s,那也是7。如果是[32, 64)之间,则是6,依次类推。 不同的Level对应不同的Interval。
    interval的作用是用来确定这个Level的检查时间,每个Level都有一个单独的goroutine,间断的对当前Level下的消息进行检查。如Level6每隔32s检查一次。检查消息时,会根据消息延迟的时间将消息放入下一层队列。
    如:一条消息进入Level6的时候是延迟39s,但是在这次检查时,这条消息距离它进入Level的时候已经过了32s,这个时候它还剩7s的延迟,经过计算,应该将它放入level3。 最后,如果计算出某条消息已经达到了需要延迟的时间,GetLevel会返回一个LevelID为负数的Level,此时将消息放入一个final list。
  • Level的设计细节
    一个level是一个dividedList,之所以叫这个名字,是因为它被设计成两个带锁的List,一个output,一个input。每次检查的时候,都是先将input队列和output队列合并(这个操作非常快,可以尽量减少占用input队列的锁的时间,不会造成上层的level或者publish频繁长时间等锁),然后再检查output队列的消息的剩余延迟,并将消息下放到下层的Level。
  1. immutable设计 immutable的设计就比较“暴力”了(q/util/queue/ImmutableDelayQueue),因为队列里所有的消息的延迟都是相同的,符合FIFO,所以immutable被设计成两个队列,一个input(dividedList,两个带锁的list),一个output(简单的带锁的List)。 算法流程: 一个单独的goroutine检查/计算 dividedList 头部的消息的延迟(每次检查的时候会执行dividedList的合并操作),如果已经超时了,则放入output对队尾。否则如果剩余的等待时间超过100ms,则等待剩余的时间再check,少于100ms则等待100ms。

系统间通信方式

基于文件 基于共享内存 基于IPC 基于Socket 基于数据库 基于RPC

缺点

  • 文件: 明显不方便,不及时
  • Socket:使用麻烦,多数情况下不如RPC
  • 数据库:不实时,但是经常有人拿数据库来模拟消息队列
  • RPC:调用关系复杂,同步处理,压力大的时候无法缓冲

期望的通信方式

  • 可以实现异步的消息通信
  • 可以简化参与各方的复杂依赖关系
  • 可以在请求量很大的时候,缓冲一下 > 类比线程池里的Queue
  • 某些情况下能保障消息的可靠性,甚至顺序

【消息队列】应运而生

MQ的四大特性

  • 异步通信:异步通信,减少线程等待,特别是处理批量等大事务、耗时操作。
  • 系统解耦:系统不直接调用,降低依赖,特别是不在线也能保持通信最终完成。
  • 削峰平谷:压力大的时候,缓冲部分请求消息,类似于背压处理。
  • 可靠通信:提供多种消息模式、服务质量、顺序保障等。

消息处理模式

常见的有两种消息模式:

  • 点对点:PTP,Point-To-Point 对应于Queue
  • 发布订阅:PubSub,Publish-Subscribe, 对应于Topic

MQ的保障

三种QoS(注意:这是消息语义的,不是业务语义的):

  • At most once,至多一次,消息可能丢失但是不会重复发送;
  • At least once,至少一次,消息不会丢失,但是可能会重复;
  • Exactly once,精确一次,每条消息肯定会被传输一次且仅一次。 消息处理的事务性:
  • 通过确认机制实现事务性;
  • 可以被事务管理器管理,甚至可以支持XA。

消息有序性

同一个Topic或Queue的消息,保障按顺序投递。 注意:如果做了消息分区,或者批量预取之类的操作,可能就没有顺序了。

开源三代MQ

  1. ActiveMQ/RabbitMQ
  2. Kafka/RocketMQ
  3. Apache Pulsar

什么是Kafka

Kafka 是一个消息系统,由 LinkedIn 于2011年设计开发,用作 LinkedIn 的活动流 (Activity Stream)和运营数据处理管道(Pipeline)的基础。
Kafka 是一种分布式的,基于发布 / 订阅的消息系统。主要设计目标如下:

  1. 以时间复杂度为 O(1) 的方式提供消息持久化能力,即使对 TB 级以上数据也能保证 常数时间复杂度的访问性能。
  2. 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒 100K 条以上消息 的传输。
  3. 支持 Kafka Server 间的消息分区,及分布式消费,同时保证每个 Partition 内的消 息顺序传输。
  4. 同时支持离线数据处理和实时数据处理。
  5. Scale out:支持在线水平扩展。

Kafka的基本概念

  1. Broker:Kafka 集群包含一个或多个服务器,这种服务器被称为 broker。
  2. Topic:每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。 (物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或 多个 broker 上,但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数 据存于何处)。
  3. Partition:Partition 是物理上的概念,每个 Topic 包含一个或多个 Partition。
  4. Producer:负责发布消息到 Kafka broker。
  5. Consumer:消息消费者,向 Kafka broker 读取消息的客户端。
  6. Consumer Group:每个 Consumer 属于一个特定的 Consumer Group(可为每个 Consumer 指定 group name,若不指定 group name 则属于默认的 group)。

Kafka 生产者-确认模式

ack=0 : 只发送不管有没有写入到broker
ack=1:写入到leader就认为成功
ack=-1/all:写入到最小的副本数则认为成功

Kafka 生产者-同步发送

Kafka 生产者-异步发送

Kafka 生产者-顺序保证

Kafka 生产者-消息可靠性传输

Kafka 消费者-Consumer Group

Kafka 消费者-Offset同步提交

Kafka 消费者-Offset异步提交

Kafka 消费者-Offset自动提交

Kafka 消费者-Offset Seek

Pulsar

本文整理自网络