【カフカ】超詳しく紹介

コンセプト

Kafka は、一連の仕様であるメッセージング エンジン (メッセージング システム) です。企業はこの仕様セットを使用して、異なるシステム間で意味的に正確なメッセージを転送し、疎結合の非同期データ転送を実現します。

達成された目標は、システム A がメッセージ エンジンにメッセージを送信し、システム B が A によって送信されたメッセージをメッセージ エンジンから読み取ることです。

Kafka はデータをバイナリで保存します。ポイントツーポイント モデルとパブリッシュ/サブスクライブ モデルの両方をサポートします。

  • ポイントツーポイント モデル: メッセージ キュー モデルとも呼ばれます。上記の「プライベート バージョン」の定義を例にとると、システム A が送信したメッセージはシステム B のみが受信でき、他のシステムは A が送信したメッセージを読み取ることができません。電話によるカスタマー サービスなどの日常生活の例は、このモデルに属します。同じ顧客からの着信には 1 人のカスタマー サービス スタッフのみが対応でき、2 人目のカスタマー サービス スタッフが顧客に対応することはできません。
  • パブリッシュ/サブスクライブ モデル: 上記とは異なり、トピック (Topic) の概念があり、同様の論理セマンティクスを持つメッセージ コンテナーとして理解できます。このモデルにも送信者と受信者がありますが、方法が異なります。送信者はパブリッシャーとも呼ばれ、受信者はサブスクライバーとも呼ばれます。ポイントツーポイント モデルとは異なり、このモデルでは、同じトピックにメッセージを送信する複数のパブリッシャーが存在する場合があり、複数のサブスクライバーが存在する場合もあり、その全員が同じトピックに関するメッセージを受信できます。日常生活における新聞の購読は、典型的な発行/購読モデルです。

メッセージ エンジンの役割:

  • ピークシェービングとバレーフィリング: アップストリームとダウンストリームの瞬間的なバーストトラフィックをバッファリングして、よりスムーズにします。特に、強力な送信機能を持つ上流システムの場合、メッセージ エンジンの保護がないと、「脆弱な」下流システムが直接圧倒され、フルリンク サービスの「雪崩」が発生する可能性があります。ただし、メッセージ エンジンが存在すると、上流のトラフィックの影響に効果的に抵抗し、上流の「山」を「谷」に本当に埋めて、トラフィック ショックを回避できます。
  • また、送信側と受信側の間の疎結合により、アプリケーション開発がある程度簡素化され、システム間の不必要な対話が削減されます。

名詞用語は次のとおりです。

  • メッセージ: 記録します。Kafka はメッセージ エンジンであり、ここでのメッセージは Kafka によって処理されるメイン オブジェクトを指します。
  • 件名: トピック。トピックはメッセージを運ぶための論理的なコンテナであり、実際の使用では特定のサービスを区別するためによく使用されます。
  • パーティション:パーティション。順序付けられた不変のメッセージのシーケンス。各トピックの下に複数のパーティションが存在する場合があります。
  • メッセージ ディスプレイスメント: オフセット。パーティション内の各メッセージの位置情報を示します。単調増加する定数値です。
  • レプリカ:レプリカ。Kafka の同じメッセージは、データの冗長性を提供するために複数の場所にコピーできます。これらの場所はレプリカと呼ばれます。ダンジョンもリーダーダンジョンとフォロワーダンジョンに分かれており、それぞれ役割分担が異なります。コピーはパーティション レベルです。つまり、高可用性を実現するために各パーティションを複数のコピーで構成できます。
  • プロデューサー:プロデューサー。新しいメッセージをトピックに発行するアプリケーション。
  • 消費者:消費者。トピックからの新しいメッセージをサブスクライブするアプリケーション。
  • コンシューマ ディスプレイスメント: コンシューマ オフセット。消費者の消費の進捗を表すために、各消費者には独自の消費者変位があります。
  • 消費者グループ: 消費者グループ。高いスループットを実現するために複数のパーティションを同時に使用する、複数のコンシューマ インスタンスのグループ。
  • リバランス: リバランス。コンシューマ・グループ内のコンシューマ・インスタンスがハングアップすると、他のコンシューマ・インスタンスがトピック・パーティションへのサブスクライブ・プロセスを自動的に再割り当てします。リバランスは、Kafka コンシューマーが高可用性を実現するための重要な手段です。

ここに画像の説明を挿入

