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