Kafka上的优化经验

1. 平滑扩容

Motivation

kafka扩容⼀台新机器的流程

假如集群有 3 broker ,⼀共有 4 TP ,每个 3 副本,均匀分布。现在要扩容⼀台机器,
broker 加⼊集群后需要通过⼯具进⾏ TP 的迁移。⼀共迁移 3 TP 的副本到新 broker
上。等迁移结束之后,会重新进⾏ leader balance ,最终的 TP 分布如图所示:

从微观的⻆度看,TP 从⼀台 broker 迁移到另⼀个 broker 的流程是怎么样的呢?咱们来看 下 TP3 第三个副本,从 broker1 迁移到 broker4 的过程,如下图所示,broker4 作为 TP3 的 follower,从 broker1 上最早的 offffset 进⾏获取数据,直到赶上最新的 offffset 为⽌,新副 本被放⼊ ISR 中,并移除 broker1 上的副本,迁移过程完毕。

但在现有的扩容流程中存有如下问题:数据迁移从 TP3 的最初的 offset 开始拷⻉数据,这 会导致⼤量读磁盘,消耗⼤量的 I/O 资源,导致磁盘繁忙,从⽽造成 produce 操作延迟增 ⻓,产⽣抖动。所以整体迁移流程不够平滑。我们看下实际的监控到的数据。从中可以看到 数据迁移中, broker1 上磁盘读量增⼤,磁盘 util 持续打满,produce P999 的延迟陡增,极其不稳定。

改进⽅案

在迁移 TP 时, 直接从 partition 最新的 offset 开始数据迁移,但是要同步保持⼀段时间,
主要是确保所有 consumer 都已经跟得上了 。如图所示,再来看这个 TP3 的第三个副本从
broker1 迁移到 broker4 的过程。这次 broker4 直接从 broker1 最新的 offset 开始迁移,即
transfer start 这条竖线。此时,因为 consumer1 还没能跟得上,所以整个迁移过程需要保
持⼀段时间,直到 transfer end 这个点。这个时候,可以将 TP3 的新副本放到 ISR 中,同
时去掉 broker1 上的副本,迁移过程完毕。从这次的迁移过程中看,因为都是读最近的数
据,不会出现读⼤量磁盘数据的问题,仅仅多了⼀个副本的流量,基本对系统⽆影响。此
时,我们再来看下磁盘读量、磁盘 util 、以及 produce 的延迟,从图中可知基本没有任何变
化。整体过程⾮常平滑,可以说通过这种⽅式很优雅地解决了 Kafka 平滑扩容问题,我们之
前也有在晚⾼峰期间做扩容的情况,但是从 Kafka 整体服务质量上看,对业务没有任何影
响。这个功能有⼀个 patch 可⽤: https://issues.apache.org/jira/browse/KAFKA-8328

这个⽅案存在的问题

1. 快⼿的这个patch是针对kafka 0.10.2.0版本,其他版本不⽀持,如果我们引⼊的话,需要针对⽣产环境上的版本进⾏代码修改

2. 设计的缺陷

a)replica直接从最新的produce offset进⾏复制,⽆法保证kafka⼀致性语义(所有的副本数据完全⼀致),最新的replica缺失now - retention_ms这段时间的数据,如果consumer想要通过⾃定义offset来消费历史数据,可能会抛异常(当这个replica变成leader时);

b)replica从最新的produce offset进⾏同步,加⼊ISR的时机为 所有consumer offset全部⼤于起始同步offset,如果某个consumer group在此期间停⽌了消费测试⽤的group id),则会导致这个replica⼀直⽆法加⼊ISR

3. 这个patch只能在jdk 7 & scala 2.10编译通过,测试通过,在jdk 8 & scala 2.11环境下,测试⽆法通过

4. 社区的意⻅

 

5. 快⼿针对这个改进提了⼀个KIP,但是这个KIP已经被删掉了,没有找到他们当时对这个improvement的讨论