Kafka のコピーが外部サービスを提供できないのはなぜですか?

  • フォロワー コピーが外部読み取りサービス (マスター、書き込み、スレーブ読み取り) の提供を許可されている場合、まずデータの整合性の問題が発生します。マスター ノードからスレーブ ノードへのメッセージの同期に時間がかかり、ノード間でデータの不整合が発生する可能性があります。マスターノードとスレーブノード。マスター書き込みとスレーブ読み取りは、リーダーノードへの負荷を軽減し、フォロワーノードへの読み取りリクエストの負荷を分散することに他なりませんが、Kafka のパーティションが各ブローカーに比較的均等に分散されていれば、負荷分散の効果も得られます。マスター書き込みを意図的に実装する必要がない 読み取りによるコード実装の複雑さの増加

消費者団体:

  • コンシューマ グループでは、パーティションは 1 つのコンシューマによってのみ使用できますが、コンシューマには複数のパーティションを割り当てることができるため、ディスプレイスメントの送信時に複数のパーティションのディスプレイスメントも送信できます。
  • コンシューマ グループ内のコンシューマの数 > パーティションの数の場合、コンシューマはどのパーティションにも割り当てることができず、アイドル状態になります。

プロデューサー:

  • プロデューサが送信するターゲット パーティションを指定した場合、メッセージは自然にそのパーティションに送信されます。それ以外の場合は、プロデューサ側のパラメータ Partitioner.class で指定されたパーティション戦略に従って決定されます。partitioner.class を指定していない場合は、メッセージはそのパーティションに送信されます。の場合、デフォルトのルールは次のとおりです: メッセージにキーがあるかどうかを参照し、ある場合はキーの murmur2 ハッシュ値を計算します% トピック パーティション番号; キーがない場合は、ポーリング方法に従ってパーティションを決定します。

モニター:

  • JMXTrans + InfluxDB + Grafana (推奨)
  • カフカマネージャー
  • カフカの鷲

導入計画

ディスク

保持されるメッセージの数に基づいた推定ディスク使用量:

  • 新しいメッセージの数
  • メッセージの保存期間
  • 平均メッセージサイズ
  • バックアップの数
  • 圧縮を有効にするかどうか
假设你所在公司有个业务每天需要向 Kafka 集群发送 1 亿条消息,每条消息保存两份以防止数据丢失,另外消息默认保存两周时间。现在假设消息的平均大小是 1KB,那么你能说出你的 Kafka 集群需要为这个业务预留多少磁盘空间吗?

我们来计算一下:每天 1 亿条 1KB 大小的消息,保存两份且留存两周的时间,那么总的空间大小就等于 1 亿 * 1KB * 2 / 1000 / 1000 = 200GB。一般情况下 Kafka 集群除了消息数据还有其他类型的数据,比如索引数据等,故我们再为这些数据预留出 10% 的磁盘空间,因此总的存储容量就是 220GB。既然要保存两周,那么整体容量即为 220GB * 14,大约 3TB 左右。Kafka 支持数据的压缩,假设压缩比是 0.75,那么最后你需要规划的存储空间就是 0.75 * 3 = 2.25TB。

通信網

QPS と帯域幅に基づいてサーバーの数を推定します: (注: 業界の帯域幅リソースは通常、MBps ではなく Mbps で測定されます)

假设你公司的机房环境是千兆网络,即 1Gbps,现在你有个业务,其业务目标或 SLA 是在 1 小时内处理 1TB 的业务数据。那么问题来了,你到底需要多少台 Kafka 服务器来完成这个业务呢?

让我们来计算一下,由于带宽是 1Gbps,即每秒处理 1Gb 的数据,假设每台 Kafka 服务器都是安装在专属的机器上,也就是说每台 Kafka 机器上没有混布其他服务,毕竟真实环境中不建议这么做。通常情况下你只能假设 Kafka 会用到 70% 的带宽资源,因为总要为其他应用或进程留一些资源。

根据实际使用经验,超过 70% 的阈值就有网络丢包的可能性了,故 70% 的设定是一个比较合理的值,也就是说单台 Kafka 服务器最多也就能使用大约 700Mb 的带宽资源。

稍等,这只是它能使用的最大带宽资源,你不能让 Kafka 服务器常规性使用这么多资源,故通常要再额外预留出 2/3 的资源,即单台服务器使用带宽 700Mb / 3 ≈ 240Mbps。需要提示的是,这里的 2/3 其实是相当保守的,你可以结合你自己机器的使用情况酌情减少此值。

