簡體   English   中英

當我們的 Kafka 分區中存在滯后時,Akka Kafka 消費者處理率會急劇下降

[英]Akka Kafka Consumer processing rate decreases drastically when lag is there in our Kafka Partitions

我們正面臨這樣一種情況:只要有延遲,我們的 akka-stream-kaka-consumer 處理率就會下降。 當我們在沒有任何分區延遲的情況下啟動它時,處理速度會突然增加。

MSK 集群 - 10 個主題 - 每個 40 個分區 => 400 個領導分區

為了在系統中實現高吞吐量和並行性,我們實現了 akka-stream-kafka 消費者分別訂閱每個主題分區,從而在消費者和分區之間實現 1:1 映射。

這是消費者設置:

  1. ec2 服務實例數 - 7
  2. 每個服務為 10 個主題中的每一個啟動 6 個消費者,從而導致每個服務實例有 60 個消費者。
  3. 消費者總數 = 實例數 (7) * 每個服務實例上的消費者數 (60) = 420

因此,我們總共啟動了 420 個消費者,分布在不同的實例中。 根據 RangeAssignor 分區策略(默認),每個分區將分配給不同的消費者,400 個消費者將使用 400 個分區,20 個消費者將保持未使用。 我們已經驗證了這個分配,看起來不錯。

使用的實例類型: c5.xlarge

MSK 配置:

Apache Kafka 版本- 2.4.1.1

經紀人總數- 9(分布在 3 個可用區)

經紀商類型: kafka.m5.large

每個區域的經紀人: 3

auto.create.topics.enable =true

default.replication.factor =3

min.insync.replicas =2

數量.io.threads = 8

num.network.threads = 5

num.partitions = 40

num.replica.fetchers =2

replica.lag.time.max.ms =30000

socket.receive.buffer.bytes =102400

socket.request.max.bytes =104857600

socket.send.buffer.bytes =102400

unclean.leader.election.enable =true

zookeeper.session.timeout.ms =18000

log.retention.ms =259200000

這是我們為每個消費者使用的配置

akka.kafka.consumer {
 kafka-clients {
  bootstrap.servers = "localhost:9092"
  client.id = "consumer1"
  group.id = "consumer1"
  auto.offset.reset="latest"
 }
 aws.glue.registry.name="Registry1"
 aws.glue.avroRecordType = "GENERIC_RECORD"
 aws.glue.region = "region"
 

    kafka.value.deserializer.class="com.amazonaws.services.schemaregistry.deserializers.avro.AWSKafkaAvroDeserializer"

 # Settings for checking the connection to the Kafka broker. Connection checking uses `listTopics` requests with the timeout
 # configured by `consumer.metadata-request-timeout`
 connection-checker {

  #Flag to turn on connection checker
  enable = true

  # Amount of attempts to be performed after a first connection failure occurs
  # Required, non-negative integer
  max-retries = 3

  # Interval for the connection check. Used as the base for exponential retry.
  check-interval = 15s

  # Check interval multiplier for backoff interval
  # Required, positive number
  backoff-factor = 2.0
 }
}

akka.kafka.committer {

 # Maximum number of messages in a single commit batch
 max-batch = 10000

 # Maximum interval between commits
 max-interval = 5s

 # Parallelism for async committing
 parallelism = 1500

 # API may change.
 # Delivery of commits to the internal actor
 # WaitForAck: Expect replies for commits, and backpressure the stream if replies do not arrive.
 # SendAndForget: Send off commits to the internal actor without expecting replies (experimental feature since 1.1)
 delivery = WaitForAck

 # API may change.
 # Controls when a `Committable` message is queued to be committed.
 # OffsetFirstObserved: When the offset of a message has been successfully produced.
 # NextOffsetObserved: When the next offset is observed.
 when = OffsetFirstObserved
}


akka.http {
 client {
  idle-timeout = 10s
 }
 host-connection-pool {
  idle-timeout = 10s
  client {
   idle-timeout = 10s
  }
 }
}

consumer.parallelism=1500

我們使用下面的代碼來實現從 Kafka 到空接收器的流

override implicit val actorSystem = ActorSystem("Consumer1")
override implicit val materializer = ActorMaterializer()
override implicit val ec = system.dispatcher
val topicsName = "Set of Topic Names"
val parallelism = conf.getInt("consumer.parallelism")


val supervisionDecider: Supervision.Decider = {
 case _ => Supervision.Resume
}

