使用TimeBasedPartitioner的Kafka Connect S3连接器内存不足错误

时间:2018-06-21 14:30:32

标签: amazon-s3 out-of-memory apache-kafka-connect

我目前正在与Kafka Connect S3 Sink Connector 3.3.1一起将Kafka消息复制到S3,处理后期数据时出现OutOfMemory错误。

我知道这似乎是一个很长的问题,但是我尽力使它变得清晰和易于理解。 非常感谢您的帮助。

高级信息

  • 连接器对Kafka消息进行简单的字节到字节的复制,并在字节数组的开头添加消息的长度(用于解压缩)。
    • 这是CustomByteArrayFormat类的角色(请参见下面的配置)
  • 根据Record时间戳对数据进行分区和存储
    • CustomTimeBasedPartitioner扩展了io.confluent.connect.storage.partitioner.TimeBasedPartitioner,其唯一目的是覆盖generatePartitionedPath方法以将主题放在路径的末尾。
  • Kafka Connect进程的总堆大小为24GB(仅一个节点)
  • 连接器每秒处理8,000至10,000条消息
  • 每封邮件的大小接近1 KB
  • Kafka主题有32个分区

内存不足错误的上下文

  • 这些错误仅在连接器关闭几个小时并且必须赶上数据时才会发生
  • 重新打开连接器时,它会开始追赶,但由于OutOfMemory错误而很快失败

可能但不完整的解释

  • 发生OOM错误时,将连接器的timestamp.extractor配置设置为Record
  • 将此配置切换为Wallclock(即Kafka Connect进程的时间)请勿抛出OOM错误,并且可以处理所有最新数据,但不再正确存储最新数据
    • 所有最近的数据都将在连接器重新打开的YYYY/MM/dd/HH/mm/topic-name时间段内存储。
  • 因此,我的猜测是,当连接器尝试根据Record时间戳正确存储数据时,它执行的并行读取过多,导致OOM错误
    • "partition.duration.ms": "600000"参数使连接器存储桶数据以每小时6个10分钟的路径进行传输(2018/06/20/12/[00|10|20|30|40|50]适用于2018年6月20日中午12点)
    • 因此,在有24h的延迟数据的情况下,连接器将不得不在24h * 6 = 144个不同的S3路径中输出数据。
    • 每个10分钟文件夹包含10,000条消息/秒* 600秒= 6,000,000条消息,大小为6 GB
    • 如果确实并行读取,则将使864GB的数据进入内存
  • 我认为我必须正确配置一组给定的参数以避免这些OOM错误,但是我不觉得我看到了大图
    • "flush.size": "100000"表示,如果有100,000条以上的消息被读取,则应将它们提交到文件中(从而释放内存)。
      • 对于1KB的消息,这意味着每100MB提交一次
      • 但是,即使有144个并行读取,这仍然只能提供14.4 GB的总空间,这比可用的堆大小要少24GB。
      • 在提交之前,"flush.size"是要每个分区读取的记录数吗?还是每个连接器的任务
    • 我了解"rotate.schedule.interval.ms": "600000"配置的方式是,即使未达到flush.size的100,000条消息,数据也将每10分钟提交一次。

我的主要问题是,给出给定的内存使用量的数学运算是什么:

  • 每秒的数量或记录
  • 记录的大小
  • 我从中读取的主题的Kafka分区数量
  • 连接器任务的数量(如果相关)
  • 每小时写入的存储桶数(此处为6,这是因为"partition.duration.ms": "600000"配置)
  • 要处理的最新数据的最大小时数

配置

S3接收器连接器配置

