2.1 安装 zookeeper
Zookeeper 集群被称为群组。Zookeeper 使用的是一致性协议,所以建议每个群组里应该包含奇数个节点(比如 3 个、5 个等),因为只有当群组里的大多数节点(也就是法定人数)处于可用状态,Zookeeper 才能处理外部的请求。
如果你有一个包含 3 个节点的群组,那么它允许一个节点失效。如果群组包含 5 个节点,那么它允许 2 个节点失效。
2.2 安装 kafka
peerPort,用于节点间通信的 TCP 端口;
leaderPort,用于leader选举的 TCP 端口;
clientPort,用于客户端连接kafka集群
客户端只需要通过 clientPort 就能连接到群组,而群组节点间的通信则需要同时用到这 3 个端口(peerPort、leaderPort、clientPort)。
2.3 broker配置
Kafka 发行包里自带的配置样本可以用来安装单机服务,但并不能满足大多数安装场景的要求。
Kafka 有很多配置选项,涉及安装和调优的方方面面。
不过大多数调优选项可以使用默认配置,除非你对调优有特别的要求。
2.3.1 常规配置
有一些配置选项,在单机安装时可以直接使用默认值,但在部署到其他环境时要格外小心。
这些参数是单个服务器最基本的配置,它们中的大部分需要经过修改后才能用在集群里。
-
broker.id
每个 broker 都需要有一个标识符,使用 broker.id 来表示。它的默认值是 0,也可以被设置
成其他任意整数。这个值在整个 Kafka 集群里必须是唯一的。这个值可以任意选定,如果
出于维护的需要,可以在服务器节点间交换使用这些 ID。建议把它们设置成与机器名具有
相关性的整数,这样在进行维护时,将 ID 号映射到机器名就没那么麻烦了。例如,如果
机器名包含唯一性的数字(比如 host1.example.com、host2.example.com),那么用这些数字
来设置 broker.id 就再好不过了。 -
port
如果使用配置样本来启动 Kafka,它会监听 9092 端口。修改 port 配置参数可以把它设置
成其他任意可用的端口。要注意,如果使用 1024 以下的端口,需要使用 root 权限启动
Kafka,不过不建议这么做。 -
zookeeper.connect
用于保存 broker 元数据的 Zookeeper 地址是通过 zookeeper.connect 来 指 定 的。
localhost:2181 表示这个 Zookeeper 是运行在本地的 2181 端口上。该配置参数是用冒号分隔的一组 hostname:port/path 列表,每一部分的含义如下:
• hostname 是 Zookeeper 服务器的机器名或 IP 地址;
• port 是 Zookeeper 的客户端连接端口;
• /path 是可选的 Zookeeper 路径,作为 Kafka 集群的 chroot 环境。如果不指定,默认使用
根路径。
如果指定的 chroot 路径不存在,broker 会在启动的时候创建它。
-
log.dirs
Kafka 把所有消息都保存在磁盘上,存放这些日志片段的目录是通过 log.dirs 指定的。
它是一组用逗号分隔的本地文件系统路径。如果指定了多个路径,那么 broker 会根据“最少使用”原则,把同一个分区的日志片段保存到同一个路径下。
要注意,broker 会往拥有最少数目分区的路径新增分区,而不是往拥有最小磁盘空间的路径新增分区。 -
num.recovery.threads.per.data.dir
对于如下 3 种情况,Kafka 会使用可配置的线程池来处理日志片段:
• 服务器正常启动,用于打开每个分区的日志片段;
• 服务器崩溃后重启,用于检查和截短每个分区的日志片段;
• 服务器正常关闭,用于关闭日志片段。
默认情况下,每个日志目录只使用一个线程。因为这些线程只是在服务器启动和关闭时会用到,所以完全可以设置大量的线程来达到并行操作的目的。
特别是对于包含大量分区的服务器来说,一旦发生崩溃,在进行恢复时使用并行操作可能会省下数小时的时间。
设置此参数时需要注意,所配置的数字对应的是 log.dirs 指定的单个日志目录。也就是说,如果 num.recovery.threads.per.data.dir 被设为 8,并且 log.dir 指定了 3 个路径,那么总共需要 24 个线程。 -
auto.create.topics.enable
默认情况下,Kafka 会在如下几种情形下自动创建主题:
• 当一个生产者开始往主题写入消息时;
• 当一个消费者开始从主题读取消息时;
• 当任意一个客户端向主题发送元数据请求时。
很多时候,这些行为都是非预期的。而且,根据 Kafka 协议,如果一个主题不先被创建,根本无法知道它是否已经存在。如果显式地创建主题,不管是手动创建还是通过其他配置系统来创建,都可以把 auto.create.topics.enable 设为 false。
2.3.2 主题的默认配置
Kafka 为新创建的主题提供了很多默认配置参数。可以通过管理工具为每个主题单独配置一部分参数,比如分区个数和数据保留策略。
服务器提供的默认配置可以作为基准,它们适用于大部分主题。
使用主题配置覆盖(override)
之前的 Kafka 版本允许主题覆盖服务器的默认配置,包括 log.retention.hours.per.topic、log.retention.bytes.per.topic 和 log.seg ment.bytes.
per.topic 这几个参数。
新版本不再支持这些参数,而且如果要对参数进行覆盖,需要使用管理工具。
- num.partitions
num.partitions 参数指定了新创建的主题将包含多少个分区。如果启用了主题自动创建功能(该功能默认是启用的),主题分区的个数就是该参数指定的值。该参数的默认值是 1。
要注意,我们可以增加主题分区的个数,但不能减少分区的个数。所以,如果要让一个主题的分区个数少于 num.partitions 指定的值,需要手动创建该主题。
第 1 章里已经提到,Kafka 集群通过分区对主题进行横向扩展,所以当有新的 broker 加入集群时,可以通过分区个数来实现集群的负载均衡。当然,这并不是说,在存在多个主题的情况下(它们分布在多个 broker 上),为了能让分区分布到所有 broker 上,主题分区的个数必须要大于 broker 的个数。
不过,拥有大量消息的主题如果要进行负载分散,就需要大量的分区。
如何选定分区数量?
为主题选定分区数量并不是一件可有可无的事情,在进行数量选择时,需要考虑如下几个因素:
• 主题需要达到多大的吞吐量?例如,是希望每秒钟写入 100KB 还是 1GB ?
• 从单个分区读取数据的最大吞吐量是多少?每个分区一般都会有一个消费者,如果你知道消费者将数据写入数据库的速度不会超过每秒 50MB,那么你也该知道,从一个分区读取数据的吞吐量不需要超过每秒 50MB。
• 可以通过类似的方法估算生产者向单个分区写入数据的吞吐量,不过生产者的速度一般比消费者快得多,所以最好为生产者多估算一些吞吐量。
• 每个 broker 包含的分区个数、可用的磁盘空间和网络带宽。
• 如果消息是按照不同的键来写入分区的,那么为已有的主题新增分区就会很困难。
• 单个 broker 对分区个数是有限制的,因为分区越多,占用的内存越多,完成首领选举需要的时间也越长。
很显然,综合考虑以上几个因素,你需要很多分区,但不能太多。
如果你估算出主题的吞吐量和消费者吞吐量,可以用主题吞吐量除以消费者吞吐量算出分区的个数。也就是说,
如果每秒钟要从主题上写入和读取 1GB 的数据,并且每个消费者每秒钟可以处理 50MB的数据,那么至少需要 20 个分区。
这样就可以让 20 个消费者同时读取这些分区,从而达到每秒钟 1GB 的吞吐量。
如果不知道这些信息,那么根据经验,把分区的大小限制在 25GB 以内可以得到比较理想的效果。
- log.retention.ms
Kafka 通常根据时间来决定数据可以被保留多久。默认使用 log.retention.hours 参数来配置时间,默认值为 168 小时,也就是一周。除此以外,还有其他两个参数 log.retention.minutes 和 log.retention.ms。
这 3 个参数的作用是一样的,都是决定消息多久以后会被删除,不过还是推荐使用 log.retention.ms。如果指定了不止一个参数,Kafka 会优先使用具有最小值的那个参数。
image.png
- log.retention.bytes
另一种方式是通过保留的消息字节数来判断消息是否过期。它的值通过参数 log.retention.bytes 来指定,作用在每一个分区上。
也就是说,如果有一个包含 8 个分区的主题,并且 log.retention.bytes 被设为 1GB,那么这个主题最多可以保留 8GB 的数据。
所以,当主题的分区个数增加时,整个主题可以保留的数据也随之增加。
- log.segment.bytes
以上的设置都作用在日志片段上,而不是作用在单个消息上。当消息到达 broker 时,它们被追加到分区的当前日志片段上。
当日志片段大小达到 log.segment.bytes 指定的上限(默认是 1GB)时,当前日志片段就会被关闭,一个新的日志片段被打开。如果一个日志片段被关闭,就开始等待过期。
这个参数的值越小,就会越频繁地关闭和分配新文件,从而降低磁盘写入的整体效率。
如果主题的消息量不大,那么如何调整这个参数的大小就变得尤为重要。
例如,如果一个主题每天只接收 100MB 的消息,而 log.segment.bytes 使用默认设置,那么需要 10 天时
间才能填满一个日志片段。因为在日志片段被关闭之前消息是不会过期的,所以如果 log.retention.ms 被设为 604 800 000(也就是 1 周),那么日志片段最多需要 17 天才会过期。
这是因为关闭日志片段需要 10 天的时间,而根据配置的过期时间,还需要再保留 7 天时间(要等到日志片段里的最后一个消息过期才能被删除)。
- log.segment.ms
另一个可以控制日志片段关闭时间的参数是 log.segment.ms,它指定了多长时间之后日志片段会被关闭。
就像 log.retention.bytes 和 log.retention.ms 这两个参数一样,log.segment.bytes 和 log.retention.ms 这两个参数之间也不存在互斥问题。
日志片段会在大小或时间达到上限时被关闭,就看哪个条件先得到满足。
默认情况下,log.segment.ms 没有设定值,所以只根据大小来关闭日志片段。
- message.max.bytes
broker 通过设置 message.max.bytes 参数来限制单个消息的大小,默认值是 1 000 000,也就是 1MB。
如果生产者尝试发送的消息超过这个大小,不仅消息不会被接收,还会收到broker 返回的错误信息。
跟其他与字节相关的配置参数一样,该参数指的是压缩后的消息大小,也就是说,只要压缩后的消息小于 message.max.bytes 指定的值,消息的实际大小可以远大于这个值。
这个值对性能有显著的影响。值越大,那么负责处理网络连接和请求的线程就需要花越多的时间来处理这些请求。它还会增加磁盘写入块的大小,从而影响 IO 吞吐量。
2.4 硬件的选择
为 Kafka 选择合适的硬件更像是一门艺术。Kafka 本身对硬件没有特别的要求,它可以运行在任何系统上。
不过,如果比较关注性能,那么就需要考虑几个会影响整体性能的因素:磁盘吞吐量和容量、内存、网络和 CPU。
在确定了性能关注点之后,就可以在预算范围内选择最优化的硬件配置。
2.4.1 磁盘吞吐量
生产者客户端的性能直接受到服务器端磁盘吞吐量的影响。生产者生成的消息必须被提交到服务器保存,大多数客户端在发送消息之后会一直等待,直到至少有一个服务器确认消息已经成功提交为止。
也就是说,磁盘写入速度越快,生成消息的延迟就越低。
在考虑硬盘类型对磁盘吞吐量的影响时,是选择传统的机械硬盘(HDD)还是固态硬盘(SSD),我们可以很容易地作出决定。固态硬盘的查找和访问速度都很快,提供了最好的性能。
机械硬盘更便宜,单块硬盘容量也更大。在同一个服务器上使用多个机械硬盘,可以设置多个数据目录,或者把它们设置成磁盘阵列,这样可以提升机械硬盘的性能。
其他方面的因素,比如磁盘特定的技术(串行连接存储技术或 SATA),或者磁盘控制器的质量,都会影响吞吐量。
2.4.2 磁盘容量
磁盘容量是另一个值得讨论的话题。需要多大的磁盘容量取决于需要保留的消息数量。如果服务器每天会收到 1TB 消息,并且保留 7 天,那么就需要 7TB 的存储空间,而且还要为其他文件提供至少 10% 的额外空间。
除此之外,还需要提供缓冲区,用于应付消息流量的增长和波动。
在决定扩展 Kafka 集群规模时,存储容量是一个需要考虑的因素。通过让主题拥有多个分区,集群的总流量可以被均衡到整个集群,而且如果单个 broker 无法支撑全部容量,可以让其他 broker 提供可用的容量。
存储容量的选择同时受到集群复制策略的影响。
2.4.3 内存
除了磁盘性能外,服务器端可用的内存容量是影响客户端性能的主要因素。磁盘性能影响生产者,而内存影响消费者。消费者一般从分区尾部读取消息,如果有生产者存在,就紧跟在生产者后面。
在这种情况下,消费者读取的消息会直接存放在系统的页面缓存里,这比从磁盘上重新读取要快得多。
运行 Kafka 的 JVM 不需要太大的内存,剩余的系统内存可以用作页面缓存,或者用来缓存正在使用中的日志片段。
这也就是为什么不建议把 Kafka 同其他重要的应用程序部署在一起的原因,它们需要共享页面缓存,最终会降低 Kafka 消费者的性能。
2.4.4 网络
网络吞吐量决定了 Kafka 能够处理的最大数据流量。它和磁盘存储是制约 Kafka 扩展规模的主要因素。
Kafka 支持多个消费者,造成流入和流出的网络流量不平衡,从而让情况变得更加复杂。
对于给定的主题,一个生产者可能每秒钟写入 1MB 数据,但可能同时有多个消费者瓜分网络流量。其他的操作,如集群复制(在第 6 章介绍)和镜像也会占用网络流量。如果网络接口出现饱和,那么集群的复制出现延时就在所难免,从而让集群不堪一击。
2.4.5 CPU
与磁盘和内存相比,Kafka 对计算处理能力的要求相对较低,不过它在一定程度上还是会影响整体的性能。客户端为了优化网络和磁盘空间,会对消息进行压缩。
服务器需要对消息进行批量解压,设置偏移量,然后重新进行批量压缩,再保存到磁盘上。这就是Kafka 对计算处理能力有所要求的地方。不过不管怎样,这都不应该成为选择硬件的主要考虑因素。
2.5 云端的Kafka
Kafka 一般被安装在云端,比如亚马逊网络服务(Amazon Web Services,AWS)。AWS 提供了很多不同配置的实例,我们要根据 Kafka 的性能优先级来选择合适的实例。
可以先从要保留数据的大小开始考虑,然后考虑生产者方面的性能。如果要求低延迟,那么就需要专门为 I/O 优化过的使用固态硬盘的实例,否则,使用配备了临时存储的实例就可以了。
选好存储类型之后,再选择 CPU 和内存就容易得多。
实际上,如果使用 AWS,一般会选择 m4 实例或 r3 实例。m4 实例允许较长时间地保留数据,不过磁盘吞吐量会小一些,因为它使用的是弹性块存储。r3 实例使用固态硬盘,具有较高的吞吐量,但保留的数据量会有所限制。
如果想两者兼顾,那么需要升级成 i2 实例或d2 实例,不过它们的成本要高得多。
2.6 Kafka集群
单个 Kafka 服务器足以满足本地开发或 POC 要求,不过集群也有它的强大之处。
使用集群最大的好处是可以跨服务器进行负载均衡,再则就是可以使用复制功能来避免因单点故障造成的数据丢失。在维护 Kafka 或底层系统时,使用集群可以确保为客户端提供高可用性。
image.png
2.6.1 需要多少个broker
一个 Kafka 集群需要多少个 broker 取决于以下几个因素。首先,需要多少磁盘空间来保留数据,以及单个 broker 有多少空间可用。如果整个集群需要保留 10TB 的数据,每个broker 可以存储 2TB,那么至少需要 5 个 broker。
如果启用了数据复制,那么至少还需要一倍的空间,不过这要取决于配置的复制系数是多少(将在第 6 章介绍)。也就是说,如果启用了数据复制,那么这个集群至少需要 10 个 broker。
第二个要考虑的因素是集群处理请求的能力。这通常与网络接口处理客户端流量的能力有关,特别是当有多个消费者存在或者在数据保留期间流量发生波动(比如高峰时段的流量爆发)时。
如果单个 broker 的网络接口在高峰时段可以达到 80% 的使用量,并且有两个消费者,那么消费者就无法保持峰值,除非有两个 broker。如果集群启用了复制功能,则要把这个额外的消费者考虑在内。
因磁盘吞吐量低和系统内存不足造成的性能问题,也可以通过扩展多个 broker 来解决。
2.6.2 broker配置
要把一个 broker 加入到集群里,只需要修改两个配置参数。首先,所有 broker 都必须配置相同的 zookeeper.connect,该参数指定了用于保存元数据的 Zookeeper 群组和路径。
其次,每个 broker 都必须为 broker.id 参数设置唯一的值。如果两个 broker 使用相同的broker.id,那么第二个 broker 就无法启动。在运行集群时,还可以配置其他一些参数,特别是那些用于控制数据复制的参数,这些将在后续的章节介绍。
2.6.3 操作系统调优
大部分 Linux 发行版默认的内核调优参数配置已经能够满足大多数应用程序的运行需求,不过还是可以通过调整一些参数来进一步提升 Kafka 的性能。
这些参数主要与虚拟内存、网络子系统和用来存储日志片段的磁盘挂载点有关。这些参数一般配置在 /etc/sysctl.conf文件里,不过在对内核参数进行调整时,最好参考操作系统的文档。
- 虚拟内存
一般来说,Linux 的虚拟内存会根据系统的工作负荷进行自动调整。我们可以对交换分区的处理方式和内存脏页进行调整,从而让 Kafka 更好地处理工作负载。对于大多数依赖吞吐量的应用程序来说,要尽量避免内存交换。
内存页和磁盘之间的交换对 Kafka 各方面的性能都有重大影响。
Kafka 大量地使用系统页面缓存,如果虚拟内存被交换到磁盘,说明已经没有多余内存可以分配给页面缓存了。
一种避免内存交换的方法是不设置任何交换分区。内存交换不是必需的,不过它确实能够
在系统发生灾难性错误时提供一些帮助。进行内存交换可以防止操作系统由于内存不足而突然终止进程。
基于上述原因,建议把 vm.swappiness 参数的值设置得小一点,比如 1。
该参数指明了虚拟机的子系统将如何使用交换分区,而不是只把内存页从页面缓存里移除。
要优先考虑减小页面缓存,而不是进行内存交换。
脏页会被冲刷到磁盘上,调整内核对脏页的处理方式可以让我们从中获益。
Kafka 依赖 I/O 性能为生产者提供快速的响应。这就是为什么日志片段一般要保存在快速磁盘上,不管是单个快速磁盘(如 SSD)还是具有 NVRAM 缓存的磁盘子系统(如 RAID)。
这样一来,在后台刷新进程将脏页写入磁盘之前,可以减少脏页的数量,这个可以通过将 vm.dirty_background_ratio 设为小于 10 的值来实现。
该值指的是系统内存的百分比,大部分情况下设为 5 就可以了。
它不应该被设为 0,因为那样会促使内核频繁地刷新页面,从而降低内核为底层设备的磁盘写入提供缓冲的能力。
通过设置 vm.dirty_ratio 参数可以增加被内核进程刷新到磁盘之前的脏页数量,可以将它设为大于 20 的值(这也是系统内存的百分比)。
这个值可设置的范围很广,60~80 是个比较合理的区间。不过调整这个参数会带来一些风险,包括未刷新磁盘操作的数量和同步刷新引起的长时间 I/O 等待。
如果该参数设置了较高的值,建议启用 Kafka 的复制功能,避免因系统崩溃造成数据丢失。
为了给这些参数设置合适的值,最好是在 Kafka 集群运行期间检查脏页的数量,不管是在生存环境还是模拟环境。可以在 /proc/vmstat 文件里查看当前脏页数量。
- 磁盘
除了选择合适的磁盘硬件设备和使用 RAID 外,文件系统是影响性能的另一个重要因素。
有很多种文件系统可供选择,不过对于本地文件系统来说,EXT4(第四代可扩展文件系统)和 XFS 最为常见。
近来,XFS 成为很多 Linux 发行版默认的文件系统,因为它只需要做少量调优就可以承担大部分的工作负荷,比 EXT4 具有更好的表现。
EXT4 也可以做得很好,但需要做更多的调优,存在较大的风险。其中就包括设置更长的提交间隔(默认是 5),以便降低刷新的频率。EXT4 还引入了块分配延迟,一旦系统崩溃,更容易造成数据丢失和文件系统毁坏。
XFS 也使用了分配延迟算法,不过比 EXT4 的要安全些。
XFS 为Kafka 提供了更好的性能,除了由文件系统提供的自动调优之外,无需额外的调优。
批量磁盘写入具有更高的效率,可以提升整体的 I/O 吞吐量。
不管使用哪一种文件系统来存储日志片段,最好要对挂载点的 noatime 参数进行合理的设置。
文件元数据包含 3 个时间戳:创建时间(ctime)、最后修改时间(mtime)以及最后访问时间(atime)。
默认情况下,每次文件被读取后都会更新 atime,这会导致大量的磁盘写操作,而且 atime 属性的用处不大,除非某些应用程序想要知道某个文件在最近一次修改后有没有被访问过(这种情况可以使用 realtime)。
Kafka 用不到该属性,所以完全可以把它禁用掉。
为挂载点设置 noatime 参数可以防止更新 atime,但不会影响 ctime 和 mtime。
- 网络
默认情况下,系统内核没有针对快速的大流量网络传输进行优化,所以对于应用程序来说,一般需要对 Linux 系统的网络栈进行调优,以实现对大流量的支持。
实际上,调整Kafka 的网络配置与调整其他大部分 Web 服务器和网络应用程序的网络配置是一样的。
首先可以对分配给 socket 读写缓冲区的内存大小作出调整,这样可以显著提升网络的传输性能。
socket 读写缓冲区对应的参数分别是 net.core.wmem_default 和 net.core.rmem_default,合理的值是 131 072(也就是 128KB)。
读写缓冲区最大值对应的参数分别是net.core.wmem_max 和 net.core.rmem_max,合理的值是 2 097 152(也就是 2MB)。
要注意,最大值并不意味着每个 socket 一定要有这么大的缓冲空间,只是说在必要的情况下才会达到这个值。
除了设置 socket 外,还需要设置 TCP socket 的读写缓冲区,它们的参数分别是 net.ipv4.tcp_wmem 和 net.ipv4.tcp_rmem。这些参数的值由 3 个整数组成,它们使用空格分隔,分别表示最小值、默认值和最大值。
最大值不能大于 net.core.wmem_max 和 net.core.rmem_max指定的大小。
例如,“4096 65536 2048000”表示最小值是 4KB、默认值是 64KB、最大值是 2MB。
根据 Kafka 服务器接收流量的实际情况,可能需要设置更高的最大值,为网络连接提供更大的缓冲空间。
还有其他一些有用的网络参数。例如,把 net.ipv4.tcp_window_scaling 设为 1,启用 TCP时间窗扩展,可以提升客户端传输数据的效率,传输的数据可以在服务器端进行缓冲。
把net.ipv4.tcp_max_syn_backlog 设为比默认值 1024 更大的值,可以接受更多的并发连接。
把 net.core.netdev_max_backlog 设为比默认值 1000 更大的值,有助于应对网络流量的爆发,特别是在使用千兆网络的情况下,允许更多的数据包排队等待内核处理。
2.7 生产环境的注意事项
当你准备把 Kafka 从测试环境部署到生产环境时,需要注意一些事项,以便创建更可靠的消息服务。
2.7.1 垃圾回收器选项
为应用程序调整 Java 垃圾回收参数就像是一门艺术,我们需要知道应用程序是如何使用内存的,还需要大量的观察和试错。
幸运的是,Java 7 为我们带来了 G1 垃圾回收器,让这种状况有所改观。在应用程序的整个生命周期,G1 会自动根据工作负载情况进行自我调
节,而且它的停顿时间是恒定的。
它可以轻松地处理大块的堆内存,把堆内存分为若干小块的区域,每次停顿时并不会对整个堆空间进行回收。
正常情况下,G1 只需要很少的配置就能完成这些工作。以下是 G1 的两个调整参数。
MaxGCPauseMillis:
该参数指定每次垃圾回收默认的停顿时间。该值不是固定的,G1 可以根据需要使用更长的时间。
它的默认值是 200ms。也就是说,G1 会决定垃圾回收的频率以及每一轮需要回收多少个区域,这样算下来,每一轮垃圾回收大概需要 200ms 的时间。
InitiatingHeapOccupancyPercent:
该参数指定了在 G1 启动新一轮垃圾回收之前可以使用的堆内存百分比,默认值是 45。
也就是说,在堆内存的使用率达到 45% 之前,G1 不会启动垃圾回收。这个百分比包括新生代和老年代的内存。
Kafka 对堆内存的使用率非常高,容易产生垃圾对象,所以可以把这些值设得小一些。
如果一台服务器有 64GB 内存,并且使用 5GB 堆内存来运行 Kafka,那么可以参考以下的配置:MaxGCPauseMillis 可以设为 20ms;InitiatingHeapOccupancyPercent 可以设为 35,
这样可以让垃圾回收比默认的要早一些启动。
Kafka 的启动脚本并没有启用 G1 回收器,而是使用了 Parallel New 和 CMS( Concurrent Mark-Sweep,并发标记和清除)垃圾回收器。不过它可以通过环境变量来修改。
本章前面的内容使用 start 命令来修改它:
image.png
2.7.2 数据中心布局
在开发阶段,人们并不会太关心 Kafka 服务器在数据中心所处的物理位置,因为即使集群在短时间内出现局部或完全不可用,也不会造成太大影响。
但是,在生产环境,服务不可用意味着金钱的损失,具体表现为无法为用户提供服务或者不知道用户正在做什么。
这个时候,使用 Kafka 集群的复制功能就变得尤为重要(请参考第 6 章),而服务器在数据中心所处的物理位置也变得重要起来。如果在部署 Kafka 之前没有考虑好这个问题,那么在后续的维护过程中,移动服务器需要耗费更高的成本。
在为 broker 增加新的分区时,broker 并无法获知机架的信息。也就是说,两个 broker 有可能是在同一个机架上,或者在同一个可用区域里(如果运行在像 AWS 这样的的云服务上),所以,在为分区添加副本的时候,这些副本很可能被分配给同一个机架上的 broker,它们使用相同的电源和网络连接。
如果该机架出了问题,这些分区就会离线,客户端就无法访问到它们。
更糟糕的是,如果发生不完整的主节点选举,那么在恢复时就有可能丢失数据。
所以,最好把集群的 broker 安装在不同的机架上,至少不要让它们共享可能出现单点故障的基础设施,比如电源和网络。也就是说,部署服务器需要至少两个电源连接(两个不同的回路)和两个网络交换器(保证可以进行无缝的故障切换)。
除了这些以外,最好还要把 broker 安放在不同的机架上。因为随着时间的推移,机架也需要进行维护,而这会导致机器离线(比如移动机器或者重新连接电源)
2.7.3 共享Zookeeper
Kafka 使用 Zookeeper 来保存 broker、主题和分区的元数据信息。对于一个包含多个节点的Zookeeper 群组来说,Kafka 集群的这些流量并不算多,那些写操作只是用于构造消费者群组或集群本身。
实际上,在很多部署环境里,会让多个 Kafka 集群共享一个 Zookeeper 群组(每个集群使用一个 chroot 路径)。
不过,消费者和 Zookeeper 之间还是有一个值得注意的地方,消费者可以选择将偏移量提交到 Zookeeper 或 Kafka,还可以选择提交偏移量的时间间隔。
如果消费者将偏移量提交到 Zookeeper,那么在每个提交时间点上,消费者将会为每一个消费的分区往 Zookeeper 写入一次偏移量。
合理的提交间隔是 1 分钟,因为这刚好是消费者群组的某个消费者发生失效时能够读取到重复消息的时间。值得注意的是,这些提交对于 Zookeeper 来说流量不算小,特别是当集群里有多个消费者的时候。
如果 Zookeeper 群组无法处理太大的流量,就有必要使用长一点的提交时间间隔。不过不管怎样,还是建议使用最新版本的 Kafka,让
消费者把偏移量提交到 Kafka 服务器上,消除对 Zookeeper 的依赖。
虽然多个 Kafka 集群可以共享一个 Zookeeper 群 组, 但 如 果 有 可 能 的 话, 不 建 议把 Zookeeper 共享给其他应用程序。Kafka 对 Zookeeper 的延迟和超时比较敏感,与Zookeeper 群组之间的一个通信异常就可能导致 Kafka 服务器出现无法预测的行为。
这样很容易让多个 broker 同时离线,如果它们与 Zookeeper 之间断开连接,也会导致分区离线。
这也会给集群控制器带来压力,在服务器离线一段时间之后,当控制器尝试关闭一个服务器时,会表现出一些细小的错误。其他的应用程序因重度使用或进行不恰当的操作给Zookeeper 群组带来压力,所以最好让它们使用自己的 Zookeeper 群组。
网友评论