Kafka设计

101 阅读26分钟

1. kafka架构图

image.png

1.1. broker内部模块说明:

  1. KafkaController,controller控制器,监听zk并指挥broker操作

  2. SocketServer, 网络模块,接收和发送数据

  3. KafkaApis,分发请求,类似于spring mvc

  4. ReplicaManager,Partition及其replica的管理,设置leader partition等操作

  5. LogManager,日志文件操作相关,比如kafka-log-retention,kafka-log-flusher,kafka-delete-logs等及创建目录,具体读取log,写log由Log代理

  6. GroupCoordinator , 分组管理,消费者join及unjoin管理group

  7. KafkaHealthcheck ,通过注册监听器向zk注册自己的消息

  8. KafkaScheduler , 实际上就是ScheduledThreadPoolExecutor

  9. AdminManager, 向zk发送创建topic、删除topic、穿创建partition,更新和查询配置,给kafkaapis模块使用

  10. TransactionCoordinator,事务管理

  11. DynamicConfigManager,配置管理

  12. Authorizer,身份和权限认证

1.2. kafka的目录结构

文件说明:

  1. cleaner-offset-checkpoint: 记录全部partition的cleaned offset,cleaned offset用于计算非活跃segment的dirtyRatio(log head和log tail的比例),dirtyRatio在日志压缩的模块会被使用

  2. recovery-point-offset-checkpoint: 记录每个partition最新刷盘的message的offset

  3. replication-offset-checkpoint: 记录每个partition最后一条被成功备份到副本的消息的offset

  4. log-start-offset-checkpoint: 记录每个partition的第一条(earliest)信息的offset

  5. meta.properties: 记录cluster或者broker的元数据如clusterId and brokerId.

  6. log日志:

    1. Kafka 分区目录由 topic名称+partition序号 组成
    2. 分区目录下是日志数据,由segment组成,segment的名称当前segment第一条数据的offset,固定长度20位,不足补0

1.3. zookeeper中kafka的数据结构

cwiki.apache.org/confluence/…

2. kafka设计

2.1.设计目标

  1. 高吞吐

  2. 低延迟

  3. 处理大量数据积压,支持来自离线系统的定期数据加载(持久化 、成本、读写性能优化、数据压缩)

  4. 对数据分区、分布式、实时处理,且能够基于数据派生新数据(激发分区、和消费者模型)

  5. 容错,在机器挂掉的情况下,数据仍然可以正确传递给其他服务(副本设计)

基于上面的目标,kafka的使用了一些特有的设计元素,比起传统的消息系统,这些设计让系统看起来更有点像数据库日志

2.2.持久化

2.2.1. kafak和文件系统

比起内存够读写,磁盘读写非常慢,既然要设计高吞吐、低延迟的系统,为什么kafka还要选择将数据存储在磁盘上而不是内存?

有几点原因:

磁盘随机读写很慢,但磁盘顺序读写速度很快,对于由6个7200转每分钟(RPM)的SATA硬盘组成的RAID-5阵列(Six 7200rpm SATA RAID-5 array)

  1. 顺序写入速度大约600 MBps,即每秒写入600兆字节的数据
  2. 随机写入速度大于100 KBps, 即每秒写入100字节数据

  原因是顺序读写提供了可预测的使用模式,操作系统利用这种模式对磁盘做了重点优化,即预读和延迟写:利用这种技术操作系统提前读取数倍大块的数据,并将较小的逻辑写入组合成大块的物理写入,以实现顺序的读写操作。

提示:某些情况下,顺序的磁盘访问可能比随机的内存访问更快,下图是2009年的一个测试

deliveryimages.acm.org/10.1145/157…

自测(自己mac电脑 4C16G)

内存随机/顺序读写20MB,随机写速度115MB/s

磁盘随机/顺序写 500MB数据,磁盘随机写入速度 5-10MB/s,顺序写入速度186MB/s,大于内存随机写115MB/s

磁盘顺序读 500MB数据,顺序读速度256MB/s

磁盘随机读 500M数据,随机读速度15MB/s

  1. 操作系统会将所有空闲的内存用作磁盘缓存,且当内存被回收时,回收内存的操作对系统的影响极小,所有的读写都会经过这些缓存再到磁盘,操作系统的这个特性很难被关闭,即使在应用中使用了程序级别缓存,缓存中的数据还会在操作系统的page cache中进行二次缓存

代码演示:一边写入数据,一边读取数据,读取数据时,数据基本上从缓存读取,很少读磁盘

2.2.2. kafka与JVM

kafka运行在JVM之上,JVM的内存有两个特点

  1. 对象占用的内存很大,通常是被存储数据本身大小的的两倍甚至更多

  2. 随着堆内存的增加,垃圾回收会变得越来越繁琐和缓慢

基于上面的缺陷,kafka采用文件系统和pagecache存储数据,数据存储在pagecache中如同访问内存,而且数据直接存储在pagecache而不是堆内存,可以获取2倍甚至更多的存储空间,一台32G内存的机器,能够使用到的内存可达28-30G,另外还有一个好处,如果kafka重启,pagecache缓存的数据还是热数据,如果存储在堆内存中,服务重启后,需要重建内存。

另外,数据写入磁盘时,kafka会将数据直接写入到文件,但是不会flush将数据刷入磁盘,这样数据会被写入pagecache,而pagecache与磁盘的同步由操作系统完成,这里有个问题:因为没有主动flush数据到磁盘,kafka如何确保数据不丢失?

2.2.3. 恒定的索引查询时间(索引的数据结构)

传统的消息队列一般采用BTrees数据结构存储数据的元数据

BTrees

优点:

  1. 通用的数据结构,适合多种场景,时间复杂度是O(log N),O(log N)可认为是恒定时间

缺点:

  1. 操作成本很高,在相同的内存下,数据增长一倍,性能下降两倍甚至更多

  2. 实现复杂度高

队列

kafka采用队列存储数据和元数据

优点

  1. 写入数据时,直接将数据追加到队列末尾,所以写数据的时间复杂度是O(1),而且读和写两个操作不用相互加锁
  2. 读元数据的时间复杂度时间复杂度是O(log N),但是因为索引序号是递增,可以采用二分法对数据进行查询,且索引数据可以映射到内存中,读取速度也极快
  3. 索引实现简单
  4. kafka利用上面的这些优点,可以在一台服务器上使用多个1+TB SATA 硬盘,对于超大的读写数据场景下,可以到达可接受的性能表现,利用1/3的价格实现3倍的存储容量

2.2.4. 传输效率提升

数据传输和数据读写存在两个性能瓶颈

  1. 太多的小 I/O 操作
  2. 太频繁字节码复制

developer.ibm.com/articles/j-…

  1. 操作系统将数据从磁盘读入内核空间的页面缓存

  2. 应用程序将数据从内核空间读入用户空间缓冲区

  3. 应用程序将数据写回到内核空间的套接字缓冲区

  4. 操作系统将数据从套接字缓冲区复制到 NIC 缓冲区,然后通过网络发送

涉及的上下文切换4次

kafka的优化方案:

  1. 批量 I/O 操作
  1. 使用0拷贝技术

使用transferTo传输数据

上下文切换2次

系统还会优化,减少socket buffer赋值,直接将文件数据写到网卡

2.2.5. 端到端数据压缩

在某些场景下,CPU和磁盘并不一定是瓶颈,而是网络带宽,因此kafka支持从生产者到服务端、服务端到消费者的 全链路数据压缩,kafka支持 GZIP, Snappy, LZ4 等压缩方式

优化:对单条数据进行压缩效果不明显,通常kafka对批量数据进行压缩后再传输,批量压缩才是关键,压缩能达到最大

2.3. 生产者

生产者的2个关键设计

2.3.1. Load balancing

  1. 随机选择partition

  2. 轮训partition

  3. 按照key hash

  4. 自定义路由

