一.引言
使用 KafkaProducer 生产数据并按照 interval = 60s 的间隔进行 streaming 日志回收,本地执行 KafkaProducer.send 操作后发现回收日志中并没有相关日志,排查原因过程中顺带整理了一下 Kafka 常用参数。
二.常用参数
完整的参数介绍与初始化可以参考 org.apache.kafka.clients.producer.ProducerConfig 类,里面有更详细的参数介绍与初始化类型与大小
1.buffer.memory
kafka 客户端发送数据到服务器会经过缓冲区,通过 KafkaProducer 发送出去的消息都是先进入客户端本地的缓存里,再通过 Sender 线程将多个 batch 的数据发送到 Broker,buffer_memory 默认为 32mb
define("buffer.memory", Type.LONG, 33554432L, ...)
存在的问题:
消息写入过快或者写入量过大,Sender 线程来不及处理,造成缓存区堆积,此时会阻塞用户线程,禁止往 kafka 写入消息,一般需要根据业务场景估算一个 buffer_memory 的合理值
2.batch.size
缓存区 buffer_memory 中存储着多个 batch_size 的数据,正常情况下一个 batch_size 中存储多条数据,每达到 batch_size 后,就会有一批数据写入缓存区。提升 batch_size 可以提升程序的吞吐,但是过大的增加 batch_size 可能会导致缓存区数据量过大或者数据延时发送情况增加
define("batch.size", Type.INT, 16384, ...)
3.max.bloack.ms
如果缓存区打满,发送延迟达到该延迟 ms 时,程序会抛出异常,此时需要提高缓存区的大小或者相应的调整程序写入的速率
4.linger.ms
有一种情况,缓存区迟迟未满,数据长时间无法写入,此时涉及到参数 linger.ms ,该函数的含义是当一个 batch 从创建开始过去 linger.ms 后,不管该 batch 满不满都会写入缓存区发送,也就是最长等待时间,避免一个 batch 没有打满而迟迟不发送的情况,默认值为0
define("linger.ms", Type.LONG, 0, ...)
存在的问题:
假如正常流量下 20ms 就能凑够一个 batch,该参数就应该设置在 20+,因为设置到小于 20ms 会导致 batch 数据还没满就发送,那么 batch 批量发送的意义就不大了
5.max.request.size
这个参数决定了每次发送给Kafka服务器请求的最大大小,同时也会限制你一条消息的最大大小也不能超过这个参数设置的值,默认1 mb
.define("max.request.size", Type.INT, 1048576, ...)
6.retries
日志写入失败的尝试次数
7. reconnect.backoff.ms
网络中断后重连的退避时间,单位为 ms
8.serializer
key/value.serializer kafka Key Value 的序列化方式, StringSerializer、ByteArraySerializer
9.compression.type
日志的压缩格式,"none", "gzip" and "snappy",默认为 "none"
三.数据发送丢失分析
先看下我的 kafka 版本和配置的相关参数:
版本: 0.10.2.1
参数:
// 日志压缩方式
props.put("compression.type", "snappy")
// 日志写入失败的重试次数
props.put("retries", "3")
//网络中断后重连的退避时间 单位毫秒
props.put("reconnect.backoff.ms", "1000")
//日志等待一段时间 将此时间段内的日志批量发送
props.put("linger.ms", "1000")
//key 序列化方式
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer")
//value 序列化方式
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer")
这里没有设置 batch_size,所以按默认的来算 16384 byte = 16 kb,linger.ms = 1000 ms,缓存区的数据还没发送程序就结束了,导致 kafka 没有写出数据,可能因素有两个:
1.数据大小
写入数据太小,未达到 batch_size,batch 对应数据未送入缓存区从而未发送
验证:
一条 kafka 记录的大小就是一个 ProducerRecord 的大小,其中不仅包括要发送信息的大小,还包括 ProducerRecord 元数据的大小,可以通过 org.apache.spark.util.SizeEstimator.estimate 类对该数据大小进行估算,为了排除数据压缩的影响,将 compreesin.type 改为 None(snappy 压缩比率约为20%),共10条数据,每条 4000 byte 左右,所以 10 * 4000 > 16384 ,排除数据太小的原因
val data = new ProducerRecord[String, String](topic, message)
val data_size = org.apache.spark.util.SizeEstimator.estimate(data)
2.linger.ms 延迟
linger.ms 设置太高,数据还没发送但是主程序已经结束,所以发送失败,该参数会等待一定时间得以让 buffer_memory 中的内容批量发送 request,程序结束太快导致没到 linger.ms 程序就结束了,所以发送失败
验证:
为了验证这里比较极端,调整 linger.ms = 0,经过测试可以正常接收数据,下面为官方解释,有兴趣可以翻一下~
"The producer groups together any records that arrive in between request transmissions into a single batched request. "
+ "Normally this occurs only under load when records arrive faster than they can be sent out. However in some circumstances the client may want to "
+ "reduce the number of requests even under moderate load. This setting accomplishes this by adding a small amount "
+ "of artificial delay—that is, rather than immediately sending out a record the producer will wait for up to "
+ "the given delay to allow other records to be sent so that the sends can be batched together. This can be thought "
+ "of as analogous to Nagle's algorithm in TCP. This setting gives the upper bound on the delay for batching: once "
+ "we get <code>" + BATCH_SIZE_CONFIG + "</code> worth of records for a partition it will be sent immediately regardless of this "
+ "setting, however if we have fewer than this many bytes accumulated for this partition we will 'linger' for the "
+ "specified time waiting for more records to show up. This setting defaults to 0 (i.e. no delay). Setting <code>" + LINGER_MS_CONFIG + "=5</code>, "
+ "for example, would have the effect of reducing the number of requests sent but would add up to 5ms of latency to records sent in the absense of load.";
3.发送数据的方法
A.调低 linger.ms (推荐)
正常集群环境下,可以设置 linger.ms 为 200ms - 300ms ,也可以根据自己场景配置
B. KafkaProducer.flush()
该方法会将缓存区的内容全部执行发送,但是会造成堵塞
C. KafkaProducer.close()
该方法会将生产者关闭,关闭前将缓冲区内容全部执行发送,会造成堵塞
四.batch 生成逻辑
上面简单了解了参数和 kafka 写数据的简要流程,其中有一步是生成 batch ,这个步骤和 batzh.size 相关联,还有一个参数 max.request.size 控制一次请求的最大大小,正常情况下 batch.size < max.request.size,趁热打铁,整理一下常见的 batch 生成情况:
1.单条数据
A.低于 batch.size
等到 linger.ms 延时时间到,生成 batch 进入缓存区
B.介于 batch.size 和 max.request.size
生成一个大于 batch.size 的大 batch 进入缓存区
C.大于 max.request.size
异常
2.多条数据
A. N 条数据 = batch.size
N 条数据包装为一个 batch 进入缓存区
B.N 条数据 < batch.size
N 条数据等待 linger.ms 延时后包装为一个 batch 进入缓存区
C.N + 1条数据 ( N条 < batch.size ,N +1 条 > batch.size)
N 条数据包装为 batch 送入缓存区,1 条等待新的数据达到 batch.size 的触发条件