{
  "name": "xxxxxxx",
  "config": {
    "connector.class": "io.confluent.connect.s3.S3SinkConnector",
    "s3.region": "us-east-1",
    "partition.duration.ms": "600000",
    "topics.dir": "xxxxx",
    "flush.size": "100000",
    "schema.compatibility": "NONE",
    "topics": "xxxxxx,xxxxxx",
    "tasks.max": "16",
    "s3.part.size": "52428800",
    "timezone": "UTC",
    "locale": "en",
    "format.class": "xxx.xxxx.xxx.CustomByteArrayFormat",
    "partitioner.class": "xxx.xxxx.xxx.CustomTimeBasedPartitioner",
    "schema.generator.class": "io.confluent.connect.storage.hive.schema.DefaultSchemaGenerator",
    "name": "xxxxxxxxx",
    "storage.class": "io.confluent.connect.s3.storage.S3Storage",
    "s3.bucket.name": "xxxxxxx",
    "rotate.schedule.interval.ms": "600000",
    "path.format": "YYYY/MM/dd/HH/mm",
    "timestamp.extractor": "Record"
}

工作人员配置

bootstrap.servers=XXXXXX
key.converter=org.apache.kafka.connect.converters.ByteArrayConverter
value.converter=org.apache.kafka.connect.converters.ByteArrayConverter
internal.key.converter=org.apache.kafka.connect.json.JsonConverter
internal.value.converter=org.apache.kafka.connect.json.JsonConverter
internal.key.converter.schemas.enable=false
internal.value.converter.schemas.enable=false
consumer.auto.offset.reset=earliest
consumer.max.partition.fetch.bytes=2097152
consumer.partition.assignment.strategy=org.apache.kafka.clients.consumer.RoundRobinAssignor
group.id=xxxxxxx
config.storage.topic=connect-configs
offset.storage.topic=connect-offsets
status.storage.topic=connect-status
rest.advertised.host.name=XXXX

修改

我忘记添加我遇到的错误的示例:

2018-06-21 14:54:48,644] ERROR Task XXXXXXXXXXXXX-15 threw an uncaught and unrecoverable exception (org.apache.kafka.connect.runtime.WorkerSinkTask:482)
java.lang.OutOfMemoryError: Java heap space
[2018-06-21 14:54:48,645] ERROR Task is being killed and will not recover until manually restarted (org.apache.kafka.connect.runtime.WorkerSinkTask:483)
[2018-06-21 14:54:48,645] ERROR Task XXXXXXXXXXXXXXXX-15 threw an uncaught and unrecoverable exception (org.apache.kafka.connect.runtime.WorkerTask:148)
org.apache.kafka.connect.errors.ConnectException: Exiting WorkerSinkTask due to unrecoverable exception.
    at org.apache.kafka.connect.runtime.WorkerSinkTask.deliverMessages(WorkerSinkTask.java:484)
    at org.apache.kafka.connect.runtime.WorkerSinkTask.poll(WorkerSinkTask.java:265)
    at org.apache.kafka.connect.runtime.WorkerSinkTask.iteration(WorkerSinkTask.java:182)
    at org.apache.kafka.connect.runtime.WorkerSinkTask.execute(WorkerSinkTask.java:150)
    at org.apache.kafka.connect.runtime.WorkerTask.doRun(WorkerTask.java:146)
    at org.apache.kafka.connect.runtime.WorkerTask.run(WorkerTask.java:190)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

1 个答案:

答案 0 :(得分:4)

我终于能够理解Kafka Connect S3连接器中堆大小用法的工作原理

  • S3连接器会将每个Kafka分区的数据写入分区的 paths
    • paths 的分区方式取决于partitioner.class参数;
    • 默认情况下,它是通过时间戳记的,然后partition.duration.ms的值将确定每个已分区的 paths 的持续时间。
  • S3连接器将为每个Kafka分区(针对所有读取的主题)和每个分区的 s3.part.size 分配paths个字节的缓冲区
    • 示例读取20个分区,将timestamp.extractor设置为Record,将partition.duration.ms设置为1h,将s3.part.size设置为50 MB
      • 每小时所需的堆大小等于20 * 50 MB = 1 GB;
      • 但是,将timestamp.extractor设置为Record时,具有对应于早于其读取时间的小时的时间戳的消息将被缓冲在此较早的时间缓冲区中。因此,实际上,连接器将需要最少20 * 50 MB * 2h = 2 GB的内存,因为总会出现延迟的事件,而如果事件的延迟时间超过1小时,则连接器将需要更多的内存;
      • 请注意,如果将timestamp.extractor设置为Wallclock,则情况并非如此,因为就Kafka Connect而言,几乎不会发生迟发事件。
    • 这些缓冲区在3种情况下被刷新(即离开内存)
      • rotate.schedule.interval.ms时间过去了
        • 此刷新条件总是触发
      • rotate.interval.ms时间过去了 timestamp.extractor时间
        • 这意味着,如果将timestamp.extractor设置为Record,则Record的10分钟时间可能会减少或超过10分钟的实际时间
          • 例如,当处理较晚的数据时,将在几秒钟内处理价值10分钟的数据,如果将rotate.interval.ms设置为10分钟,则此条件将每秒触发一次(应有的时间); < / li>
          • 相反,如果事件流中有暂停,则只有在看到带有时间戳的事件表明该条件自上次触发以来已经超过rotate.interval.ms时,该条件才会触发。
          • li>
      • flush.size条消息的读取时间少于min(rotate.schedule.interval.msrotate.interval.ms)
        • 对于rotate.interval.ms,如果没有足够的消息,这种情况可能永远不会触发。
    • 因此,您至少需要计划Kafka partitions * s3.part.size堆大小
      • 如果您使用Record时间戳进行分区,则应将其乘以max lateness in milliseconds / partition.duration.ms
        • 这是最坏的情况,您在所有分区中以及max lateness in milliseconds的所有范围中都经常发生延迟事件。
  • 当从Kafka读取数据时,S3连接器还将为每个分区缓冲consumer.max.partition.fetch.bytes个字节
    • 默认设置为2.1 MB。
  • 最后,您不应该考虑所有的堆大小都可用于缓冲Kafka消息,因为其中还有很多不同的对象
    • 一个安全的考虑是确保Kafka消息的缓冲不超过总可用堆大小的50%。