6. 这个patch kafka社区没有approve

2. Kafka RPC 队列隔离

Motivation

如图所示, Kafka RPC 框架中,⾸先由 accepter 从⽹络中接受连接,每收到⼀个连接,都
会交给⼀个⽹络处理线程( processer )处理, processor 在读取⽹络中的数据并将请求简单
解析处理后,放到 call 队列中, RPC 线程会从 call 队列中获取请求,然后进⾏ RPC 处理。
此时,假如 topic2 的写⼊出现延迟,例如是由于磁盘繁忙导致,则会最终将 RPC 线程池打
满,进⽽阻塞 call 队列,进⽽打满⽹络线程池,这样发到这个 broker 的所有请求都没法处
理了。

改进⽅案

解决这个问题的思路也⽐较直接,需要按照控制流、数据流分离,且数据流要能够按照
topic 做隔离。⾸先将 call 队列按照拆解成多个,并且为每个 call 队列都分配⼀个线程池。
call 队列的配置上,⼀个队列单独处理 controller 请求的队列(隔离控制流),其余多个
队列按照 topic hash 的分散开(数据流之间隔离)。如果⼀个 topic 出现问题,则只会
阻塞其中的⼀个 RPC 处理线程池,以及 call 队列。这⾥还有⼀个需要注意的是 processor
在将请求放⼊ call 队列中,如果发现队列已满,则需要将请求⽴即失败掉(否则还是会被阻
塞)。这样就保障了阻塞⼀条链路,其他的处理链路是畅通的。

3. Cache改造

Motivation

我们都知道, Kafka 之所以有如此⾼的性能,主要依赖于 page cache producer 的写操
作, broker 会将数据写⼊到 page cache 中,随后 consumer 发起读操作,如果短时间内
page cache 仍然有效,则 broker 直接从内存返回数据,这样,整体性能吞吐⾮常⾼。
但是由于 page cache 是操作系统层⾯的缓存,难于控制,有些时候,会受到污染,从⽽导
Kafka 整体性能的下降。我们来看 2 个例⼦:
 
第⼀个 case consumer lag 读会对 page cache 产⽣污染。

如图所示,假如有 2 consumer 1 producer 。其中,蓝⾊ producer 在⽣产数据,蓝
consumer 正在消费数据,但是他们之间有⼀定的 lag ,导致分别访问的是不同的 page
cache 中的 page 。如果⼀个橙⾊ consumer topic partition 最初的 offffset 开始消费数据
的话,会触发⼤量读盘并填充 page cache 。其中的 5 个蓝⾊ topic page 数据都被橙⾊
topic 的数据填充了。另外⼀⽅⾯,刚刚蓝⾊ producer ⽣产的数据,也已经被冲掉了。此
时,如果蓝⾊ consumer 读取到了蓝⾊ producer 刚刚⽣产的数据,它不得不再次将刚刚写
⼊的数据从磁盘读取到 page cache 中。综上所述,⼤ lag consumer 会造成 cache
染。在极端情况下,会造成整体的吞吐下降。
第⼆个 case follower 也会造成 page cache 的污染。

 

在图中 broker1 机器内部,其中 page cache 中除了包括蓝⾊ producer 写⼊的数据之外,
还包括橙⾊ follower 写⼊的数据。但是橙⾊ follower 写⼊的数据,在正常的情况下,之后不
会再有访问,这相当于将不需要再被访问的数据放⼊了 cache ,这是对 cache 的浪费并造
成了污染。所以,很容易想到 Kafka 是否可以⾃⼰维护 cache 呢?⾸先,严格按照时间的
顺序进⾏ cache ,可以避免异常 consumer lag 读造成的 cache 污染。其次,控制
follower 的数据不进⼊ cache ,这样阻⽌了 follower cache 的污染,可以进⼀步提升
cache 的容量。
 

改进⽅案