2.3.2. Asynchronous send

生产者会在本地内存中积累数据,然后一次发送积累的批量数据,以此提升传输效率。

缓存多少批量数据可以通过数据大小(字节)和延迟时间决定(比如64k or 10 ms),具体配置多少要平衡吞吐量和延迟的需求

2.4. 消费者

消费者设计

2.4.1. Push vs. pull

服务端推送消息给消费者(Push)还是消费者从服务端拉取信息(Pull),两种方式各有优缺点,应该如何选择?

2.4.1.1. Push

Push的优点:

时效性极高,信息可以及时推送给消费者

Push的缺点:

  1. 由于服务端控制信息的发送速率,主动推送很难应对多样化的消费者,如果消费者跟不上生产者的速度,消费者可能会超负荷运行,本质上类似于拒绝服务攻击

  2. 虽然可以通过协议约定让服务器感知消费者的负载压力,然后降低传输率,但实现比较麻烦

2.4.1.2 Pull

Kafka偏向于Pull设计, 作为对比,看看Pull的优缺点

Pull的优点:

  1. 即使消费者落后于生产者的生产速度,但是在条件允许的情况下,消费者可以最终跟上生产者的速度

  2. 消费者可以更好处的处理批量数据读取,基于Push的系统无论是选择立即发送数据还是积累一定数据,都有弊端,因为服务端不知道下游是否能处理推送的数据

Pull的缺点:

  1. 如果服务端无数据,会导致消费者不停地轮训服务器,导致消费者处于空转的状态

kafka如何解决空转的问题?

kafka在请求参数中设置了等待时长参数,如果服务端无数据,则服务端会挂起当前请求(通过时间轮实现延迟),直到生产者有数据写入或者等待时长到期。

2.4.2. 消费者position管理

kafka需要追踪哪些数据被消费了,追踪哪些数据被消费的信息可以在服务端进行,也可在客户端进行,应该如何选择?

2.4.2.1. 服务端管理

在服务端追踪的优点:

  1. 当服务端数据发送给消费者后,服务端知道数据已经被消费,于是可以立即删除数据,服务端可以保证数据是最小的,节省存储空间

在服务端追踪的缺点:

  1. 信息丢失或者重复消费

    1. 信息丢失: 如果服务端将数据发送给消费者后,就认为消息已经送达(consumed状态),当消费者尚未来得及处理消息就挂掉了,则会造成消息丢失
    2. 重复消费:如果服务端将数据发送给消费者后,将信息记录为send状态,消费者处理完后再发送ack给服务端,如果消费者处理信息后还未发送ack就挂掉了,则消息可能会被重复消费
  2. 性能问题

服务端需要记录每一条信息的多种状态,比如send/consumed状态,而且需要处理消费者不发送ack时,消息应该如何处理的棘手问题

2.4.2.2. 客户端管理

kafka选择在客户端追踪哪些数据被消费的元数据,我们看看在客户端的优缺点:

优点:

  1. 实现简单、成本小

kafka的每个topic可以分成多个partition,但是每个partition只会被一个消费者订阅(一个分组下的消费者),因此可以在消费者中跟踪订阅的partition的position,而且只要一个数字,成本非常小。如果一个partition会被多个消费者订阅(一个分组下的多个消费者),则多个消费者需要协调谁消费哪些数据,在客户端则难以实现

  1. 消费者可以回溯到之前的位置,重新消费数据,这个特性在某些场景有用,比如代码出了bug,需要重新消费

2.5. 消息传递的语义(传递规则和行为)

  1. At most once, 最多一次 — 消息可能会丢失,但绝不会重新传递

生产者

  1. At least once, 至少一次 — 消息绝不会丢失,但可能会重新传递

  2. Exactly once, 恰好一次 — 这是人们真正想要的,每条消息只传递一次

生产者和消费者都存在这些语义

2.5.1. 对于生产者:

  1. At least once