好了,有了 240Mbps,我们就可以计算 1 小时内处理 1TB 数据所需的服务器数量了。根据这个目标,我们每秒需要处理 2336Mb 的数据,除以 240,约等于 10 台服务器。如果消息还需要额外复制两份,那么总的服务器台数还要乘以 3,即 30 台。

CPU

通常、Kafka は CPU をあまり使用しないため、この点に関するベスト プラクティスはありません。ただし、場合によっては、Kafka ブローカーが大量の CPU を消費します。

  1. サーバーとクライアントは異なる圧縮アルゴリズムを使用します。
  2. サーバーとクライアントのバージョン間の不一致により、メッセージ形式の変換が発生します。3
  3. ブローカー側の解凍チェック

ただし、帯域幅リソースと比較すると、通常、CPU がボトルネックになることはありません。

パーティションの数

インターネット上にはパーティショニングに関する提案がいくつかありますが、この粗粒な方法は非常に優れており、試してみる価値があると思います。

  1. まず最初に、ビジネスの SLA を決定する必要があります。たとえば、T1 であると仮定して、プロデューサー TPS を 100,000 メッセージ/秒にしたいとします。
  2. T2 を想定して、実際の環境に単一パーティションのトピックを作成して TPS をテストします。
  3. 必要なパーティションの数は、T1 / T2 とほぼ同じになります。

注文

2.2 より上では –bootstrap-server を使用し、2.2 より下では –zookeeper を使用してください

バージョンを見る

cd kafka/libs
其中有kafka_2.12-2.8.0.jar,则版本为2.8.0

Kafka と Zookeeper の IP/ポートを見つける

root@master~# kubectl get svc -n kafka -o wide
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE   SELECTOR
bootstrap   ClusterIP   10.109.83.55    <none>        9092/TCP            15d   app=kafka
broker      ClusterIP   None            <none>        9092/TCP            15d   app=kafka
outside-0   NodePort    10.108.34.0     <none>        32400:32400/TCP     15d   app=kafka,kafka-broker-id=0
outside-1   NodePort    10.104.65.215   <none>        32401:32401/TCP     15d   app=kafka,kafka-broker-id=1
outside-2   NodePort    10.99.118.241   <none>        32402:32402/TCP     15d   app=kafka,kafka-broker-id=2
pzoo        ClusterIP   None            <none>        2888/TCP,3888/TCP   15d   app=zookeeper,storage=persistent
zoo         ClusterIP   None            <none>        2888/TCP,3888/TCP   15d   app=zookeeper,storage=persistent-regional
zookeeper   ClusterIP   10.103.22.130   <none>        2181/TCP            15d   app=zookeeper

トピック

  • クライアントがプロデューサーを作成するために特定のトピックにエラーを報告した場合kafka: client has run out of available brokers to talk to (Is your cluster reachable?)、トピックを手動で削除してからトピックを手動で再構築できます。

トピックを作成する

./kafka-topics.sh --zookeeper 10.103.22.130:2181 --create --topic ttt1 --partitions 1 --replication-factor 1

チェック

トピックリストを取得する

./kafka-topics.sh  --list --bootstrap-server 192.168.2.165:9092
./kafka-topics.sh --zookeeper 10.103.22.130:2181/kafka --list

# out
__consumer_offsets
topic1
kafka-image-topic2

トピックの詳細を取得する

./kafka-topics.sh --zookeeper 10.103.22.130:2181 --describe --topic topic-a

トピックを変更する

パーティションレベルパラメータの変更(パーティションの追加など)

./kafka-topics.sh --zookeeper 10.103.22.130:2181 --alter --topic ttt1 --partitions 2

## out
WARNING: If partitions are increased for a topic that has a key, the partition logic or ordering of the messages will be affected
Adding partitions succeeded

トピックを削除する

  • 前提条件はkafka配置文件server.propertiesindelete.topic.enableを true に設定することです
./kafka-topics.sh --zookeeper 192.168.2.165:2181 --delete  --topic <topic_name>

メッセージサイズ制限を設定する

  • トピックレベルの静的パラメータには –zookeeper を使用し、動的パラメータには –bootstrap-server を使用します
./kafka-configs.sh --zookeeper 10.103.22.130:2181 --entity-type topics --entity-name ttt1 --alter --add-config max.message.bytes=10485760

## out
Completed Updating config for entity: topic 'ttt1'.

製造

生産を見る

./kafka-console-producer.sh --broker-list 192.168.2.158:9092 --topic topic-a

制作ニュース

./kafka-console-producer.sh --topic topic-a --bootstrap-server broker:9092
> 进站去重车数据如下:
> {
    
    "Ts":1677507305663,"Data":"99990000","Info":1080}}

参考

消費を見る

サーバ

# 输入
   ./kafka-console-consumer.sh --bootstrap-server 192.168.2.111:9092   --topic topic-a
或 ./kafka-console-consumer.sh --zookeeper        127.0.0.1:2181/kafka --topic topic-a


./kafka-console-consumer.sh --bootstrap-server 192.168.2.142:32400   --topic ttt

```bash
zk启动 brew services start zookeeper或zkServer start
/usr/local/Cellar/kafka/2.4.0/libexec/bin

ビューのオフセット

時間に応じてオフセットを見つけたい場合は、次の方法があります。

  • 指定された時刻で*.indexおよび.log、ファイル名はオフセットされます。
-rw-r--r--  1 root root       43 Nov 28  2021 partition.metadata
-rw-r--r--  1 root root       10 Nov  8 12:30 00000000000000380329.snapshot
-rw-r--r--  1 root root 10485756 Nov  8 12:31 00000000000000380329.timeindex
-rw-r--r--  1 root root 10485760 Nov  8 12:31 00000000000000380329.index
drwxr-xr-x  2 root root     4096 Nov  8 12:31 ./
-rw-r--r--  1 root root     3622 Nov  8 12:33 00000000000000380329.log
drwxr-xr-x 77 root root     4096 Nov  8 12:34 ../

バックログを表示する

サーバ

# 输入
cd ~/deep/kafka/kafka/bin
watch -n 1 ./kafka-consumer-groups.sh --bootstrap-server 192.168.2.111:9092 --describe --group topic-a

# 输出:其中lag有值表示积压。
Note: This will only show information about consumers that use the Java consumer API (non-ZooKeeper-based consumers).
TOPIC                          PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG        CONSUMER-ID                                       HOST                           CLIENT-ID
topic-a                 0          159955          159955          0          UBUNTU.local-61e8b3d0-1456-49ce-8656-fe18cab4026a    

クライアントが通信できるブローカーを使い果たしました (クラスターに到達可能ですか?)。

  • Kafka は、Zookeeper に基づいて一貫性検証を実行します。トピックはブローカーに対応する必要があります。インポーターが[ERROR] kafka %v create producer failed:client has run out of available brokers to talk to (Is your cluster reachable?)エラー Kafka に問題がある可能性があります。オフセットが不整合で乱れが発生している可能性があります。詳細暴力的な方法は、動物園の飼育員と Kafka のログを削除することです

原理

  • 生産者 = "複数のパーティションに分割 (たとえば、p1、p2、p3...p10 に分割) = "各コンシューマは異なるパーティションから読み取ります (コンシューマ 1 が p1 を読み取る場合、コンシューマ 2 は p1 を読み取ることはできません)。

UIツール

Kafka Offset Explorer 支持Mac、Win、Linux

go sarama ライブラリが使用するのは

Shopify/サラマライブラリ

消費者

NewConsumerGroup() を使用して作成することも、最初に NewClient() を使用してから NewConsumerGroupFromClient() を使用して作成することもできます。

Setup()、ConsumeClaim()、CleanUp() の 3 つのコールバック関数を実装する必要があります。sarama ライブラリは上記の関数をスケジュールします。

オフセットをリセットする必要がある場合は、Setup() の ResetOffset() を通じて実装できます。

完全なコードは次のとおりです。

package kafka

import (
	"context"
	"github.com/Shopify/sarama"
	log "github.com/siruspen/logrus"
	"lonk/configs"
	"strconv"
	"time"
)

func StartConsumerGroup(ctx context.Context, conf *configs.KafkaInputConfig, msgHandler KafkaMsgHandler) {
    
    
	cli, err := newConsumerGroup(conf) // 新建一个 client 实例
	if err != nil {
    
    
		log.Fatalf("[newConsumerGroup] conf: %v, err: %v", conf, err)
	}
	k := kafkaConsumerGroup{
    
    
		msgHandler:                msgHandler,
		ready:                     make(chan bool, 0), // 标识 consumer 是否 ready
		partitionInitialOffsetMap: conf.PartitionInitialOffsetMap,
	}
	go func() {
    
    
		defer cli.Close()
		for {
    
    
			// Consume().newSession().newConsumerGroupSession() 先调用 Setup(); 再开多个协程(每个协程都用for循环持续调用consume().ConsumeClaim()来处理消息); Consume() 内部的 <-sess.ctx.Done() 会阻塞
			if err := cli.Consume(ctx, []string{
    
    conf.Topic}, &k); err != nil {
    
    
				log.Errorln("Error from consumer", err)
			}
			if ctx.Err() != nil {
    
     // 若 ctx.cancel() 而会引发 cli.Consume() 内部对 ctx.Done() 的监听,从而结束 cli.Consume() 的阻塞, 并
				return
			}
			k.ready = make(chan bool, 0) // 当 rebalanced 时 cli.Consume() 会退出且 ctx.Err() == nil, 则重置此 channel, 继续在下一轮 for 循环调用 Consume()
		}
	}()
	<-k.ready // 直到 close(consumer.ready) 解除阻塞
	log.Infoln("Sarama consumer up and running!...")
}

func newConsumerGroup(conf *configs.KafkaInputConfig) (sarama.ConsumerGroup, error) {
    
    
	sConf := sarama.NewConfig()
	sConf.Version = sarama.V2_8_0_0
	sConf.Consumer.Offsets.Initial = sarama.OffsetOldest
	sConf.Consumer.Offsets.Retention = 7 * 24 * time.Hour
	cli, err := sarama.NewClient(conf.Brokers, sConf)
	if err != nil {
    
    
		log.Fatalf("[NewClient] conf: %v, err: %v", sConf, err)
	}
	consumerGroup, err := sarama.NewConsumerGroupFromClient(conf.Group, cli)
	if err != nil {
    
    
		log.Fatalf("[NewConsumerGroupFromClient] conf: %v, err: %v", sConf, err)
	}
	return consumerGroup, nil
}

// Consumer represents a Sarama consumer group consumer
type kafkaConsumerGroup struct {
    
    
	msgHandler                func(message *sarama.ConsumerMessage)
	ready                     chan bool
	partitionInitialOffsetMap map[string]int64
}

// Setup 回调函数 is run at the beginning of a new session, before ConsumeClaim
func (k *kafkaConsumerGroup) Setup(session sarama.ConsumerGroupSession) error {
    
    
	for topic, partitions := range session.Claims() {
    
    
		for _, partition := range partitions {
    
    
			initialOffset, ok := k.partitionInitialOffsetMap[strconv.Itoa(int(partition))]
			if !ok {
    
    
				log.Fatalf("invalid topic:%v, partition: %v", topic, partition)
			}
			log.Infof("Sarama Consumer is resetting offset to %v:%v:%v", topic, partition, initialOffset)
			session.ResetOffset(topic, partition, initialOffset, "")
		}
	}
	close(k.ready) // 启动后此处会标记 ready, 使 StartKafkaConsumer() 不再阻塞
	return nil
}

// Cleanup 回调函数 is run at the end of a session, once all ConsumeClaim goroutines have exited
func (k *kafkaConsumerGroup) Cleanup(sarama.ConsumerGroupSession) error {
    
    
	log.Infoln("Sarama Consumer is cleaning up!...")
	return nil
}

// ConsumeClaim 回调函数 must start a consumer loop of ConsumerGroupClaim's Messages().
func (k *kafkaConsumerGroup) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
    
    
	// NOTE: Do not move the code below to a goroutine. The `ConsumeClaim` itself is called within a goroutine, see: https://github.com/Shopify/sarama/blob/master/consumer_group.go#L27-L29
	for {
    
    
		select {
    
    
		case message := <-claim.Messages():
			k.msgHandler(message)
			session.MarkMessage(message, "")
		// Should return when `session.Context()` is done. If not, will raise `ErrRebalanceInProgress` or `read tcp <ip>:<port>: i/o timeout` when kafka rebalance. see: https://github.com/Shopify/sarama/issues/1192
		case <-session.Context().Done():
			return nil
		}
	}
}

参考: 公式 ConsumerGroup の例
参考: sarama コンシューマ グループの使用
参考: sarama パーティション コンシューマは時間に応じてオフセットを指定する

おすすめ

転載: blog.csdn.net/jiaoyangwm/article/details/126338452