我们在 broker 中引⼊了两个对象:⼀个是 block cache ;另⼀个是 flflush queue Producer
的写⼊请求在 broker 端⾸先会被以原 message 的形式写⼊ flflush queue 中,之后再将数据
写⼊到 block cache 的⼀个 block 中,之后整个请求就结束了。在 flflush queue 中的数据会
由其他线程异步地写⼊到磁盘中(会经历 page cache 过程)。⽽ follower 的处理流程保持
和原来⼀致,从其他 broker 读到数据之后,直接把数据写到磁盘(也会经历 page
cache )。这种模式保证了 block cache 中的数据全都是 producer 产⽣的,不会被 follower
污染。
对于 consumer ⽽⾔,在 broker 接到消费请求后,⾸先会从 block cache 中检索数据,如
果命中,则直接返回。否则,则从磁盘读取数据。这样的读取模式保障了 consumer
cache miss 读并不会填充 block cache ,从⽽避免了产⽣污染,即使有⼤ lag consumer
读磁盘,也仍然保证 block cache 的稳定。

接下来,我们看下 block cache 的微观设计,整个 block cache 3 个部分组成:

第⼀部分: 2 block pool ,维护着空闲的 block 信息,之所以分成 2 类,主要是因
segment 数据以及 segment 的索引⼤⼩不同,统⼀划分会导致空间浪费;
第⼆部分:先进先出的 block 队列,⽤于维护 block ⽣产的时序关系,在触发淘汰时,会优
先淘汰时间上最早的 block
第三部分: TP+offffset 到有效 blocks 的索引,⽤于快速定位⼀个 block 。⼀个 block 可以看
做是 segment 的⼀部分。 segment 数据以及 segment 索引和 block 的对应关系如图所示。
最后,还有两个额外的线程:
eliminater 线程,⽤于异步进⾏ block cache 淘汰,当然,如果 produce 请求处理时,发现
block cache 满也会同步进⾏ cache 淘汰的;
异步写线程,⽤于将 flflush queue 中的 message 异步写⼊到磁盘中。
这个就是 Kafka cache 的整体设计,可以看出,已经很好地解决了上述的两个对 cache
染的问题了。

性能测试

我们搭建了 5 broker 的集群,其中⼀个换成了 Kafka cache 的版本。并创建了⼀个 150
partition topic 3 副本。所以算上副本,⼀共有 450 partition ,每台机器上 90
TP 。之后我们 Mirror 了⼀个线上的流量数据,并启动 150 consumer ,以总体 lag 450w
条数据开始读。

从图中可以看出,原始版本在这种情况下会造成⼤量的磁盘读,⽽ Kafka cache 版本没有任

何磁盘读取操作,这说明 Kafka cache 版本可以 cache 更多的有效数据,这点是排除了
follower 造成的污染,经过精细化统计,发现 Kafka cache 有效空间刚好为原版本的 2
(正好和同⼀台 broker TP leader follower 的数量⽐⼀致)。从恢复的时间上看,
由于排除了读磁盘以及多个 consumer 之间读可能会 cache 产⽣的污染, Kafka cache 的版
本也⽐原有版本速度要快了 30%

除此之外,再看下改进后的 broker ,从这个图中可以看出, produce 整个写⼊过程,先是同
步写⼊内存,然后再被异步刷⼊磁盘。虽然 page cache 的模式也是类似这种,但是 page cache 会存在⼀定不稳定性(可能会触发同步写盘)。这个是我们上线 Kafka cache 版本前
后的 produce p999 的延迟对⽐。从图中可以看到: Kafka cache 版本⽐原来版本的延迟低
了很多,且稳定性有极⼤改进。

 

引⼊该⽅案存在的问题

1. ⽬前快⼿只提出了⼀个⾃⼰管理 Cache 的⽅案,没有公开源码,我们需要⾃⼰写代码实
2. 我们集群 Kafka 的版本⽬前不统⼀,在实现这个⽅案之前需要统⼀ Kafka 版本

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值