如果信息发出去后,生产者未收到信息已经发送成功的响应信息,则生产者可能重新发送信息,这就是At least once,从0.11.0.0版本开始,kafka支持幂等传递,重复发送不会产生重复数据,另外这个版本还支持了在多个topic之前实现事务发送,数据要么成功,要么失败。

  1. At most once

如果生产者禁用retries,则最多只发送一次,如果生产者与服务器中间发送网络异常,则信息可能丢失

2.5.2. 对于消费者

  1. At most once

消费者读取信息后,随即提交position,最后再处理数据,则可能出现position保存成功但是处理数据时消费者挂了,导致数据处理失败,如果另外一个消费者接着处理,则只会处理position之后的数据,已经处理失败的数据则不会处理,这就是At most once,最多处理一次

  1. At least once

消费者读取信息后,随即处理数据,最后提交position,则可能出现数据已经处理成功但是保存position时消费者挂了,如果另外一个消费者接着处理,则会重复消费已经处理成功的数据,这就是At least once ,至少处理一次

  1. Exactly once

场景1(kafka系统之内): 如果消费者从一个topic消费数据,然后处理数据,最后将数据写入另外一个topic,则可以利用kafka的事务能力,这种情况需要把消费者的position存储在一个topic中,于是position可以和处理结果(存储在另外一个topic)放在同一个事物中提交,如果事务被中断,则position恢复到原来的值,并且处理结果也不会对消费者可见,当然消费者是否可以看到数据,取决于事物隔离级别,如果是"read_uncommitted",消费者是可以看到数据的,如果是"read_committed",则消费者只能看到已经提交的数据。

场景2(kafka与外部系统):

  1. 两阶段提交,position和输出结果通过两阶段提交存储到外部系统,但是很多外部系统不支持两阶段提交,所以不推荐

  2. 将position和输出结果存储在一个事务中,要么都成功、要么都失败,当然将position存储在外部系统中会涉及与kafka系统的协调,kafka提供Kafka Connect组件来帮助实现这个功能

总结:对于Exactly once的实现,如果在kafka系统内部,kafka可以通过事务实现,如果需要与外部系统交互,kafka提供Kafka Connect帮助实现这个功能,另外如果使用Kafka Streams(kafka大数据流处理平台),Kafka Streams支持Exactly once。

最后,kafka默认配置是At least once

2.6. Replication(副本)

kafka会备份每个topic所有partition的数据,并将备份的数据均匀分布到多个服务节点上(可以针对单个topic设置副本因子),如果集群有服务挂掉,kafka会自动进行故障转移,使用副本继续提供服务。

备份的对象是partition,备份数量取决于topic的副本数量/副本因子( replication factor),副本数量包含leader,所有写操作都在leader partition进行,读操作可以在leader 或者 follower partition进行,follower的数据和数据的顺序与leader保持一致,follower同步数据其实和消费者读取数据方式一样,也是消费leader的数据,然后写入日志。当然,在任何给定时间点,leader的日志末尾可能会有尚未完成备份的少量数据。

2.6.1.1. “alive”节点 的定义:

在分布式系统中,要自动处理故障需要明确定义 alive节点 是什么? kafka系统中有一个特殊的节点叫 Controller节点,这个节点会管理集群中其他节点的注册,alive节点需要满一个条件:

  1. 保持心跳(active session),节点必须与Controller保持活动会话,以便定期接收元数据更新

其中保持心跳(active session)的行为在新老版本有所不同

在KRaft集群(不用zookeeper)中,主要是维护节点与Controller的心跳,心跳时间通过broker.session.timeout.ms配置,节点超时未发送心跳给Controller,则认为节点下线

在使用Zookeeper的集群中,心跳是通过节点注册在zookeeper的临时路径决定的,如果zookeeper.session.timeout.ms时间内,节点未与zookeeper发送心跳,则临时节点会被删除,于是Controller就会感知节点被删除,然后Controller将相应节点标记为下线

2.6.1.2. “in sync”节点的定义:
  1. 节点必须是“alive”节点

  2. follower节点必须保持与leader节点同步,follower的数据不能落后太多leader节点的数据

不能落后太多的含义,如果follower在一定时间还未读到leader末尾的数据则认为落后了,时间通过replica.lag.time.max.ms配置

2.6.1.3. “ISR”定义(in sync replica):

leader节点会跟踪“in sync”的节点,处于“in sync”的节点则被认为是ISR,如果节点挂掉了或者follower节点的数据落后leader节点(不是“in sync” )但是节点并未挂掉,这两种情况,leader都会将节点从ISR中移除

ISR用来做什么?

  1. 可以更准确的定义消息提交,比如acks=allacks=-1时:生产者等待集群中的所有in sync的副本(ISR,In-Sync Replicas)接收到确认才认为消息提交成功,当然生产者可以在latency and durability之间权衡,如果选择低延迟则可以选择不需要ISR中的副本确认,或者仅仅leader确认即可。

  2. 节点发生故障时,用于leader选择

2.6.2. 副本复制:Quorums, ISRs, and State Machines

Kafka 分区的核心是数据复制,数据复制是分布式数据系统中最基本的原语之一,有许多方法可以实现数据复制。最简单和快捷的方式就是选择一个leader,leader负责数据的写入顺序,只要leader还存活,follower复制leader的值即可。

数据复制算法需要确保客户端的发送的数据只要被commited,即使leader挂了,从follower中选举的新leader必须有被commited的数据。这也意意味着leader等待越多的follower ack(复制数据后对leader进行反馈),则可选择的潜在leader越多。

2.6.2.1 Quorums

Quorums(法定人数)的定义:

如果 需要ack的follower数量 和 实际的partition的副本数量 进行比较并能从中选择leader,要求这两个数的范围必须有重叠,这个follower数就是法定人数。

选择Quorums的常见做法是采用majority vote,数据提交和leader选举中通常使用majority vote算法。假如有 2f+1 个副本,如果有大于等于f+1(正好大于f)个副本接收到了数据,则可以认为数据成功提交(commited),当leader挂了后,从这f+1个副本选举一个新leader,可以确保新leader有完整的数据。所以f+1个副本可以容忍f个节点挂掉,因为f+1个副本中,至少有一个以上的节点包含完整的(所有已经提交)数据。

majority vote有一个很好特点是:

延迟(latency)取决于最快的节点,比如有3个副本,1个leader,两个follower,只要一个follower复制成功即可,而延迟则取决于最快的那个follower。

majority vote的缺点:

majority vote可能在面对多个故障时变得脆弱,因为它依赖于足够数量的成员参与投票。比如要容忍1个节点失败,则需要2*1+1=3个节点,要容忍2个节点失败,则需要5个节点,也意味着需要5次数据写入,和5倍的存储容量而吞吐量却只有1/5,对于大容量数据系统来说这不是很适用。

所以quorum 算法一般适用于共享集群配置如zookeeper系统。HDFS namenode的高可用性特性建立也是建立在majority-vote-based journal,但是HDFS的数据存储没有使用这种算法。

很多算法如ZooKeeper's Zab, RaftViewstamped Replication 都属于majority vote算法的变种。

2.6.2.2. ISRs

kafka的选择Quorums的方式不一样,不是采用majority vote,而是动态维护一个 in sync状态的副本集,也就是ISR。生产者发送数据后,只有数据被所有 in sync的副本接受了,才会认为信息被提交成功(committed),通过 ISR 模型和 f+1 个副本,Kafka 主题可以容忍 f 个副本故障而不会丢失已提交的消息。

ISR与Majority vote的对比:

在认为信息被认为committed之前, ISR和Majority vote要等待ack的副本数是一样的,例如,为了承受一次故障,Majority vote需要三个副本和其中一个副本ack,而 ISR 方法需要两个副本和其中一个副本ack。如果要承受两次故障,Majority vote需要2*2+1=5个副本和其中2个副本ack(一个leader,2个follower ack),而ISR需要3个副本和其中2个副本ack(一个leader,2个follower ack)

ISR的优点:

  1. kafka可以通过客户端参数来决定是否要阻塞以等待所有in sync副本同步完成还是无需副本ack来优化延迟(对比Majority vote的优点),以此提高系统吞吐量,并且由于所需的副本更少,更节省存储空间

  2. kafka的这种设计 不要求崩溃的节点在所有数据完好无损的情况下才能恢复。而Majority vote对磁盘的稳定性和数据完整性(比如频繁刷盘以持久化数据)要求更高。ISR 的协议确保崩溃的节点在重新加入ISR队列之前,节点必须完成与Leader数据的完成同步,即使在崩溃时丢失了未刷新的数据。

与ISRs算法类似的是来自微软的 PacificA 算法

2.6.3. Unclean leader election(所有副本都挂了怎么办)?

两种策略

  1. 等待 ISR 中的副本恢复运行,并选择恢复的副本作为领导者(希望它仍拥有所有数据)。

  2. 选择第一个恢复运行的副本(不一定在 ISR 中)作为领导者,这就是Unclean leader election。

这是可用性和一致性之间的简单权衡。0.11.0.0版本开始,kafka默认使用第一种策略

2.6.4. 可用性和一致性保证

当数据写入 Kafka 时,生产者可以选择等待 0、1 个或all(-1)个副本确认消息。默认情况下,当 acks=all 时,所有in sync状态的副本收到消息后才会认为信息被committed。比如有两个副本,但是其中一个副本挂掉了,另外一个副本是in sync状态,如果此时acks=all,则只要in sync状态的副本写入数据成功,就认为信息committed。这种行为虽然保证了partition的最大可用性,但对于数据一致性要求很高的应用来说并不一定是期望的行为,比如in sync写完数据后也挂了,则可能导致数据丢失,为此kafka提供了两个topic级别的配置:

  1. Disable unclean leader election,如果所有的副本的都不可用了,则kafka也会不可用,直到有副本恢复并成为leader。

  2. 指定最小 ISR 大小 - 仅当 ISR 数量高于某个最小值时,分区才会接受写入。此设置在一致性和可用性之间提供了权衡。

2.7. 日志压缩

kafka除了提供简单的日志保留机制:固定时间段后或日志达到预定大小时丢弃旧日志数据。kafka还提供了日志压缩的保留机制,日志压缩提供了更细粒度的保留机制,这样kafka可以保存每个主键的最后一次更新记录,而不是每一次更新记录,日志压缩可确保单个topic 分区的数据日志中保留每个消息键的最后一个已知值。

压缩在后台通过定期重新复制日志的segment完成,日志清理不会阻止数据读取,并且可以限制使用不超过可配置的 I/O 吞吐量,以避免影响生产者和消费者。压缩日志的实际过程如下所示:

日志压缩要求每条信息都必须带有Key。

使用场景:

可以利用kafka存储数据的最终状态,当系统挂掉后用于状态重建、缓存重建等场景

日志压缩过程:

日志压缩由日志清理器(log cleaner)处理,它是一组后台线程,用于重新复制日志段文件,删除日志头部出现的键记录。每个线程的工作原理如下

  1. 选择日志头与日志尾比例最高的日志

  2. 为日志头中的每个键创建最后偏移量的简洁摘要信息

  3. 从头到尾重新复制日志,删除日志中较晚出现的键。新的干净段会立即交换到日志中,因此所需的额外磁盘空间只是一个额外的日志段(而不是日志的完整副本)

  4. 日志头的摘要本质上只是一个空间紧凑的哈希表。它的每个条目使用 24 个字节。因此,使用 8GB 的缓冲区,一次清洁迭代可以清理大约 366GB 的日志头(假设一条信息1k大小)。

2.8. Quote(配额:服务器资源使用额度 or 降级)

Kafka 集群能够对请求实施配额,以控制客户端使用的服务器资源,因为生产者和消费者可能会生产/消费大量数据或以极高的速率生成请求,从而垄断服务器资源,导致网络饱和,并通常会对其他客户端和broker本身造成 DOS 攻击。

2.8.1. kafka有两种类型的配额设置:

  1. 网络带宽配额(自 0.9 版本起)

比如10MB/s,表示每秒钟生产者/消费者最多只能发送/消费10MB的数据

  1. 请求速率配额,即 CPU 利用率(自 0.11 版本起)

由于kafka服务端用于处理 I/O和网络的线程数是基于机器的CPU核数,所以线程的使用时间可以认为是CPU的处理时间,比如num.io.threads + num.network.threads=10,那么总的CPU使用率的计算公式是 ((num.io.threads + num.network.threads) * 100)%= 1000%,如果配额是10%,则表示 单位时间内,一个线程只能使用10%的CPU时间。

2.8.2. 配额限制的对象是谁?

配置是针对client group,注意不是consumer group。client group是通过<user,client-id> 二元组构成的,或者单独的user group、单独的client-id group,生产者或者消费者初始化时需要设置<user、client-id>,user or client-id。

2.8.3. 如何设置配额

zookeeper有两个路径用于存储配额,分别在 /config/users和/config/clients,

/config/users 存储 User or <user, client-id>的配额

/config/clients 存储client-id的配额

配额配置的优先顺序是:

  1. /config/users//clients/

  2. /config/users//clients/

  3. /config/users/

  4. /config/users//clients/

  5. /config/users//clients/

  6. /config/users/

  7. /config/clients/

  8. /config/clients/

2.8.4. 最后

  1. 配额是在broker维度配置的。每个客户端都可以在受到限流之前可以利用每个broker的此配额。因为在集群维度设置配额实现起来比较难,需要考虑在所有broker之间共享配额。
  2. 如果客户端受到配额限流,服务端并不会直接返回错误,而是会计算客户端需要静默的时间,然后服务端会静默客户端的链接,在此期间,服务端不会处理该链接的请求,同时,服务端会给客户端返回静默时间,客户端收到静默时间后,也不能给服务端发送请求。所以被限流的请求会被客户端和服务端同时block。

有一个计算延迟时间的公式:


* Basically, if O is the observed rate and T is the target rate over a window of W, to bring O down to T,
* we need to add a delay of X to W such that O * W / (W + X) = T. (为什么是W/(W+X))而不是A/(W+X),A是指W时间窗口内累计的值,因为O = A/W, 但是到这一步时,不知道A是多少,只知道O,所以要使用W
* 转换后的公式是 A/W * W/(W + X) = T = O * W / (W + X) = T
* Solving for X, we get X = (O - T)/T * W.  
  1. kafka会通过多个小窗口(比如30 个窗口,每个窗口持续 1 秒计算网络带宽和CPU使用率,而不是大窗口(比如1个窗口,每个窗口30秒)检查,因为小窗口可以更快速更准的检测出问题。大窗口容易导致流量激增,随后出现长时间延迟,降低用户体验。

  2. 如果要启用配额,需要启用kafka的用户认证机制

例子:

给user=youradminusername的用户设置配额的命令,producer_byte_rate=2048000000表示生产者的网络带宽,单位是字节(byte)

bin/kafka-configs.sh --zookeeper localhost:2181 --alter \

--add-config 'producer_byte_rate=2048000000,consumer_byte_rate=2048000000' \

--entity-type users --entity-name youradminusername

测试CPU 配额 40%,耗时170184毫秒,约2.8364分钟

bin/kafka-configs.sh --zookeeper localhost:2181 --alter \

--add-config 'producer_byte_rate=2048000000,consumer_byte_rate=2048000000,request_percentage=40' \

--entity-type users --entity-name youradminusername

测试CPU 配额 20%,耗时365862毫秒,约6分钟