val commiter = committerSettings.getOrElse(CommitterSettings(actorSystem))
val supervisionStrategy = ActorAttributes.supervisionStrategy(supervisionDecider)
Consumer
 .committableSource(consumerSettings, Subscriptions.topics(topicsName))
 .mapAsync(parallelism) {
  msg =>
   f(msg.record.key(), msg.record.value())
    .map(_ => msg.committableOffset)
    .recoverWith {
     case _ => Future.successful(msg.committableOffset)
    }
 }
 .toMat(Committer.sink(commiter).withAttributes(supervisionStrategy))(DrainingControl.apply)
 .withAttributes(supervisionStrategy)

代碼中的庫版本

"com.typesafe.akka" %% "akka-http"            % "10.1.11",
 "com.typesafe.akka" %% "akka-stream-kafka" % "2.0.3",
 "com.typesafe.akka" %% "akka-stream" % "2.5.30"

觀察如下,

  1. 假設在 1 小時的連續間隔內,只有一些消費者
    正在以預期的速度積極消耗延遲和處理。
  2. 在接下來的 1 小時內,其他一些消費者變得活躍起來
    從其分區消耗,然后停止處理。
  3. 正如從 offsetLag Graph 觀察到的那樣,所有滯后都在一次拍攝中被清除。

我們希望所有消費者並行運行並實時處理消息。 3 天的處理延遲導致我們嚴重停機。 我嘗試按照給定的鏈接,但我們已經在固定版本https://github.com/akka/alpakka-kafka/issues/549

任何人都可以幫助我們在消費者配置或其他一些問題方面所缺少的東西。

每個主題每個分區的偏移滯后圖

在我看來,該滯后圖表明您的整個系統無法處理所有負載,而且幾乎看起來一次只有一個分區實際上在取得進展。

這種現象向我表明,在f中進行的處理最終取決於可以清除某些隊列的速率,並且mapAsync階段中的並行度太高,有效地使分區相互競爭。 由於Kafka消費者批量記錄(默認為500條,假設消費者的滯后超過500條記錄)如果並行度高於此值,所有這些記錄基本上與塊同時進入隊列。 看起來mapAsync的並行mapAsync是1500; 考慮到 Kafka 默認 500 批量大小的明顯使用,這似乎太高了:沒有理由讓它大於 Kafka 批量大小,如果您想要分區之間更均勻的消耗率,它應該少很多比那個批量大小。

如果沒有關於f發生的事情的詳細信息,很難說該隊列是什么以及應該減少多少並行度。 但我可以分享一些一般准則:

  • 如果工作受 CPU 限制(這表明您的消費者的 CPU 利用率非常高),則您有 7 個消費者,每個消費者有 4 個 vCPU。 您一次不能物理處理超過 28 (7 x 4) 條記錄,因此 mapAsync 中的並行度不應超過 1; 或者,您需要更多和/或更大的實例
  • 如果工作受 I/O 限制或以其他方式阻塞,我會注意工作是在哪個線程池/執行上下文/Akka 調度程序上完成的。 所有這些通常只會產生有限數量的線程,並在所有線程都忙時維護一個工作隊列; 該工作隊列很可能是感興趣的隊列。 擴展該池中的線程數(或者如果使用默認執行上下文或默認 Akka 調度程序,將該工作負載移至適當大小的池)將減少隊列壓力
  • 由於您包含akka-http ,因此f的消息處理可能涉及向某些其他服務發送 HTTP 請求。 在這種情況下,重要的是要記住 Akka HTTP 為每個目標主機維護一個隊列; 目標端也可能有一個隊列來控制那里的吞吐量。 這是第二種(I/O 綁定)情況的特殊情況。

I/O 綁定/阻塞情況將通過實例上的 CPU 利用率非常低來證明。 如果您按目標主機填充隊列,您將看到有關“超出配置的最大打開請求值”的日志消息。

另一個值得注意的是,由於Kafka消費者天生就是阻塞的,Alpakka Kafka消費者actor在自己的調度器中運行,其大小默認為16,這意味着每個主機最多只能有16個消費者或生產者同時工作. akka.kafka.default-dispatcher.thread-pool-executor.fixed-pool-size設置為至少您的應用程序啟動的消費者數量(每 7 個主題配置 6 個消費者中的 42 個)可能是一個好主意。 Alpakka Kafka 調度程序中的線程飢餓會導致消費者重新平衡,這將破壞消費。

在不進行任何其他更改的情況下,我建議,為了跨分區更均勻的消耗率,設置

akka.kafka.default-dispatcher.thread-pool-executor.fixed-pool-size = 42
consumer.parallelism = 50

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM