前面已经和大家说过Kafka高吞吐的神秘面纱,Kafka的设计初衷是尽一切努力在内存中完成数据交换,无论是对外作为一整个消息系统,或是内部同底层操作系统的交互。如果Producer和Consumer之间生产和消费进度上配合得当,完全可以实现数据交换零I/O。
那么Kafka的优化就可以从这两方面入手了。首先从Producer端开始要保证消息有序。
Kafka保证在同一主题同一分区内有序生产者将消息按Key分组如(Table+PK),一个分组写入一个分区。所以如何去合理partition是非常有必要的。
Partition是Kafka可以很好的横向扩展和提供高并发处理以及实现Replication的基础。
扩展性方面:
首先,Kafka允许Partition在集群内的Broker之间任意移动,以此来均衡可能存在的数据倾斜问题。其次,Partition支持自定义的分区算法,例如可以将同一个Key的所有消息都路由到同一个Partition上去。
同时Leader也可以在In-Sync的Replica中迁移。由于针对某一个Partition的所有读写请求都是只由Leader来处理,所以Kafka会尽量把Leader均匀的分散到集群的各个节点上,以免造成网络流量过于集中。
并发方面:
任意Partition在某一个时刻只能被一个Consumer Group内的一个Consumer消费(反过来一个Consumer则可以同时消费多个Partition),Kafka非常简洁的Offset机制最小化了Broker和Consumer之间的交互,这使Kafka并不会像同类其他消息队列一样,随着下游Consumer数目的增加而成比例的降低性能。
此外,如果多个Consumer恰巧都是消费时间序上很相近的数据,可以达到很高的PageCache命中率,因而Kafka可以非常高效的支持高并发读操作,实践中基本可以达到单机网卡上限。
Partition的数量是越多越好吗?
Partition的数量并不是越多越好,Partition的数量越多,平均到每一个Broker上的数量也就越多。考虑到Broker宕机(Network Failure, Full GC)的情况下,需要由Controller来为所有宕机的Broker上的所有Partition重新选举Leader,假设每个Partition的选举消耗10ms,如果Broker上有500个Partition,那么在进行选举的5s的时间里,对上述Partition的读写操作都会触发LeaderNotAvailableException。
再进一步,如果挂掉的Broker是整个集群的Controller,那么首先要进行的是重新任命一个Broker作为Controller。新任命的Controller要从Zookeeper上获取所有Partition的Meta信息,获取每个信息大概3-5ms,那么如果有10000个Partition这个时间就会达到30s-50s。而且不要忘记这只是重新启动一个Controller花费的时间,在这基础上还要再加上前面说的选举Leader的时间。
此外,在Broker端,对Producer和Consumer都使用了Buffer机制。其中Buffer的大小是统一配置的,数量则与Partition个数相同。如果Partition个数过多,会导致Producer和Consumer的Buffer内存占用过大。
总结:
其实在Producer端的优化大部分消息系统采取的方式都比较单一,无非也就化零为整、同步变异步这么几种。
Kafka系统默认支持MessageSet,把多条Message自动地打成一个Group后发送出去,均摊后拉低了每次通信的RTT。而且在组织MessageSet的同时,还可以把数据重新排序,从爆发流式的随机写入优化成较为平稳的线性写入。
此外,还要着重介绍的一点是,Producer支持End-to-End的压缩。数据在本地压缩后放到网络上传输,在Broker一般不解压(除非指定要Deep-Iteration),直至消息被Consume之后在客户端解压。
当然用户也可以选择自己在应用层上做压缩和解压的工作(毕竟Kafka目前支持的压缩算法有限,只有GZIP和Snappy),不过这样做反而会意外的降低效率! Kafka的End-to-End压缩与MessageSet配合在一起工作效果最佳,上面的做法直接割裂了两者间联系。至于道理其实很简单,压缩算法中一条基本的原理“重复的数据量越多,压缩比越高”。无关于消息体的内容,无关于消息体的数量,大多数情况下输入数据量大一些会取得更好的压缩比。
消息副本保证
配置request.required.acks参数
0 生产者从不等待ack
1 生产者等Leader写成功后返回
-1 /all 生产者Leader和所有ISR中的Follower写成功后返回
配置min.insync.replicas参数
该属性规定了最小的ISR数。当producer设置request.required.acks为all或-1时,指定副本(replicas)的最小数目,如果这个数目没有达到,producer会产生异常。
Producer数据丢失
每次发送数据时,Producer都是send()之后就认为已经发送出去了,但其实大多数情况下消息还在内存的MessageSet当中,尚未发送到网络,这时候如果Producer挂掉,那就会出现丢数据的情况。
解决办法:
(1)如果对性能要求较高,又能在一定程度上允许Message的丢失,那就可以设置request.required.acks=0 来关闭ack,以全速发送。
2)如果需要对发送的消息进行确认,就需要设置request.required.acks为1或-1。
a)如果配置为1,表示消息只需要被Leader接收并确认即可,其他的Replica可以进行异步拉取无需立即进行确认,在保证可靠性的同时又不会把效率拉得很低。
(b)如果设置为-1,表示消息要Commit到该Partition的ISR集合中的所有Replica后,才可以返回ack,消息的发送会更安全,而整个过程的延迟会随着Replica的数量正比增长,这里就需要根据不同的需求做相应的优化。
在生产环境中,往往需要根据实际的数据量和大小来决定更好的参数配置。
下面就给大家解析几个常用的参数:
无需添加所有的集群地址,Kafka 会根据提供的地址发现其他的地址,但尽量多提供几个,以防提供的服务器关闭。
acks=0 如果设置为 0,那么生产者将不等待任何消息确认。消息将立刻添加到 socket 缓冲区并考虑发送。在这种情况下不能保障消息被服务器接收到。并且重试机制不会生效(因为客户端不知道故障了没有)。每个消息返回的 offset 始终设置为-1。
acks=1,这意味着 leader 写入消息到本地日志就立即响应,而不等待所有follower 应答。在这种情况下,如果响应消息之后但 follower 还未复制之前 leader立即故障,那么消息将会丢失。
acks=all 这意味着 leader 将等待所有副本同步后应答消息。此配置保障消息不会丢失(只要至少有一个同步的副本)。这是最强壮的可用性保障。
等价于acks=-1。
生产者用来缓存等待发送到服务器的消息的内存总字节数。如果消息发送比可传递到服务器的快,生产者将阻塞 max.block.ms 之后,抛出异常。此设置应该大致对应生产者将要使用的总内存,但不是硬约束,因为生产者所使用的所有内存都用于缓冲。一些额外的内存将用于压缩(如果启动压缩),以及用于保持发送中的请求。
首先要明确一点,那就是在内存缓冲里大量的消息会缓冲在里面,形成一个一个的 Batch,每个 Batch 里包含多条消息。然后 KafkaProducer 有一个 Sender线程会把多个 Batch 打包成一个 Request 发送到 Kafka 服务器上去。
那么如果要是内存设置的太小,可能导致一个问题:消息快速的写入内存缓冲里面,但是Sender 线程来不及把 Request 发送到 Kafka 服务器。这样是不是会造成内存缓冲很快就被写满?一旦被写满,就会阻塞用户线程,不让继续往 Kafka 写消息了。
所以对于“buffer.memory”这个参数应该结合自己的实际情况来进行压测,你需要测算一下在生产环境,你的用户线程会以每秒多少消息的频率来写入内存缓冲。比如说每秒 300 条消息,那么你就需要压测一下,假设内存缓冲就 32MB,每秒写 300 条消息到内存缓冲,是否会经常把内存缓冲写满?经过这样的压测,你可以调试出来一个合理的内存大小。
当多个消息要发送到相同分区的时,生产者尝试将消息批量打包在一起,以减少请求交互。这样有助于客户端和服务端的性能提升。该配置的默认批次大小(以字节为单位):16384,即 16KB。
不会打包大于此配置大小的消息,发送到broker 的请求将包含多个批次,每个分区一个,用于发送数据。较小的批次大小有可能降低吞吐量(批次大小为 0 则完全禁用批处理)。一个非常大的批次大小
可能更浪费内存,因为我们会预先分配这个资源。
比如说发送消息的频率就是每秒 300 条,那么如果 batch.size 调节到了 32KB,或者 64KB,也不会提升发送消息的整体吞吐量。
理论上来说,提升 batch 的大小,可以允许更多的数据缓冲在里面,那么一次 Request 发送出去的数据量就更多了,这样吞吐量可能会有所提升。但是也不能无限的大,过于大了之后,要是数据老是缓冲在 Batch 里迟迟不发送出去,那么岂不是你发送消息的延迟就会很高。
比如说,一条消息进入了 Batch,但是要等待 5 秒钟 Batch 才凑满了 64KB,才能发送出去。那这条消息的延迟就是 5 秒钟。
所以需要在这里按照生产环境的发消息的速率,调节不同的 Batch 大小自己测试一下最终出去的吞吐量以及消息的延迟,设置一个最合理的参数。
数据压缩的类型。默认为空(就是不压缩)。有效的值有 none,gzip,snappy, 或 lz4。压缩全部的数据批,因此批的效果也将影响压缩的比率(更多的批次意味着更好的压缩)。
设置一个比零大的值,客户端如果发送失败则会重新发送。注意,这个重试功能和客户端在接到错误之后重新发送没什么不同。如果max.in.flight.requests.per.connection 没有设置为 1,有可能改变消息发送的顺序,因为如果 2 个批次发送到一个分区中,并第一个失败了并重试,但是第二个成功了,那么第二个批次将超过第一个。
“retries”和“retries.backoff.ms”决定了重试机制,也就是如果一个请求失败了可以重试几次,每次重试的间隔是多少毫秒。这个大家适当设置几次重试的机会,给一定的重试间隔即可,比如给 100ms 的重试间隔。
指逗留时间,这个逗留指的是消息不立即发送,而是逗留这个时间后一块发送。这个设置是比较有用的,有时候消息产生的要比能够发送的要快,这个参数完美的实现了一个人工的延迟,使得大批量可以聚合到一个 Batch 里一块发送, 当 Batch 慢了的话,会忽略这个参数立即发送。默认值 : 0。
当发出请求时传递给服务器的 id 字符串。这样做的目的是允许服务器请求记录这个“逻辑应用名”,这样能够追踪请求的源,而不仅仅只是 ip:port。
request.required.acks属性取值含义
三种机制,性能依次递减 (producer吞吐量降低),数据健壮性则依次递增。
request.required.acks=0
producer.type=async
在异步模式下,一个batch发送的消息数量。producer会等待直到要发送的消息数量达到这个值,之后才会发送。但如果消息数量不够,达到queue.buffer.max.ms时也会直接发送。
batch.num.messages=100
默认值:200,当使用异步模式时,缓冲数据的最大时间。例如设为100的话,会每隔100毫秒把所有的消息批量发送。这会提高吞吐量,但是会增加消息的到达延时.
queue.buffering.max.ms=100
默认值:5000,在异步模式下,producer端允许buffer的最大消息数量,如果producer无法尽快将消息发送给broker,从而导致消息在producer端大量沉积,如果消息的条数达到此配置值,将会导致producer端阻塞或者消息被抛弃。
queue.buffering.max.messages=1000
默认值:10000,发送队列缓冲长度;当消息在producer端沉积的条数达到queue.buffering.max.meesages 时,阻塞一定时间后,队列仍然没有enqueue(producer仍然没有发送出任何消息)。此时producer可以继续阻塞或者将消息抛弃,
queue.enqueue.timeout.ms=100
此timeout值用于控制阻塞的时间,如果值为-1(默认值)则 无阻塞超时限制,消息不会被抛弃;如果值为0 则立即清空队列,消息被抛弃。
compression.codec=gzip
消息压缩
Kafka设计的初衷是迅速处理短小的消息,一般10K大小的消息吞吐性能最好(可参见LinkedIn的kafka性能测试)。但有时候,我们需要处理更大的消息,比如XML文档或JSON内容,一个消息差不多有10-100M,这种情况下,Kakfa应该如何处理?
以上方法解决不了问题,还是需要处理超大数据,那么,则可以在kafka中设置下面一些参数:
message.max.bytes(默认:1000000) -–- broker能接收消息的最大字节数,这个值应该比消费端的fetch.message.max.bytes更小才对,否则broker就会因为消费端无法使用这个消息而挂起。
log.segment.bytes(默认: 1GB) --– kafka数据文件的大小,确保这个数值大于一个消息的长度。一般说来使用默认值即可(一般一个消息很难大于1G,因为这是一个消息系统,而不是文件系统)。
replica.fetch.max.bytes(默认: 1MB) --– broker可复制的消息的最大字节数。这个值应该比message.max.bytes大,否则broker会接收此消息,但无法将此消息复制出去,从而造成数据丢失。
fetch.message.max.bytes (默认 1MB) --– 消费者能读取的最大消息。这个值应该大于或等于message.max.bytes。
kafka在消息为10K时吞吐量达到最大,更大的消息会降低吞吐量,在设计集群的容量时,尤其要考虑这点。
可用的内存和分区数:
Brokers会为每个分区分配replica.fetch.max.bytes参数指定的内存空间,假设replica.fetch.max.bytes=1M,且有1000个分区,则需要差不多1G的内存,确保 分区数 * 最大的消息不会超过服务器的内存,否则会报OOM错误。
同样地,消费端的 fetch.message.max.bytes指定了最大消息需要的内存空间,同样,分区数*最大需要内存空间 不能超过服务器的内存。所以,如果你有大的消息要传送,则在内存一定的情况下,只能使用较少的分区数或者使用更大内存的服务器。
Consumer API分为High level和Low level两种:
通过Consumer Group,可以支持生产者消费者和队列访问两种模式。
前一种重度依赖Zookeeper,所以性能差一些且不自由,但是超省心。
第二种不依赖Zookeeper服务,无论从自由度和性能上都有更好的表现,但是所有的异常(Leader迁移、Offset越界、Broker宕机等)和Offset的维护都需要自行处理。
指定了消费者是否自动提交偏移量,默认值是 true,为了尽量避免重复数据和数据丢失,可以把它设置为 false,有自己控制合适提交偏移量,如果设置为true,可以通过设置 auto.commit.interval.ms 属性来控制提交的频率。
该参数指定了消费者在读取一个没有 Offset 或者 Offset 无效(消费者长时间失效,当前的 Offset 已经过时并且被删除了)的分区的情况下,应该作何处理,默认值是 latest,也就是从最新记录读取数据(消费者启动之后生成的记录);
另一个值是 earliest,意思是在偏移量无效的情况下,消费者从起始位置开始读取数据。
该参数指定了当消费者被认为已经挂掉之前可以与服务器断开连接的时间。默认是 3s,消费者在 3s 之内没有再次向服务器发送心跳,那么将会被认为已经死亡。此时,协调器将会触发再均衡,把它的分区分配给其他的消费者;
该参数与 heartbeat.interval.ms 紧密相关,该参数定义了消费者发送心跳的时间间隔,也就是心跳频率,一般要同时修改这两个参数;heartbeat.interval.ms 参数值必须要小 于 session.timeout.ms ,一般是 session.timeout.ms 的三分之一;
比如,session.timeout.ms 设置成 3min,那么 heartbeat.interval.ms 一般设置成 1min,这样,可以更快的检测以及恢复崩溃的节点,不过长时间的轮询或垃圾收集可能导致非预期的再均衡(有一种情况就是网络延迟,本身消费者是没有挂掉的,但是网络延迟造成了心跳超时,这样本不该发生再均衡,但是因为网络原因造成了非预期的再均衡),把该参数的值设置得大一些,可以减少意外的再均衡,不过检测节点崩溃需要更长的时间。
该参数指定了服务器从每个分区里返回给消费者的最大字节数。它的默认值是 1MB,也就是说,KafkaConsumer.poll()方法从每个分区里返回的记录最多不超 max.partitions.fetch.bytes 指定的字节。
如果一个主题有 20 个分区和 5 个消费者,那么每个消费者需要至少 4MB 的可用内存来接收记录。在为消费者分配内存时,可以给它们多分配一些,因为如果群组里有消费者发生崩溃,剩下的消费者需要处理更多的分区。
max.partition.fetch.bytes 的值必须比 Broker 能够接收的最大消息的字节数(通过 max.message.size 属性配置)大, 否则消费者可能无法读取这些消息,导致消费者一直挂起重试。
例如,max.message.size 设置为 2MB,而该属性设置为 1MB,那么当一个生产者可能就会生产一条大小为 2MB 的消息,那么就会出现问题,消费者能从分区取回的最大消息大小就只有 1MB,但是数据量是 2MB,所以就会导致消费者一直挂起重试。
在设置该属性时,另一个需要考虑的因素是消费者处理数据的时间。消费者需要频繁调用 poll()方法来避免会话过期和发生分区再均衡,如果单次调用 poll()返回的数据太多,消费者需要更多的时间来处理,可能无怯及时进行下一个轮询来避免会话过期。
如果出现这种情况,可以把max.partitioin.fetch.bytes 值改小,或者延长会话过期时间。
消费者从服务器获取记录的最小字节数,Broker 收到消费者拉取数据的请求的时候,如果可用数据量小于设置的值,那么 Broker 将会等待有足够可用的数据的时候才返回给消费者,这样可以降低消费者和 Broker 的工作负载。
因为当主题不是很活跃的情况下,就不需要来来回回的处理消息,如果没有很多可用数据,但消费者的CPU 使用率却很高,那么就需要把该属性的值设得比默认值大。
如果消费者的数量比较多,把该属性的值设置得大一点可以降低 Broker 的工作负载。
fetch.min.bytes 设置了 Broker 返 回 给 消 费 者 最 小 的 数 据 量 , 而fetch.max.wait.ms设置的则是Broker的等待时间,两个属性只要满足了任何一条,Broker 都会将数据返回给消费者;
也就是说举个例子,fetch.min.bytes 设置成 1MB,fetch.max.wait.ms 设置成 1000ms,那么如果在 1000ms 时间内,如果数据量达到了 1MB,Broker 将会把数据返回给消费者;
如果已经过了1000ms,但是数据量还没有达到 1MB,那么 Broker 仍然会把当前积累的所有数据返回给消费者。
socket 在读写数据时用到的 TCP 缓冲区也可以设置大小。如果它们被设为-1 ,就使用操作系统的默认值。如果生产者或消费者与 broker 处于不同的数据中心内,可以适当增大这些值,因为跨数据中心的网络一般都有比较高的延迟和比较低的带宽。
Consumer 进程的标识。如果设置成可读的值,跟踪问题会比较方便。
Kafka参数优化就说到这里!有什么好的建议希望大家能给小编留言,最后关注走一波!