![](/img/trans.png)
[英]Multithreaded Kafka Consumer not processing all the partitions in parallel
[英]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 映射。
這是消費者設置:
因此,我們總共啟動了 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"
觀察如下,
我們希望所有消費者並行運行並實時處理消息。 3 天的處理延遲導致我們嚴重停機。 我嘗試按照給定的鏈接,但我們已經在固定版本https://github.com/akka/alpakka-kafka/issues/549
任何人都可以幫助我們在消費者配置或其他一些問題方面所缺少的東西。
在我看來,該滯后圖表明您的整個系統無法處理所有負載,而且幾乎看起來一次只有一個分區實際上在取得進展。
這種現象向我表明,在f
中進行的處理最終取決於可以清除某些隊列的速率,並且mapAsync
階段中的並行度太高,有效地使分區相互競爭。 由於Kafka消費者批量記錄(默認為500條,假設消費者的滯后超過500條記錄)如果並行度高於此值,所有這些記錄基本上與塊同時進入隊列。 看起來mapAsync
的並行mapAsync
是1500; 考慮到 Kafka 默認 500 批量大小的明顯使用,這似乎太高了:沒有理由讓它大於 Kafka 批量大小,如果您想要分區之間更均勻的消耗率,它應該少很多比那個批量大小。
如果沒有關於f
發生的事情的詳細信息,很難說該隊列是什么以及應該減少多少並行度。 但我可以分享一些一般准則:
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.