整理了对kafka消息队列的一些理解,和一些实际应用,可能也有一些面试常问的一些点;
前言:
一:消息队列的两种模式
- 点对点模式(一对一)
- 消费者主动拉取数据,收到消息后清除;
- 一个queue支持多个消费者,但是对一个消息来说,只能被一个消费者消费;
- 发布订阅模式(一对多)
- 消费者消费到数据后,不会删除消息;
- 发布给topic中的消息,会被所有的订阅者消费到;
其中kafka是基于发布订阅模式的,主动拉取数据,并消费后不会删除消息;
二:发布订阅模式中又分两种?
1:队列主动推数据(推模式-push):消息中间件主动将消息推送给消费者
2:消费者主动拉数据(拉模式-pull):消费者主动从消息中间件拉去消息
特点:
- 拉取模式特点(pull):
- 拉取模式是消费者主动获取数据的,采用长轮询机制,不间断的过两秒钟去询问,有没有新的消息过来,
- 若是消费者消费不过来,可以根据一定的策略停止拉取,或者间隔拉取;
- 优点:
- 适合批量发送
- 推模式是来一个消息就推,也可以缓存一批消息后推送,但是推模式的缺点也很明显,不清楚消费者的能力和状态,
- 而拉模式可以参考消费者的状态,来决定一次缓存多少消息后批量发送;
- 适合批量发送
- 缺点:
- 消息延迟
- 拉模式是消费者主动拉取数据的,是不清楚什么时候有消息的,所以只能不断地去轮询,但又要控制好轮询的频率,不然生产者压力就很大;
- 耗费性能
- 若是生产者没有消息,消费者还是要不停的去轮询,比较浪费资源
- 消息延迟
- 推送模式特点(push):
- 生产者强制推消息给消费者;
- 消息实时性高,生产者接收到消息后,会立马推送给消费者;
- 对于消费者来说比较简单,只需要等消息推送过来就行了;
- 缺点:
- 推送速率难以适应消费速率
- 推模式的目标就是为了以最快的速度推送消息,当生产者发送消息的速率大于消费者消费消息的速率时,随着时间的增长,消费者可能会挤压很多的消息,导致消费者消费不过来;
- 推送速率难以适应消费速率
- 场景:适用于消息量不大,要求实时性高的场景,根据消费者的状态控制推送速率;
总结:
rabbitmq默认是推模式,但是它也支持拉模式;
rocketMq和kafka都选择了拉模式;而且都是利用“长轮询”来实现的拉模式;
activityMq就是标准的基于推模式的消息队列;
扫描二维码关注公众号,回复: 13515192 查看本文章
1:kafka简单了解
- kafka依赖的环境:java,zookeeper(用于保存broker的元数据信息)
- consumergroup:各个consumer可以组成一个组,每个消息只能被组中的一个consumer消费,如果一个消息可以被多个consumer消费的话,那么这些consumer必须在不同的组。
- kafka 允许多个消费组同时订阅一个topic,不同的消费组可以对同一条消息进行重复消费,并不进行互斥。比如有消费组A和消费组B,都订阅了topic xxx,当生产者C发布一个topic为xxx的消息,消费组A和消费组B都会收到消息。
- 消息状态: 在Kafka中,消息的状态被保存在consumer中,broker不会关心哪个消息被消费了被谁消费了,只记录一个offset值(指向 partition中下一个要被消费的消息位置),这就意味着如果consumer处理不好的话,broker上的一个消息可能会被消费多次。
- 消息持久化:Kafka中会把消息持久化到本地文件系统中,并且保持极高的效率。
- 消息有效期:Kafka会长久保留其中的消息,以便consumer可以多次消费,当然其中很多细节是可配置的。
- kafak不会删除已经消费的消息,而是不管消息是否消费都一并按照broker的配置保留一段时间,如broker配置保留2天,那就统一保留2天
- Kafka集群中broker之间的关系:不是主从关系,各个broker在集群中地位一样,我们可以随意的增加或删除任何一个broker节点。
- 负载均衡方面: Kafka提供了一个 metadata API来管理broker之间的负载(对Kafka0.8.x而言,对于0.7.x主要靠zookeeper来实现负载均衡)。
- 分区机制partition:Kafka的broker端支持消息分区,Producer可以决定把消息发到哪个分区,在一个分区中消息的顺序就是Producer发送消息的顺序,一个主题中可以有多个分区,具体分区的数量是可配置的。
- 一个 topic 可以分布到多个 broker(即服务器)上
- 一个 topic 可以分为多个 partition
- 每个 partition 是一个有序的队列
2:kafka架构
1:kafka集群:broker(一台启动了kafka进程的服务器)
这里是kafka存储数据的地方;存储在topic;
2:Topic:(主题):作为消息的分类,根据业务区分的;
3:一个主题里面有分区和副本;
分区(partition):用来做某一个主题topic的负载均衡;还可以提高读写的效率(从leader中读写数据)
副本(follower):做数据备份冗余;
4:消费者中有一个消费者组(consumer-group):
这里面需要考虑消费者组中的消费者消息的分配策略:
原则一:若:消费者组中有两个消费者,同时订阅了某个topic,那么该topic的消息只能被一个消费者消费;
原则二:若:一个topic被多个消费者组中的某个消费者订阅了,那么这个topic下发的消息是可以被多个消费者消费的(但是在同一个组内的多个消费者遵循第一个原则);
5:生产者消息分配:
当一个topic有多个partition分区的时候,生产者有了一个消息,分配给哪个分区呢,生产者也有所谓的分配策略的;
方式一:直接指定分配给哪一个partition;
方式二:没有指定partition,但是指定key,根据对key的value进行hash,选出一个partition;
方式三:partition和key都没有指定,则轮询选出一个partition;
3:kafka工作流程
首选我们要创建一个topic,用于存储消息的;
这个是官方的一个图,这个图表示有一个topic主题,
这个topic里面有三个分区,
生产者往topic写数据,
从这个图可以看出,每一个分区都维护了自己的偏移量offset;
所以这里有一个面试点:
kafka并不能保证消息的全局有序性,只能在某一个分区里面,是有序的;
3.1:producer发布消息:
- 写入方式
- producer 采用 push 模式将消息发布到 broker,每条消息都被 append 到 patition 中,属于顺序写磁盘(顺序写磁盘效率比随机写内存要高,保障 kafka 吞吐率)。
- 消息路由
-
producer 发送消息到 broker 时,会根据分区算法选择将其存储到哪一个 partition。其路由机制为:
1. 指定了 patition,则直接使用; 2. 未指定 patition 但指定 key,通过对 key 的 value 进行hash 选出一个 patition 3. patition 和 key 都未指定,使用轮询选出一个 patition。
-
- 写入流程
- producer 写入消息序列如下所示:
1. producer 先从 zookeeper 的 "/brokers/.../state" 节点找到该 partition 的 leader 2. producer 将消息发送给该 leader 3. leader 将消息写入本地 log 4. followers 从 leader pull 消息,写入本地 log 后 leader 发送 ACK 5. leader 收到所有 ISR 中的 replica 的 ACK 后,增加 HW(high watermark,最后 commit 的 offset) 并向 producer 发送 ACK
- producer 写入消息序列如下所示:
3.2:broker保存消息
- 存储方式
- 物理上把 topic 分成一个或多个 patition(对应 server.properties 中的 num.partitions=3 配置),每个 patition 物理上对应一个文件夹(该文件夹存储该 patition 的所有消息和索引文件)
- 存储策略
-
无论消息是否被消费,kafka 都会保留所有消息。有两种策略可以删除旧数据:
1. 基于时间:log.retention.hours=168 2. 基于大小:log.retention.bytes=1073741824
需要注意的是,因为Kafka读取特定消息的时间复杂度为O(1),即与文件大小无关,所以这里删除过期文件与提高 Kafka 性能无关
-
4:文件存储
当然这个图有个少的:
其实这里是有三个文件的:
- .log存储消息文件
- .index存储消息的索引
- .timeIndex,时间索引文件,通过时间戳做索引
在kafka中每个topic可以指定多个分区(partition),
而每个partition又可以分为多个segment file:
当生产者往partition中存储数据时,内存中存不下了,就会往segment file里面存储。
为防止log文件过大导致数据定位效率低,kafka采取了分片和索引机制,将每个partition分为多个segment(逻辑上的概念,index+log文件)
每个segment file对应两个文件,分别是以【.log】结尾的数据文件和以【.index】结尾的索引文件。在服务器上,每个partition是一个文件夹,每个segment是一个文件。
每个segment file也有自己的命名规则,每个名字有20个字符,不够用0填充。每个名字从0开始命名,下一个segment file文件的名字就是,上一个segment file中最后一条消息的索引值。在.index文件中,存储的是key-value格式的,key代表在.log中按顺序开始第条消息,value代表该消息的位置偏移。但是在.index中不是对每条消息都做记录,它是每隔一些消息记录一次,避免占用太多内存。即使消息不在index记录中,在已有的记录中查找,范围也大大缩小了。
kafka的配置文件:service.properties 配置了一些常见的配置
log.rerention.hours = 168 (这个就是配置了日志文件存在的时间:168小时,也就是7天)
log.segment.bytes = 1072741824 (日志文件最大值:1个G,表示每个segment file文件最大为一个G,超过一个G,再创建一个新的)
message.maxbytes = 6525000 (消息体的最大大小(6M左右),单位是字节)
在segment中查找数据的过程。。。。。。。。。。。。。。
二分查找?索引?偏移量?
百家争鸣,取长补短之道:
kafka工作流:
kafka文件存储机制:
-
-
kafka配置参数解释:
kafka常见生产问题: