消息队列概述
消息队列一般简称为 MQ (Messges Queue),是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成,是在消息的传输过程中保存消息的容器。消息队列本质上是一个队列,而队列中存放的是一个个消息。这个队列需要支持高吞吐、高并发、并且高可用。
队列是一个数据结构,具有先进先出的特点。而消息队列就是将消息放到队列里,用队列做存储消息的介质。消息的发送方称为生产者,消息的接收方称为消费者。
消息队列由 Broker(消息服务器,核心部分)、Producer(消息生产者)、Consumer(消息消费者)、Topic(主题)、Queue(队列)和Message(消息体)组成。
消息队列的特点以及应用场景
消息队列主要用于将项目中一些无需即时返回且耗时的操作提取出来,进行异步处理。通过这种异步处理的方式能够大大节省服务器的请求响应时间,提升服务器的吞吐量。
消息队列依据其特点有以下应用场景:
-
流量削峰:主要用于在高并发情况下,业务异步处理,提供高峰期业务处理能力,避免系统瘫痪。
假设系统只能处理1000个请求,但这时突然来了3000个请求,如果不加以限制就会造成系统瘫痪。使用消息队列做缓冲,将多余的请求存放在消息队列中,等系统根据自己处理请求的能力去消息队列去。
-
应用解耦:主要用于当一个业务需要多个模块共同实现,或者一条消息有多个系统需要对应处理时,只需要主业务完成以后,发送一条MQ,其余模块消费MQ消息,即可实现业务,降低模块之间的耦合。
假设某个服务 A 需要调用服务 B,但是服务 B 突然出现问题,这样会导致服务 A 也会出现问题。如果使用消息队列,当服务 A 执行完成之后,发送一条消息到队列中,服务 B 读取到这条消息,那么它立刻开始进行业务的执行。
-
异步通信:主业务执行结束后从属业务通过MQ,异步执行,减低业务的响应时间,提高用户体验。
假设有一个业务,要先执行服务 A ,然后服务 A 去调用服务 B ,当服务 B 完成之后,服务 A 调用服务 C,这个业务需要一步步走下去。当使用了消息队列之后,服务 A 完成之后,可以同时执行服务 B 和 服务 C ,这样就减低业务的响应时间,提高用户体验。
-
排序保证:消息队列能够控制数据处理的顺序。
消息队列使用的是队列这个数据结构,具有先进先出的特性,这使得消息队列能够保证一些场景下数据处理的顺序,如商品下单顺序等。
消息队列的传输模式
一个消息系统负责将数据从一个应用传递到另外一个应用,应用只需关注于数据,无需关注数据在两个或多个应用间是如何传递的。分布式消息传递基于可靠的消息队列,在客户端应用和消息系统之间异步传递消息。有两种主要的消息传递模式:点对点传递模式、发布-订阅模式。
-
点对点传递模式:用于消息生产者和消息消费者之间点对点的通信,生产者发送一条消息到queue,只有一个消费者能收到。
在点对点消息系统中,消息持久化到一个队列中。此时,将有一个或多个消费者消费队列中的数据。但是一条消息只能被消费一次。当一个消费者消费了队列中的某条数据之后,该条数据则从消息队列中删除。该模式即使有多个消费者同时消费数据,也能保证数据处理的顺序。这种架构描述示意图如下:
-
发布-订阅消息传递模式:发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息,在这种情况下发布者和订阅者彼此不知道。
在发布-订阅消息系统中,消息被持久化到一个topic中。与点对点消息系统不同的是,消费者可以订阅一个或多个topic,消费者可以消费该topic中所有的数据,同一条数据可以被多个消费者消费,数据被消费后不会立马删除。在发布-订阅消息系统中,消息的生产者称为发布者,消费者称为订阅者。该模式的示例图如下:
有两种订阅类型:
- 持久订阅:订阅关系建立后,消息就不会消失,不管订阅者是否都在线;
- 非持久订阅:订阅者为了接受消息,必须一直在线。 当只有一个订阅者时约等于点对点模式
常用的消息队列
-
Kafka:分布式的、分区的、多副本的日志提交服务,在高吞吐量场景下发挥出色。
优点:单机吞吐量每秒百万级,时效性毫秒级,不会丢失数据,不会导致不可用。
缺点:支持消息顺序,但是一台代理宕机后,就会产生消息乱序;消费失败不支持重试;社区更新较慢。
-
RocketMQ:阿里系下开源的一款分布式、队列模型的消息中间件,低延迟、强一致性、高性能、高可靠、万亿级容量和灵活的可扩展性,广泛用于实时场景中。
优点:单机吞吐量十万级,时效性毫秒级,消息可以做到 0 丢失,支持 10 亿级别的消息堆积。
缺点:支持的客户端语言不多,目前是 java 及 c++;社区活跃度一般。
-
Pulsar:下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体、采用存算分离的架构设计。
优点:支持多租户、持久化存储、多机房跨区域数据复制,具有强一致性、高吞吐、低延时及高可扩展性等流数据存储特性。
缺点:相对缺乏支持、文档和案例;n 层体系结构导致需要更多组件:BookKeeper;插件和客户端相对 Kafka 较少;云中的支持较少,Confluent 具有托管云产品。
-
BMQ:和Pulsar架构类似,存算分离,初期定位是承接高吞吐的离线业务场景,逐步替换掉队员的Kafka集群。
Kafka
体系架构
ZooKerper:是一个分布式的协调服务,主要用于维护集群的元数据信息和配置信息。
- 管理Kafka broker节点的状态信息,包括broker的上-下线状态、主题分区信息、副本信息等;
- 存储和管理Kafka集群的元数据信息和配置信息,包括broker的IP地址、端口号、主题分区的分配方案等;
- 通过监控Kafka broker节点的状态信息,帮助Kafka集群实现自动故障转移和负载均衡等功能。
Producer:消息生产者,就是向 kafka broker 发消息的客户端。
- 生产者即数据的发布者,该角色将消息发布到Kafka的topic中。broker接收到生产者发送的消息后,broker将该消息追加到当前用于追加数据的segment文件中。生产者发送的消息,存储到一个partition中,生产者也可以指定数据存储的partition。
Consumer:消息消费者,向 kafka broker 取消息的客户端。
- 消费者可以从broker中读取数据。消费者可以消费多个topic中的数据。
Consumer Group:用于实现一个Topic消息的广播和单播的手段。
- 每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。
broker:一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。
- Kafka 集群包含一个或多个服务器,服务器节点称为broker。
- broker存储topic的数据。如果某topic有N个partition,集群有N个broker,那么每个broker存储该topic的一个partition。
- 如果某topic有N个partition,集群有(N+M)个broker,那么其中有N个broker存储该topic的一个partition,剩下的M个broker不存储该topic的partition数据。
- 如果某topic有N个partition,集群中broker数目少于N个,那么一个broker存储该topic的一个或多个partition。在实际生产环境中,尽量避免这种情况的发生,这种情况容易导致Kafka集群数据不均衡。
Topic:Kafka中的逻辑队列,可以理解成每一个不同的业务场景就是一个Topic,一个队列,一个 Topic 又分为一个或多个分区
- 每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)
- 类似于数据库的表名
Partition:实现消息队列拓展性的有序队列。
- topic中的数据分割为一个或多个partition(分区)。每个topic至少有一个partition。每个partition中的数据使用多个segment文件存储。partition中的数据都会被分配一个有序的id(offset),不同partition间的数据丢失了数据的顺序。如果topic有多个partition,消费数据时就不能保证数据的顺序。在需要严格保证消息的消费顺序的场景下,需要将partition数目设为1。
- 由于消息是以追加到分区中的,多个分区顺序写磁盘的总效率要比随机写内存还要高,是Kafka高吞吐率的 重要保证之一。
- 分区对于 Kafka 集群的好处是:实现负载均衡。分区对于消费者来说,可以提高并发度,提高效率。
- Kafka 不支持减少分区数。减少分区的操作可能会破坏有序性。
Offset:消息在partition内的相对位置信息,可以理解为唯一ID,在partition内部严格递增。
- kafka 的存储文件都是按照 offset.kafka 来命名,用 offset 做名字的好处是方便查找。例如你想找位于 2049 的位置,只要找到 2048.kafka 的文件即可。当然 the first offset 就是 00000000000.kafka。对于每一个partition来说,每一条消息都有唯一一个offset。
Replica:分片的副本,分布在不同机器上,每个partition至少有一个replication。
-
replica可用来容灾,Leader对外服务,Follwer异步去拉去leader的数据进行一个同步,如果leader挂掉了,可以将Follower提升成leader再对外进行服务。
-
Leader:每个partition有多个副本,其中有且仅有一个作为Leader,Leader是当前负责数据的读写的partition。
-
Follower:Follower跟随Leader,所有写请求都通过Leader路由,数据变更会广播给所有Follower,Follower与Leader保持数据同步。如果Leader失效,则从Follower中选举出一个新的Leader。当Follower与Leader挂掉、卡住或者同步太慢,leader会把这个follower从“in sync replicas”(ISR)列表中删除,重新创建一个Follower。
ISR:同步中的副本(In-Sync Replicas)
- 是每个partition的replication的一个子集,表示目前处于alive(通过zk确认一个节点是否alive)并且与leader同步的集合。每个partition生产的数据,首先都是落在leader上,然后ISR同步后,认为这次数据成功写入了。对于follower来说,始终是和leader存在一定差距的,当差距较小时,将该follower副本加入ISR中,不在ISR的副本不允许提升成leader。ISR是动态变化的,延迟的时间、延迟的记录数,都会导致某个replication被踢出ISR。
OSR、AR:非同步中的副本(Out-of-Sync Replicas)、所有副本(Assigned Replicas)。
- ISR是由leader维护,follower从leader同步数据有一些延迟,超过相应的阈值会把 follower 剔除出 ISR, 存入OSR列表,新加入的follower也会先存放在OSR中。AR=ISR+OSR。
kafka是如何实现高吞吐量的
Kafka是分布式消息系统,需要处理海量的消息,Kafka的设计是把所有的消息都写入速度低容量大的硬盘,以此来换取更强的存储能力,但实际上,使用硬盘并没有带来过多的性能损失。kafka主要使用了以下几个方式实现了超高的吞吐率:
- 顺序读写;磁盘顺序读或写的速度400M/s,能够发挥磁盘最大的速度。kafka将来自Producer的数据,顺序追加在partition,partition就是一个文件,以此实现顺序写入。Consumer从broker读取数据时,因为自带了偏移量,接着上次读取的位置继续读,以此实现顺序读。顺序读写,是kafka利用磁盘特性的一个重要体现。
- 零拷贝:kafka数据写入磁盘前,数据先写到进程的内存空间。数据直接在内核完成输入和输出,不需要把 内核空间页缓存里的数据拷贝到应用层缓存,再从应用层缓存拷贝到 Socket 缓存了。
- 文件分段:kafka 的队列topic被分为了多个区partition,每个partition又分为多个段segment,所以一个队列中的消息实际上是保存在N多个片段文件中。通过分段的方式,每次文件操作都是对一个小文件的操作,非常轻便,同时也增加了并行处理能力。
- 批量发送:Kafka 允许进行批量发送消息,先将消息缓存在内存中,然后一次请求批量发送出去,比如可以指定缓存的消息达到某个量的时候就发出去,或者缓存了固定的时间后就发送出去,如100条消息就发送,或者每5秒发送一次,这种策略将大大减少服务端的I/O次数。
- 数据压缩:Kafka 还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩,压缩的好处就是减少传输的数据量,减轻对网络传输的压力,Producer压缩之后,在Consumer需进行解压,虽然增加了CPU的工作,但在对大数据处理上,瓶颈在网络上而不是CPU,所以这个成本很值得。