Kafka线上集群部署方案
考虑 操作系统、磁盘、磁盘容量和带宽
操作系统
Kafka 由 Scala 语言和 Java 语言
编写而成,编译之后的源代码就是普通的“.class”文件
部署在linux上的三个优势
- I/O 模型的使用
I/O 模型与 Kafka 的关系
Kafka 客户端底层使用了 Java 的 selector,selector在 Linux 上的实现机制是 epoll
,而在 Windows 平台上的实现机制是 select
因此将 Kafka 部署在 Linux 上是有优势的,因为能够获得更高效的 I/O 性能 - 数据网络传输效率
Kafka 生产和消费的消息都是通过网络传输的,而消息保存在磁盘,故 Kafka 需要在磁盘和网络间进行大量数据传输
零拷贝(Zero Copy)技术
,就是当数据在磁盘和网络进行传输时避免昂贵的内核态数据拷贝从而实现快速的数据传输
Linux 平台实现了这样的零拷贝机制,在 Linux 部署 Kafka 能够享受到零拷贝技术所带来的快速数据传输特性 - 社区支持度
Windows 平台上部署 Kafka 只适合于个人测试或用于功能验证,千万不要应用于生产环境
I/O 模型
- 阻塞式 I/O – Java 中 Socket 对象的阻塞模式
- 非阻塞式 I/O – Java 中 Socket 对象的非阻塞模式
- I/O 多路复用 – Linux 中的系统调用 select 函数
- 信号驱动 I/O – epoll 系统调用, 介于I/O 多路复用和信号驱动 I/O之间
- 异步 I/O – 很少有 Linux 系统支持,Windows 系统提供了一个叫 IOCP 线程模型
磁盘
机械磁盘 - 成本低且容量大,但易损坏
固态硬盘 - 性能优势大,不过单价高
建议使用机械磁盘
Kafka 大量使用磁盘,使用的方式多是顺序读写操作
,一定程度上规避了机械磁盘最大的劣势,即随机读写操作慢
从性价比上来说,机械磁盘物美价廉,而它因易损坏而造成的可靠性差等缺陷,又由 Kafka 在软件层面提供机制来保证,故使用普通机械磁盘是很划算的
使用磁盘阵列(RAID)优势:
- 提供冗余的磁盘存储空间
- 提供负载均衡
一方面 Kafka 自己实现了冗余机制来提供高可靠性;
另一方面通过分区的概念,Kafka 也能在软件层面自行实现负载均衡
磁盘建议:
- 追求性价比的公司可以不搭建 RAID,使用普通磁盘组成存储空间即可
- 使用机械磁盘完全能够胜任 Kafka 线上环境
磁盘容量
Kafka 需要将消息保存在底层的磁盘上,这些消息默认会被保存一段时间 (可以配置) 然后自动被删除
假设每天向 Kafka 集群发送 1 亿条 1KB 大小的消息,保存两份且留存两周的时间
计算磁盘空间:
1 亿 * 1KB * 2 / 1000 / 1000 = 200GB
一般情况下 Kafka 集群除了消息数据还有其他类型的数据,比如索引数据等,预留出 10% 的磁盘空间,因此总的存储容量就是 220GB
既然要保存两周,那么整体容量即为 220GB * 14,大约 3TB 左右
Kafka 支持数据的压缩,假设压缩比是 0.75,那么最后需要规划的存储空间就是 0.75 * 3 = 2.25TB
在规划磁盘时需要考虑
- 新增消息数
- 消息留存时间
- 平均消息大小
- 备份数
- 是否启用压缩
带宽
对于 Kafka 这种通过网络大量进行数据传输的框架而言,带宽特别容易成为瓶颈
带宽资源不足导致 Kafka 出现性能问题的比例至少占 60% 以上
如果涉及跨机房传输,那么情况可能就更糟了
带宽也主要有两种:1Gbps 的千兆网络和 10Gbps 的万兆网络,千兆网络应该是一般公司网络的标准配置了
带宽资源的规划,其实真正要规划的是所需的 Kafka 服务器的数量
Q: 假设公司的机房环境是千兆网络,即 1Gbps,现在有个业务,其业务目标或 SLA 是在 1 小时内处理 1TB 的业务数据,到底需要多少台 Kafka 服务器来完成这个业务呢?
由于带宽是 1Gbps,即每秒处理 1Gb 的数据
,假设每台 Kafka 服务器都是安装在专属的机器上,也就是说每台 Kafka 机器上没有混布其他服务,毕竟真实环境中不建议这么做
通常情况下假设 Kafka 会用到 70% 的带宽资源
,因为总要为其他应用或进程留一些资源。根据实际使用经验,超过 70% 的阈值就有网络丢包的可能性了,故 70% 的设定是一个比较合理的值,也就是说单台 Kafka 服务器最多也就能使用大约 700Mb 的带宽资源
最大带宽资源,不能让 Kafka 服务器常规性使用这么多资源,故通常要再额外预留出 2/3 的资源,即单台服务器使用带宽 700Mb / 3 ≈ 240Mbps
需要提示的是,这里的 2/3 其实是相当保守
的,可以结合机器的使用情况酌情减少此值
1024/3600 = 0.284G = 285MB 字节
285*8 ≈ 2336 Mb 比特
有了 240Mbps,可以计算 1 小时内处理 1TB 数据所需的服务器数量
根据这个目标,每秒需要处理 2336Mb 的数据,除以 240,约等于 10 台服务器
如果消息还需要额外复制两份,那么总的服务器台数还要乘以 3,即 30 台
用这种方法评估线上环境的服务器台数是比较合理的,而且这个方法能够随着业务需求的变化而动态调整
建议
集群参数配置
Kafka 服务器端的配置
- Broker 端参数
也叫静态参数(Static Configs)
必须在 Kafka 的配置文件 server.properties 中进行设置的参数
必须重启 Broker 进程才能令它们生效 - 主题(Topic)级别的参数
Kafka 提供了专门的 kafka-configs 命令来修改它们 - JVM 端参数
- 操作系统级别的参数
Broker 端参数
目前 Kafka Broker 提供了近 200 个参数
Broker 是需要配置存储信息的,即 Broker 使用哪些磁盘。针对存储信息的重要参数有以下这么几个:
log.dirs:指定了 Broker 需要使用的若干个文件目录路径。没有默认值的,必须亲自指定
log.dir:注意这是 dir,结尾没有 s,说明它只能表示单个路径,它是补充上一个参数用的
只要设置log.dirs就好了,不要设置log.dir
在线上生产环境中一定要为log.dirs配置多个路径,具体格式是一个 CSV 格式
,也就是用逗号分隔的多个路径
比如/home/kafka1,/home/kafka2,/home/kafka3
这样
如果有条件的话最好保证这些目录挂载到不同的物理磁盘上
这样的好处:
- 提升读写性能:比起单块磁盘,多块物理磁盘同时读写数据有更高的吞吐量
- 能够实现故障转移:即 Failover
这是 Kafka 1.1 版本新引入的强大功能。在以前,只要 Kafka Broker 使用的任何一块磁盘挂掉了,整个 Broker 进程都会关闭。但是自 1.1 开始,这种情况被修正了,坏掉的磁盘上的数据会自动地转移到其他正常的磁盘上,而且 Broker 还能正常工作。这个改进正是舍弃 RAID 方案的基础:没有这种 Failover 的话,只能依靠 RAID 来提供保障
ZooKeeper 相关的设置
ZooKeeper, 一个分布式协调框架,负责协调管理并保存 Kafka 集群的所有元数据信息,
比如集群都有哪些 Broker 在运行、创建了哪些 Topic,
每个 Topic 都有多少分区以及这些分区的 Leader 副本都在哪些机器上等信息
CSV 格式, 2181 是 ZooKeeper 的默认端口
zookeeper.connect: zk1:2181,zk2:2181,zk3:2181
让多个 Kafka 集群使用同一套 ZooKeeper 集群
使用ZooKeeper 的概念chroot,类似于别名
两套 Kafka 集群,假设分别叫它们 kafka1 和 kafka2,指定两套集群的zookeeper.connect参数:
zk1:2181,zk2:2181,zk3:2181/kafka1 和 zk1:2181,zk2:2181,zk3:2181/kafka2。
切记 chroot 只需要写一次,而且是加到最后的
zk1:2181/kafka1,zk2:2181/kafka2,zk3:2181/kafka3,这样的格式是不对的
Broker连接相关
客户端程序或其他 Broker 如何与该 Broker 进行通信的设置
- listeners:监听器,告诉外部连接者要通过什么协议访问指定主机名和端口开放的 Kafka 服务
- advertised.listeners:和 listeners 相比多了个 advertised。Advertised 的含义表示宣称的、公布的,就是说这组监听器是 Broker 用于对外发布的
- host.name/port:这两个参数是过期的参数,不要指定值
监听器是若干个逗号分隔的三元组,每个三元组的格式为<协议名称,主机名,端口号>
这里的协议名称可能是标准的名字,比如 PLAINTEXT 表示明文传输、SSL 表示使用 SSL 或 TLS 加密传输等;也可能是自己定义的协议名字,比如CONTROLLER: //localhost:9092
自定义协议名称,还必须要指定listener.security.protocol.map参数
告诉这个协议底层使用了哪种安全协议,比如指定listener.security.protocol.map=CONTROLLER:PLAINTEXT
表示CONTROLLER这个自定义协议底层使用明文不加密传输数据
主机名设置中到底使用 IP 地址还是主机名?
建议最好全部使用主机名,即 Broker 端和 Client 端应用配置中全部填写主机名
Broker 源代码中也使用的是主机名,如果在某些地方使用了 IP 地址进行连接,可能会发生无法连接的问题
Topic 管理
auto.create.topics.enable
:是否允许自动创建 Topic
建议false
, 否则线上环境会有许多稀奇古怪的topic
比如要为名为 test 的 Topic 发送事件,但是不小心把 test 写成了 tst,之后启动了生产者程序, 一个名为 tst 的 Topic 就被自动创建了
每个部门被分配的 Topic 应该由运维严格把控,决不能允许自行创建任何 Topic
unclean.leader.election.enable
:是否允许 Unclean Leader 选举
Leader 副本
: Kafka 有多个副本, 每个分区都有多个副本来提供高可用。在这些副本中只能有一个副本对外提供服务
只有保存数据比较多的那些副本才有资格竞选Leader
,那些落后进度太多的副本没资格做这件事
假设那些保存数据比较多的副本都挂了怎么办?还要不要进行 Leader 选举了?
此时这个参数就派上用场了
- 如果
设置成 false
,那么就坚持之前的原则,坚决不能让那些落后太多的副本竞选 Leader
这样做的后果是这个分区就不可用
了,因为没有 Leader 了 - 反之如果是
true
,那么 Kafka 允许从那些“跑得慢”的副本中选一个出来当 Leader。这样做的后果是数据有可能就丢失
了,因为这些副本保存的数据本来就不全,当了 Leader 之后它本人就变得膨胀了,认为自己的数据才是权威的
不同版本的 Kafka默认值不同,建议还是显式地把它设置成 false
auto.leader.rebalance.enable
:是否允许定期进行 Leader 选举
值为 true 表示允许 Kafka 定期地对一些 Topic 分区进行 Leader 重选举
与上一个参数中 Leader 选举的最大不同在于,它不是选 Leader,而是换 Leader!
比如 Leader A 一直表现得很好,但若auto.leader.rebalance.enable=true,那么有可能一段时间后 Leader A 就要被强行卸任换成 Leader B
换一次 Leader 代价很高
的,原本向 A 发送请求的所有客户端都要切换成向 B 发送请求,而且这种换 Leader 本质上没有任何性能收益,建议在生产环境中把这个参数设置成 false
数据留存
log.retention.{hours|minutes|ms}
:“三兄弟”,都制一条消息数据被保存多长时间。
从优先级上来说 ms 设置最高、minutes 次之、hours 最低
虽然 ms 设置有最高的优先级,但是通常情况下还是设置 hours 级别的多一些,比如log.retention.hours=168表示默认保存 7 天的数据,自动删除 7 天前的数据
。如果把 Kafka 当作存储来使用,那么这个值就要相应地调大log.retention.bytes
:这是指定 Broker 为消息保存的总磁盘容量大小
默认是 -1,表明在这台 Broker 上保存多少数据都可以,至少在容量方面 Broker 不会做任何阻拦
这个参数真正发挥作用的场景其实是在云上构建多租户的 Kafka 集群:设想要做一个云上的 Kafka 服务,每个租户只能使用 100GB 的磁盘空间,为了避免有个“恶意”租户使用过多的磁盘空间,设置这个参数就显得至关重要message.max.bytes
:控制 Broker 能够接收的最大消息大小
默认的 1000012 太少了,还不到 1MB
实际场景中突破 1MB 的消息都是屡见不鲜的,因此在线上环境中设置一个比较大的值还是比较保险的做法
毕竟它只是一个标尺而已,仅仅衡量 Broker 能够处理的最大消息大小,即使设置大一点也不会耗费什么磁盘空间的
Topic 级别参数
Topic 级别参数
如果同时设置了 Topic 级别参数和全局 Broker 参数,
Topic 级别参数会覆盖全局 Broker 参数的值,而每个 Topic 都能设置自己的参数值
eg
消息数据的留存时间参数log.retention.{hours|minutes|ms}
在实际生产环境中,如果为所有 Topic 的数据都保存相当长的时间,这样做既不高效也无必要
更适当的做法是允许不同部门的 Topic 根据自身业务需要,设置自己的留存时间
如果只能设置全局 Broker 参数,那么势必要提取所有业务留存时间的最大值作为全局参数值,此时设置 Topic 级别参数把它覆盖,就是一个不错的选择
保存消息维度方面参数
retention.ms
:规定了该 Topic 消息被保存的时长。默认是 7 天,即该 Topic 只保存最近 7 天的消息。一旦设置了这个值,它会覆盖掉 Broker 端的全局参数值
retention.bytes
:规定了要为该 Topic 预留多大的磁盘空间。和全局参数作用相似,这个值通常在多租户的 Kafka 集群中会有用武之地。当前默认值是 -1,表示可以无限使用磁盘空间
能处理的消息大小维度
max.message.bytes
: 决定了 Kafka Broker 能够正常接收该 Topic 的最大消息大小
目前在很多公司都把 Kafka 作为一个基础架构组件来运行,上面跑了很多的业务数据
如果在全局层面上,不好给出一个合适的最大消息值
那么不同业务部门能够自行设定这个 Topic 级别参数就显得非常必要了
Topic 级别参数的设置只有两种方式:
- 创建 Topic 时进行设置
bin/kafka-topics.sh kafka-topics命令用于创建 Topic
--bootstrap-server localhost:9092
--create
--topic transaction
--partitions 1
--replication-factor 1
--config retention.ms=15552000000 该 Topic 消息被保存的时长
--config max.message.bytes=5242880 能处理的消息大小维度, 5MB, 5*1024*1024
--config 指定了想要设置的 Topic 级别参数
- 修改 Topic 时设置
bin/kafka-configs.sh kafka-configs来修改 Topic 级别参数
--zookeeper localhost:2181
--entity-type topics
--entity-name transaction
--alter
--add-config max.message.bytes=10485760 修改为10MB
JVM 参数
Kafka 服务器端代码是用 Scala 语言编写的,但终归还是编译成 Class 文件在 JVM 上运行,因此 JVM 参数设置对于 Kafka 集群的重要性不言而喻
Java 版本 – JAVA8
堆大小参数: 6GB, 目前业界比较公认的一个合理值, 默认的1GB有点小
Kafka Broker 在与客户端进行交互时会在 JVM 堆上创建大量的 ByteBuffer 实例,Heap Size 不能太小
后续会讨论调优
GC 设置
Java 7
- 如果 Broker 所在机器的 CPU 资源非常充裕,建议使用 CMS 收集器。启用方法是指定
-XX:+UseCurrentMarkSweepGC
- 否则,使用吞吐量收集器。开启方法是指定
-XX:+UseParallelGC
Java 8
手动设置使用 G1 收集器: 比CMS出色, 更少的 Full GC,需要调整的参数更少
设置环境变量
- KAFKA_HEAP_OPTS:指定堆大小
- KAFKA_JVM_PERFORMANCE_OPTS:指定 GC 参数
可以这样启动 Kafka Broker,即在启动 Kafka Broker 之前,先设置上这两个环境变量:
$> 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
操作系统参数
-
文件描述符限制
在 Linux 系统中,一个长连接会占用一个 Socket 句柄(文件描述符),像 Ubuntu 默认是 1024,也就是最多 1024 个 Socket 长连接,Kafka 网络通信中大量使用长连接,这对比较大的 Kafka 集群来说可能是不够的。 为了避免 Socket 句柄不够用,将这个设置为一个比较大值是合理的
比如ulimit -n 1000000
文件描述符系统资源并不像想象的那样昂贵, 但不设置的话会经常看到“Too many open files”的错误 -
文件系统类型
文件系统指的是如 ext3、ext4 或 XFS 这样的日志型文件系统
XFS 的性能要强于 ext4,所以生产环境最好还是使用 XFS
ZFS 的数据报告,貌似性能更加强劲 -
Swappiness
网上很多文章都提到设置其为 0,将 swap 完全禁掉以防止 Kafka 进程使用 swap 空间
可以设置成一个较小的值
因为一旦设置成 0,当物理内存耗尽时,操作系统会触发 OOM killer 这个组件,它会随机挑选一个进程然后 kill 掉,即根本不给用户任何的预警。
但如果设置成一个比较小的值,当开始使用 swap 空间时,至少能够观测到 Broker 性能开始出现急剧下降,从而进一步调优和诊断问题的时间
基于这个考虑,建议将 swappniess 配置成一个接近 0 但不为 0 的值,比如 1 -
提交时间/Flush 落盘时间
向 Kafka 发送数据并不是真要等数据被写入磁盘才会认为成功,而是只要数据被写入到操作系统的页缓存(Page Cache)
上就可以了
随后操作系统根据 LRU 算法会定期将页缓存上的“脏”数据落盘到物理磁盘上
这个定期就是由提交时间来确定的,默认是 5 秒
一般情况下认为这个时间太频繁了,可以适当地增加提交间隔来降低物理磁盘的写操作
当然可能会有这样的疑问:如果在页缓存中的数据在写入到磁盘前机器宕机了,那岂不是数据就丢失了
的确,这种情况数据确实就丢失了,但鉴于 Kafka 在软件层面已经提供了多副本的冗余机制
,因此这里稍微拉大提交间隔去换取性能还是一个合理的做法
总结
(配置-上) 这些参数都是要修改默认值的参数, 不适合生产环境