kafka部署硬件方案

最后更新:2020-02-02

1. 磁盘

在对 Kafka 集群进行磁盘规划时经常面对的问题是,我应该选择普通的机械磁盘还是固态硬盘?前者成本低且容量大,但易损坏;后者性能优势大,不过单价高。我们一般使用普通机械硬盘即可。

Kafka 大量使用磁盘不假,可它使用的方式多是顺序读写操作,一定程度上规避了机械磁盘最大的劣势,即随机读写操作慢。从这一点上来说,使用 SSD 似乎并没有太大的性能优势,毕竟从性价比上来说,机械磁盘物美价廉,而它因易损坏而造成的可靠性差等缺陷,又由 Kafka 在软件层面提供机制来保证,故使用普通机械磁盘是很划算的。

关于磁盘选择另一个经常讨论的话题就是到底是否应该使用磁盘阵列(RAID)。使用 RAID 的两个主要优势在于:

  • 提供冗余的磁盘存储空间
  • 提供负载均衡

以上两个优势对于任何一个分布式系统都很有吸引力。不过就 Kafka 而言,一方面 Kafka 自己实现了冗余机制来提供高可靠性;另一方面通过分区的概念,Kafka 也能在软件层面自行实现负载均衡。如此说来 RAID 的优势就没有那么明显了。

追求性价比的公司可以不搭建 RAID,使用普通磁盘组成存储空间即可。使用机械磁盘完全能够胜任 Kafka 线上环境。

2. 磁盘容量

Kafka 需要将消息保存在底层的磁盘上,这些消息默认会被保存一段时间然后自动被删除。虽然这段时间是可以配置的,但你应该如何结合自身业务场景和存储需求来规划 Kafka 集群的存储容量呢?

假设有个业务每天需要向 Kafka 集群发送 1 亿条消息,每条消息保存两份以防止数据丢失,另外消息默认保存两周时间。现在假设消息的平均大小是 1KB, 那么Kafka 集群需要为这个业务预留磁盘空间的计算方式如下:

我们来计算一下:每天 1 亿条 1KB 大小的消息,保存两份且留存两周的时间,那么总的空间大小就等于 1 亿 * 1KB * 2 / 1000 / 1000 = 200GB。一般情况下 Kafka 集群除了消息数据还有其他类型的数据,比如索引数据等,故我们再为这些数据预留出 10% 的磁盘空间,因此总的存储容量就是 220GB。既然要保存两周,那么整体容量即为 220GB * 14,大约 3TB 左右。Kafka 支持数据的压缩,假设压缩比是 0.75,那么最后你需要规划的存储空间就是 0.75 * 3 = 2.25TB。

总之在规划磁盘容量时你需要考虑下面这几个元素:

  • 新增消息数
  • 消息留存时间
  • 平均消息大小
  • 备份数
  • 是否启用压缩

3. 带宽

对于 Kafka 这种通过网络大量进行数据传输的框架而言,带宽特别容易成为瓶颈。如果你的环境中还涉及跨机房传输,那么情况可能就更糟了。

与其说是带宽资源的规划,其实真正要规划的是所需的 Kafka 服务器的数量。假设机房环境是千兆网络,即 1Gbps,现在有个业务,其业务目标或 SLA 是在 1 小时内处理 1TB 的业务数据。那么问题来了,到底需要多少台 Kafka 服务器来完成这个业务呢?

由于带宽是 1Gbps,即每秒处理 1Gb 的数据,假设每台 Kafka 服务器都是安装在专属的机器上,也就是说每台 Kafka 机器上没有混布其他服务,毕竟真实环境中不建议这么做。通常情况下只能假设 Kafka 会用到 70% 的带宽资源,因为总要为其他应用或进程留一些资源。

根据实际使用经验,超过 70% 的阈值就有网络丢包的可能性了,故 70% 的设定是一个比较合理的值,也就是说单台 Kafka 服务器最多也就能使用大约 700Mb 的带宽资源。

但这只是它能使用的最大带宽资源,我们不能让 Kafka 服务器常规性使用这么多资源,故通常要再额外预留出 2/3 的资源,即单台服务器使用带宽 700Mb / 3 ≈ 240Mbps。需要提示的是,这里的 2/3 其实是相当保守的,你可以结合你自己机器的使用情况酌情减少此值。好了,有了 240Mbps,我们就可以计算 1 小时内处理 1TB 数据所需的服务器数量了。根据这个目标,我们每秒需要处理 2336Mb 的数据,除以 240,约等于 10 台服务器。如果消息还需要额外复制两份,那么总的服务器台数还要乘以 3,即 30 台。

注意:带宽的单位是bit

4. 操作系统参数

4.1. swap的调优

swap可以设置成一个较小的值而不是0。因为一旦设置成0,当物理内存耗尽时,操作系统会触发OOM killer这个组件,它会随机挑选一个进程然后kill掉,即根本不给用户任何的预警。但如果设置成一个比较小的值,当开始使用swap空间时,我们至少能够观测到Broker性能开始出现急剧下降,从而给我们进一步调优和诊断问题的时间。基于这个考虑,建议将swappniess配置成一个接近0 但不为0的值,比如1。

4.2. 提交时间或者说是Flush落盘时间

向Kafka发送数据并不是真要等数据被写入磁盘才会认为成功,而是只要数据被写入到操作系统的页缓存(Page Cache)上就可以了,随后操作系统根据LRU算法会定期将页缓存上的“脏”数据落盘到物理磁盘上。这个定期就是由提交时间来确定的,默认是5秒。一般情况下我们会认为这个时间太频繁了,可以适当地增加提交间隔来降低物理磁盘的写操作。当然一般可能会有这样的疑问:如果在页缓存中的数据在写入到磁盘前机器宕机了,那岂不是数据就丢失了。的确,这种情况数据确实就丢失了,但鉴于Kafka在软件层面已经提供了多副本的冗余机制,因此这里稍微拉大提交间隔去换取性能还是一个合理的做法。

Page Cache刷写磁盘策略

  1. 页高速缓存变得太满,但还需要更多的页,或者脏页的数量已经太多。
  2. 自从页变成脏页以来已过去太长的时间。
  3. 用户进程通过调用sync()、fsync()或者fdatasync()系统调动来触发。

前两项都可以通过系统参数配置来调整。 使用sysctl -a | grep dirty命令可以查看默认配置:

# sysctl -a | grep dirty
vm.dirty_background_bytes = 0
# 占系统内存的百分比,可以设为5
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
# 脏页的总量
vm.dirty_ratio = 20
# 理论上调小这个参数,可以提高刷磁盘的频率,从而尽快把脏数据刷新到磁盘上。但一定要保证间隔时间内一定可以让数据刷盘完成
vm.dirty_writeback_centisecs = 500
vm.dirtytime_expire_seconds = 43200

5. JVM参数

JVM堆大小设置成6GB,业界比较公认的一个合理值。Java8 GC手动指定为G1收集器。 设置两个环境变量:

  • KAFKA_HEAP_OPTS:指定堆大小。
  • KAFKA_JVM_PERFORMANCE_OPTS:指定GC参数。
$ export KAFKA_HEAP_OPTS=--Xms6g --Xmx6g
$ export KAFKA_JVM_PERFORMANCE_OPTS= -server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent -Djava.awt.headless=true
$ bin/kafka-server-start.sh config/server.properties

4. 参考资料

《Kafka核心技术与实战》

Edgar

Edgar
一个略懂Java的小菜比