カフカ -- Python

1. カフカとは

ここに画像の説明を挿入

  • プロデューサー: プロデューサー、つまりメッセージのプロデューサーは、メッセージの入り口です。
  • Consumer: Consumer、つまりメッセージのコンシューマは、メッセージの出口です。
  • ブローカー: 中間プロキシ、つまりブローカーはサーバーです。Kafka クラスター内の各ブローカーには、図の Broker-0、Broker-1 などの一意の番号が付いています。
  • トピック (topic): メッセージの分類として理解でき、Kafka のデータはトピックに格納されます。各ブローカー上に複数のトピックを作成でき、コンピューターのフォルダーと比較することもできます。
  • イベント (event): レコード、メッセージ (Message) とも呼ばれ、ファイルと比較できます。イベントの例には、支払いトランザクション、携帯電話からの位置情報の更新、発送注文、IoT デバイスや医療機器からのセンサー測定などが含まれます。これらのイベントはトピックに編成され、保存されます。
  • パーティション: スケーラビリティを実現するために、非常に大きなトピックを複数のブローカー (つまりサーバー) に分散でき、トピックを複数のパーティションに分割でき、各パーティションは順序付きキューになります。パーティション内の各メッセージには、順序付けされた ID (オフセット) が割り当てられます。Kafka は、パーティションの順序でコンシューマにメッセージを送信することのみを保証し、(複数のパーティション間で) 全体としてのトピックの順序を保証しません。
  • Consumer Group (CG): シリコンバレーでも話題になりましたが、トピックはパーティションに分かれており、消費者もグループ化できるためです。各コンシューマはグループを形成でき、同じコンシューマ グループのコンシューマは同じトピックの異なるパーティションからデータを消費できます。これにより、Kafka のスループットも向上します。
  • オフセット: Kafka のストレージ ファイルは offset.kafka に従って名前が付けられます。オフセットを名前として使用する利点は、見つけやすいことです。たとえば、2049 の場所を見つけたい場合は、ファイル 2048.kafka を見つけるだけで済みます。もちろん、最初のオフセットは 00000000000.kafka です。

具体的な処理についてはリンク「Kafkaの基本原理を詳しく解説」を参照してください。

たくさんの情報を読んだ後、私が理解したことは次のとおりです。

Kafka には多数のブローカーがあり (ボーカーはクラウド サーバー、つまり大きなコンピューターに相当するため)、各ブローカーには多くのトピック (多数のフォルダーを持つコンピューターに相当) があり、トピックが大きすぎる場合があります。ブローカーはトピックを保存できないため、トピックA-パーティション0、トピックA-パーティション1に分割され、ブローカー1とブローカー2に保存されます(分散ストレージに相当し、大きなフォルダーを多数の小さなフォルダーに分割します。もちろん、リスクを防ぐために、さらに多くのコピー)、各パーティションにはイベント (フォルダーに保存されているドキュメントに相当) が保存されます。



2. Docker に Kafka をデプロイする

  1. 飼育員とカフカの鏡像を引いてください

    docker pull wurstmeister/zookeeper
    
    docker pull wurstmeister/kafka
    
  2. コンテナを作成して実行します。

    実行順序の作成には注意してください。最初に Zookeeper、次に Kafka を指定する必要があります。そうしないと、エラーが報告されます。

    2.1 Zookeeper の作成と実行

    docker run --name zookeeper \
    -d -t wurstmeister/zookeeper \
    -p 2181:2181 
    

    パラメータの説明 (詳細については、「docker の例、新人チュートリアル」を参照してください):

    実行後、コンテナIDが返されます。

    2.2 Kafka の作成と実行

    	docker run --name kafka \
    	-t wurstmeister/kafka\ 
    	-p 9092:9092 \
    	--link zookeeper:zk\
    	-e HOST_IP=localhost \
    	-e KAFKA_BROKER_ID=1 \
    	-e KAFKA_ZOOKEEPER_CONNECT=zk:2181 \
    	-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
    	-e KAFKA_LISTENERS=PLAINTEXT://localhost:9092 
    

    パラメータの説明:

    • –name kafka: このコンテナには kafka というエイリアスが付けられます。
    • -d: バックグラウンドで実行します。
    • -t: 疑似端末または新しいコンテナ内の端末を指定します。
    • wurstmeister/kafka: 実行するイメージを指定します。Docker はまずローカル ホストにイメージが存在するかどうかを確認します。存在しない場合、Docker はイメージ ウェアハウス Docker Hub からパブリック イメージをダウンロードします。
    • -p ポート番号 (ホストにマッピングされたポート: kafka ポート番号)。一部のネットワーク アプリケーションはコンテナ内で実行できます。これらのアプリケーションへの外部アクセスを許可するには、-P または -p パラメータを使用してポート マッピングを指定できます (詳細については、「Docker コンテナ相互接続のポート マッピング、初心者チュートリアル」を参照してください)たとえば、ここでは、ubuntu 仮想マシンに docker をデプロイし、ubuntu 仮想マシンの localhost:9092 にアクセスして、docker 内の kafka にアクセスします。
      --link=[]: 別のコンテナへのリンクを追加します。ここでは、Zookeeper コンテナにリンクすると同時にエイリアス zk を取得することを意味します。
    • -e : 環境変数を設定します:
      HOST_IP : ホスト マシンの IP;
      KAFKA_BROKER_ID : Kafka クラスターでは、各 Kafka がそれ自体を区別するための BROKER_ID を持っているため、この ID はクラスターの一意の識別子です;
      KAFKA_ZOOKEEPER_CONNECT==<ここで置き換えますあなたの Zookeeper アドレスとポート> : kafka を管理するための Zookeeper のパスを設定します;
      KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://<ここをあなたの Kafka アドレスとポートに置き換えます> : Kafka はクライアントがサービス アドレスを使用できるように Zookeeper に公開します;
      KAFKA_LISTENERS : 設定しますkafka のリスニング ポート。これにより、PLAINTEXT リスナーの使用が可能になります。インターネット上のいくつかのチュートリアルは意味-e KAFKA_LISTENERS=PLAINTEXT://localhost:9092変更されました。-e ALLOW_PLAINTEXT_LISTENER=yesインターネット上のいくつかのブログでは、0.0.0.0 と書かれています。これは、すべてのポートの情報をリッスンすることを意味します。詳細は「0.0.0.0、localhost、127.0.0.0の違い」を参照してください。

    2.3 次のコマンドを使用して、コンテナが正常に作成されたかどうかを確認します。

    docker ps -a
    

    以下の図からわかるように、それらはすべて稼働状態になっており、作成と操作が成功したことを意味します。

    ここに画像の説明を挿入

  3. メッセージのテスト送信

    3.1 コンテナ内部へのアクセス

    docker exec -it kafka /bin/bash  # 注意,这里kafka是之前创建的容器名。
    

    パラメータの説明:

    • -i: コンテナー内で標準入力 (STDIN) を操作できるようにします。
    • -t: 疑似端末または新しいコンテナ内の端末を指定します。
    • kafka: 以前にコンテナを実行したときに作成されたコンテナ名は次のとおりです。
    • /bin/bash: 平たく言えば、#!/bin/bash: は、このスクリプトが解釈と実行に /bin/bash を使用することを意味します。このうち #! は特殊な記号で、その後にこのスクリプトを解釈するためのシェル パスが続きます。Bash はシェルの 1 種類にすぎません。sh、csh、ksh、tcsh など、他にも多くのシェルがあります。シェル スクリプトの最初の文は通常 #!/bin/bash です。多くの場合、この行が設定されていない場合、プログラムが実行するためにどのシェルを使用する必要があるかをシステムが判断できないため、プログラムが実行されないことがあります。

    以下の図は、コンテナーに正常に入力されたことを示しています。@ に続く「63d8a927b72c」がコンテナー ID です。

    ここに画像の説明を挿入
    3.2 コンテナーの下の /opt/kafka/bin ディレクトリに切り替えます。

    すべての公式 .sh ファイルはこのディレクトリに保存されます。これらの .sh ファイルはすべてスクリプト ファイルです。異なるスクリプト ファイルは、異なるコマンド ライン コマンドを組み合わせます。1 つのスクリプト ファイルは 1 つの機能を実装します。ユーザーは、この .sh ファイルとパラメータを実行するだけで、多くのコマンドを実装でき、非常に便利です。

    たとえば、トピック、プロデューサー、およびコンシューマーを作成するには、対応する .sh ファイルを実行する必要があります。このディレクトリに切り替える利点は、以前のパスを省略できることです。

    以下の図に従って、順番に切り替えることができます (kafka コンテナーのデフォルト フォルダーにどのようなファイルがあるかを理解するためにそうすることをお勧めします)。もちろん、ワンステップで実行することもできます。

    cd /opt/kafka/bin
    

    ここに画像の説明を挿入
    3.3 テーマを作成する

    kafka-topics.sh --create \
    --zookeeper zookeeper:2181 \
    --replication-factor 1 \
    --partitions 1 \
    --topic mykafka
    

    パラメータの説明 (特定のパラメータについては、「kafka-topics.sh スクリプトの詳細な説明」を参照してください):

    • 作成: トピックを作成します。
    • Zukerrper: 接続されている zk のアドレスを指定します。
    • replication-factor: トピック作成時のレプリカ数を指定します。
    • パーティション: トピックの作成時またはパーティションの追加時に指定されたパーティションの数
    • トピック: トピック名を指定します

    次のコマンドを使用して、作成したトピックを表示します。次の図は、以前に作成した Mykafka トピックを示しています。

    kafka-topics.sh --zookeeper zookeeper:2181 --list

    ここに画像の説明を挿入

    3.4 メッセージ プロデューサーを実行し、作成したばかりのトピックとしてトピックを指定します。

    kafka-console-producer.sh --broker-list localhost:9092 \
    --topic mykafka
    

    パラメータの説明 (特定のパラメータについては、「kafka-console-Producer.sh スクリプトの詳細な説明」を参照してください):

    • Broker-list: 接続先のサーバーは、bootstrap-server で置き換えることもできます。
    • topic: メッセージを受信するトピックの名前。

    3.5 新しいターミナルを開き、同じディレクトリに入り、コンシューマを実行して、同じトピックを指定します。

    	docker exec -it kafka /bin/bash
    	cd opt/kafka/bin
    	kafka-console-consumer.sh --bootstrap-server localhost:9092 \
    	--topic mykafka \
    	--from-beginning
    

    次の図は、プロデューサーがメッセージを送信し、コンシューマーがそのメッセージを消費することを示しています。

    ここに画像の説明を挿入

    なぜ消費者は以前に送信されたメッセージを受信するのでしょうか?という疑問を持つ人がいるはずです。このパラメーターは Consumer の構成時に追加されるため--from-beginning、コンシューマーは最新のメッセージからではなく、存在する最も古いメッセージから消費を開始することを意味します。以下の図は、パラメータを削除した--from-beginning同じトピックのコンシューマを示しており、最新のニュースからコンシューマが開始されていることがわかります。

    ここに画像の説明を挿入



3. Kafka コンテナーに Python をデプロイし、プロデューサー/コンシューマーの単純なコードを実行します。

前提:

Zookeeper コンテナと Kafka コンテナはデフォルトでデプロイされ、稼働状態になっています。起動状態でない場合は、必ず最初に Zookeeper を再起動してから、kafka を再起動してください。

すべてのコンテナとステータスを表示するには、sudo docker ps -a を使用します。パラメータ -a がない場合は、実行中のコンテナのみを表示できます。以前に仮想マシンをシャットダウンしたので、以前にデプロイされた 2 つのコンテナーを見ると、下の図が起動していないことがわかります。

ここに画像の説明を挿入
再起動後、以下の図に示すように、Zookeeper コンテナと Kafka コンテナがデプロイされ、エイリアスが Zookeeper と Kafka になっていることがわかります。

ここに画像の説明を挿入
Python環境のデプロイを始めましょう

  1. 高度なコンテナ内:

    docker exec -it kafka /bin/bash #(这里kafka为你的容器id或别名)
    

    コンテナーに正常に入ると、現在のユーザーが自動的に root に切り替わり、@ の後にコンテナー ID が続くことがわかります。

    ここに画像の説明を挿入

  2. apt コマンドを更新します。ステップ 3 を更新しないと、エラーが報告される可能性があります。

    apt update
    
  3. pipコマンドをインストールします。pip をインストールすると、python3 環境が自動的にインストールされます。

    apt install pip
    

    python コマンドが実行され、インストール環境が正常に構築されたことがわかります。次に exit() を入力して終了します。

    ここに画像の説明を挿入

  4. サードパーティのライブラリ kafka-python をインストールします。

    Python コードでは、kafka import KafkaConsumer から、KafkaProducer がこのライブラリから派生します。

    pip install kafka-python
    

    ここに画像の説明を挿入

  5. opt ディレクトリに切り替えて、Python ドキュメントを保存するフォルダーを作成します。ここでは python_kafka とします。

    ここに画像の説明を挿入

  6. python_kafka ディレクトリに切り替えたら、3 つのファイルの作成を開始します。config1.py はパラメータ初期化ファイル、consumer1.py はコンシューマ プロセス、production.py はプロデューサー プロセスです。3 つのファイルは「How to use Python to read and write Kafka?」から参照されています。

    具体的な手順は次のとおりです。

    6.1. config1.py

    python_kafka ディレクトリで vim コマンドを使用して、config1.py ファイルを作成および編集します。初めて vim をインストールするには、コマンド apt install vim を使用する必要があることに注意してください。

    	vim config1.py
    

    次のコードは、config1.py の具体的な内容です。Windows 上の vscode に記述し、直接コピーして貼り付けました。Windows 上でコピーされ、vim では挿入モードで Shift キーと右マウス ボタンを押したまま貼り付けます (詳細については、「 Linux で Windows と CentOS vim エディターの間でテキストをコピーして貼り付ける方法」を参照してください)

    # config1.py
    # 参数初始化
    SERVER = 'localhost:9092'
    TOPIC = 'mykafka_py_1'
    

    ここに画像の説明を挿入
    完了したら、忘れずに保存して終了してください。

  7. プロデューサー1.py

    プロデューサーを作成します。また、python_kafka ディレクトリで、コマンド vim を使用して、Producer1.py ファイルを作成および編集します。

    vim producer1.py
    

    次のコードは、Producer1.py の具体的な内容です。

    import json
    import time
    import datetime
    import config1  
    from kafka import KafkaProducer
    
    
    producer = KafkaProducer(bootstrap_servers=config1.SERVER,
                             value_serializer=lambda m: json.dumps(m).encode())
    
    for i in range(100):
        data = {
          
          'num': i, 'ts': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
        producer.send(config1.TOPIC, data)
        time.sleep(1)
    

    ここに画像の説明を挿入

  8. Consumer1.py

    消費者を作成します。また、python_kafka ディレクトリで、コマンド vim Consumer1.py を使用して、consumer1.py ファイルを作成および編集します。

    vim consumer1.py
    

    次のコードは、consumer1.py の具体的な内容です。

     import config1
    	from kafka import KafkaConsumer
    	consumer = KafkaConsumer(config1.TOPIC,
    	                         bootstrap_servers=config1.SERVER,
    	                         group_id='test',
    	                         auto_offset_reset='earliest')
    	for msg in consumer:
    	    print(msg.value)
    
  9. 1 人の生産者、1 人の消費者。やり取りされたメッセージが確認できます。

    ここに画像の説明を挿入

    注: 上記の単純なコードの特定の関数とパラメーターについては、以下の最新インターフェイスの kafka_interface.py ファイル内のコメントを参照してください。プロデューサーでメッセージを送信する 3 つの方法をまとめました。

4. インターフェイスの 2 番目のバージョン

貢献:
1. パート 3 の単純なコードを書き直し、それを 3 つのファイルに抽象化します;
2. kafka_interface.py はプロデューサー内の 3 つのメソッドを要約し、コードを再現します (詳細についてはそのメモを参照してください);
3. コンシューマー消費メソッド A についても同様です概要を作成しました、詳細は注意事項を参照してください;
4. テスト中に繰り返し消費されるバグが見つかりました。

参考:

Python Kafka サブスクリプションのプロデューサーの 3 つのモード
Kafka の使い方を学ぶ (11) Python プロデューサーとコンシューマー API の使用法「 Apache Kafka シリーズ」 Kafka プロデューサー (プロデューサー) がメッセージを送信するためのいくつかの方法と
プロデューサーのPython kafka操作例(kafka-python)Python kafkaメッセージキュー操作入門Python操作Kafka — kafka-pythonApache Kafkaの3つのコアPythonクライアントライブラリkafkaシリーズ5つ、kafkaよく使われるJava API




4.1、kafka_config.py

# -*- coding: utf-8 -*-
# kafka_config.py

SERVER = 'localhost:9092'

TOPIC = 'howtousekafka'

group_id = 'test'

auto_offset_reset = 'earliest'

4.2、kafka_interface.py

# -*- coding: utf-8 -*-
# kafka_interface.py
from kafka import KafkaConsumer, KafkaProducer
import logging
import traceback
import kafka_config
log = logging.getLogger(__name__)


class KafkaInterface:

    def __init__(self):
        self.producer = KafkaProducer(bootstrap_servers=kafka_config.SERVER)
        # self.consumer = KafkaConsumer(kafka_config.TOPIC,
        #                               bootstrap_servers=kafka_config.SERVER,
        #                               group_id=kafka_config.group_id,
        #                               auto_offset_reset=kafka_config.auto_offset_reset)
        '''
        因为消费者消费数据时可以才从subscribe方法订阅相应的主题或assign订阅相应的分区,
        故初始化时不必指定topic,故采用下面此种写法。
        再高级点的用法是在subscribe基础上,搭配poll函数。有时候,我们并不需要实时获取数据,
        因为这样可能会造成性能瓶颈,我们只需要定时去获取队列里的数据然后批量处理就可以,
        这种情况,我们可以选择主动拉取数据。
        '''
        self.consumer = KafkaConsumer(bootstrap_servers=kafka_config.SERVER,
                                      group_id=kafka_config.group_id,
                                      auto_offset_reset=kafka_config.auto_offset_reset)
    '''
    Kafka发送消息主要有三种方式:
        1.发送并忘记
        2.同步发送
        3.异步发送+回调函数

    下面以单节点的方式分别用三种方法发送消息
    '''

    def send_message_asyn_producer(self, message):
        """
        方式一:
            发送并忘记(把消息发送给服务器,不关心消息是否正常到达,对返回结果不做任何判断处理)
        说明:
            1、发送并忘记的方式本质上也是一种异步的方式,消息先存储在缓冲区中,达到设定条件后批量发送,
            只是它不会获取消息发送的返回结果,这种方式的吞吐量是最高的,但是无法保证消息的可靠性。
            2、由于测试数据太少,异步看着像同步。当发送例如1w条消息时,可以通过对比执行完成时间可以看出:
            异步发送时间是低于同步发送时间的。
        """
        while True:
            counter = 0  # 记录一共发了多少条消息
            try:
                for item in message.split(","):
                    counter += 1  # 发一条消息就增加一条
                    self.producer.send(kafka_config.TOPIC,
                                       item.encode('utf-8'))
                self.producer.flush()  # 批量提交
                print("您使用方法一简单异步发送消息,此次一共发送了{}条消息".format(counter))
                self.producer.close()
                break
            except Exception as e:
                log.error("Kafka asyn send fail, {}.".format(e))
                traceback.format_exc()

    def send_message_sync_producer(self, message):
        """
        方式二:
            同步发送数据(通过get方法等待Kafka的响应,判断消息是否发送成功)
        说明:
            1、以同步的方式发送消息时,一条一条的发送,对每条消息返回的结果判断,可以明确地知道每条消息的发送情况,
            但是由于同步的方式会阻塞,只有当消息通过future.get返回元数据时,才会继续下一条消息的发送。
            2、如果业务要求消息必须是按顺序发送的,那么可以使用同步的方式,并且只能在一个partation上,
            结合参数设置retries的值让发送失败时重试,因为在同一个分区上是FIFO,不同分区上不能保证顺序读取,
            故只有指定同一个分区才能保证顺序读取。
            3、send函数是有返回值的是RecordMetadata,也就是记录的元数据,包括主题、分区、偏移量
        """
        while True:
            counter = 0  # 记录一共发了多少条消息
            try:
                for item in message.split(","):
                    counter += 1  # 发一条消息就增加一条
                    # 同步确认消费,即监控是否发送成功。future.get函数等待单条消息发送完成或超时,或用time.sleep代替
                    future = self.producer.send(
                        kafka_config.TOPIC, item.encode('utf-8'))
                    # time.sleep(10) # 等价于future.get(timeout=10)
                    # future.get会返回一个类ConsumerRecord, 内容格式如下:
                    # ConsumerRecord{topic=xxx, partition=xxx, offset=xxx, timestamp=xxx, timestamp_type=xxx, ...}
                    record_metadata = future.get(timeout=10)  # 返回元数据
                    partition = record_metadata.partition     # 数据所在的分区
                    offset = record_metadata.offset           # 数据所在分区的位置
                    log.debug("save success, partition: {}, offset: {}".format(
                        partition, offset))  # 输出到日志
                print("您使用方法二同步发送消息,此次一共发送了{}条消息".format(counter))
                break
            except Exception as e:
                log.error("Kafka sync send fail, {}.".format(e))
                traceback.format_exc()

    def send_message_asyn_producer_callback(self, message):
        """
        方法三:
            异步发送+回调函数(消息以异步的方式发送,通过回调函数返回消息发送成功/失败)
        说明:
            1、在调用send方法发送消息的同时,指定一个回调函数,服务器在返回响应时会调用该回调函数,通过回调函数
            能够对异常情况进行处理,当调用了回调函数时,只有回调函数执行完毕生产者才会结束,否则一直会阻塞。
            2、如果业务上需要知道消息发送是否成功,并且对消息的顺序不关心,那么可以用异步+回调的方式来发送消息,
            配合参数retries=0,并将发送失败的消息记录到日志文件中。
        """
        while True:
            counter = 0  # 记录一共发了多少条消息
            try:
                for item in message.split(","):
                    counter += 1  # 发一条消息就增加一条
                    self.producer.send(kafka_config.TOPIC, item.encode('utf-8')).add_callback(
                        self.send_success).add_errback(self.send_error)
                    # 注册回调也可以这样写,上面的写法就是为了简化
                    # future.add_callback(self._onSendSucess)
                    # future.add_errback(self._onSendFailed)
                # self.producer.send(self.topic, data).add_callback(self.send_success).add_errback(self.send_error)
                self.producer.flush()  # 批量提交
                print("您使用方法三异步+回调发送消息,此次一共发送了{}条消息".format(counter))
                self.producer.close()
                break
            except Exception as e:
                log.error("Kafka asyn send fail, {}.".format(e))
                traceback.format_exc()

    def send_success(self, record_metadata):
        """
        异步发送成功回调函数,也就是真正发送到kafka集群且成功才会执行。发送到缓冲区不会执行回调方法。
        """
        print("发送成功")
        print("被发往的主题:", record_metadata.topic)
        print("被发往的分区:", record_metadata.partition)
        # 这个偏移量是相对偏移量,也就是相对起止位置,也就是队列偏移量。
        print("队列位置:", record_metadata.offset)
        log.debug("save success")

    def send_error(self):
        print("发送失败")
        log.debug("save error")

	# 最初最简单的发送消息版本
    # def send_message(self, message):
    #     producer = self.producer.send(
    #         kafka_config.TOPIC, message.encode('utf-8'))
    #     producer.flush()

    def receive_message(self):
        print("消费者开始消费来自生产者的消息:")
        self.consumer.subscribe([kafka_config.TOPIC])
        for k, v in enumerate(self.consumer, start=1):    # 这里将list转换为enumerate类型
            print("收到第{}条消息为:{}".format(k, v.value.decode('utf-8')))
            # 消费,这里具体为打印,到时候具体什么逻辑操作,再定


'''
不足:
    1、单机,没有实现分布式。尝试搭建一个boker集群,并指定不同分区试一试分布式;
    2、配置文件很简单,应像之前那样修改,例如读一个json文件来返回;
    3、代码是否有些变量和方法需要改成私有?
    4、此代码并不是最终抽象的接口,例如回调函数那里,可以利用@abc.abstractmethod,变成抽象方法。
    好处是不同实例可能回调函数编写不同,例如UAV实例和environment实例;
    5、message那里,有待商榷。目前初步想法是用户实例产生的:环境状态信息、智能体信息、错误信息,
    这些统一格式例如json格式,然后用户实例调用时可以将json里的message统一发送。统一格式的好处是
    就算message很复杂,但是统一后可以对message进行统一处理,例如切片处理等。
'''


4.3、run.py

# -*- coding: utf-8 -*-
import kafka_interface
import time

usermessage = input('请输入你要发的消息,每条消息间用英文逗号隔开: ')


kafka_interface = kafka_interface.KafkaInterface()

start_time = time.time()

'''
测试时记得修改,测试3种方案
'''
kafka_interface.send_message_asyn_producer(usermessage)  # 简单异步,即发送并忘记
# kafka_interface.send_message_sync_producer(usermessage) # 同步
# kafka_interface.send_message_asyn_producer_callback(usermessage) # 异步加回调

end_time = time.time()
print("time: {}".format(end_time-start_time))

kafka_interface.receive_message()


4.4. テスト

テスト 1 : run.py ファイルを変更し、単純な非同期送信を使用する

ここに画像の説明を挿入
ここに画像の説明を挿入

テスト 2 : run.py ファイルを変更し、同期送信を使用する

ここに画像の説明を挿入

ここに画像の説明を挿入
テスト 3 : run.py ファイルを変更し、非同期 + コールバックを使用して送信します (下の図は間違っており、同期とコールバックではありません)。

ここに画像の説明を挿入

ここに画像の説明を挿入

分析:

  • メッセージを同期的に送信する時間は、非同期的に送信する場合よりも長くなることがわかります。
  • 一般的には非同期 + コールバックがよく使用されます

知らせ:

ctrl+z で run.py を終了する場合は、run.py が表示されてから終了する前に必ず 5 秒待ってください。そうしないと、以下の繰り返し消費が発生します。

ここに画像の説明を挿入

理由:

繰り返し消費には 2 つの一般的なシナリオがあり、1 つはコンシューマーの消費プロセス中にアプリケーション プロセスが強制終了または異常終了 (ハング...) すること、もう 1 つはコンシューマーが長時間消費することです。

  1. コンシューマー消費プロセス中にプロセスがハング/異常終了する

    Kafka コンシューマー側の使用では、オフセット (Offset) を送信する方法として、自動送信と手動送信の 2 つの方法があります。自動送信の場合、コンシューマが消費するメッセージのバッチをプルするときに、オフセットを送信する必要があります。コンシューマがオフセットを送信する前に、コンシューマはハングアップします。コンシューマが再起動してオフセットを再度プルすると、プルはまだ、以前に消費された Offset をハングアップするため、繰り返し消費される問題が発生します。手動送信モードでは、コード呼び出しを送信する前にコンシューマが切断され、繰り返し消費が発生します。

  2. 消費者が費やす時間が長すぎる

    Kafka コンシューマのパラメータ max.poll.interval.ms は、2 つのポーリング間の最大間隔を定義します。そのデフォルト値は 5 分です。これは、コンシューマがポーリング メソッドによって返されたメッセージを 5 分以内に消費できない場合、コンシューマは「グループからの脱退」リクエストを開始します。

    コンシューマ グループを離れると、Rebalance が開始されるため、Offset の送信は失敗します。リバランス後、コンシューマーがパーティションを再度割り当てた後、ポーリングはメッセージを再度プルし、以前に消費されたメッセージから消費を開始します。これにより、繰り返し消費が発生します。そして、消費時間が長すぎるという問題が解決されない場合、ニュースのこの部分が繰り返し消費される可能性があります。

    一般的に、消費中にメッセージ データをウェアハウスに処理するが、オフセット送信の実行時に Kafka がダウンしているか、ネットワークがオフセットを送信できない場合、サービスを再起動するかリバランス プロセスがトリガーされると、コンシューマはメッセージデータを再度送信します。

まとめ:ここで繰り返し消費する理由は理由1です。ここでの設定は、デフォルトでオフセットを自動的に送信することであり、コンシューマーが消費後 5 秒ごと (auto.commit.interval.ms で指定) にオフセットを送信すると、自動送信が行われます。そして、ここでの殺害プロセスは速すぎて、提出する時間がないため、繰り返し消費されるのは当然です。

5. 第 3 バージョンのインターフェース

例証します:

1. Kafka_config.py と kafka_interface.py はあまり変更されておらず、run.py のみが変更されています。run.py を生産者.py と消費者.py に分割して、2 つの端末間の通信をシミュレートします。コードには 3 つのメソッドがあるため、3 つのプロデューサー.py に分割されています。

2. テスト中の経験。

5.1、kafka_config.py

4.1 コードを参照

5.2、kafka_interface.py

4.2 コードを参照

5.3、プロデューサー1.py

# -*- coding: utf-8 -*-
import kafka_interface

def runProducer():
    usermessage = input('请输入你要发的消息,每条消息间用英文逗号隔开: ')
    ki = kafka_interface.KafkaInterface()
    ki.send_message_asyn_producer(usermessage)  # 简单异步,即发送并忘记

if __name__ == '__main__':
    print("你使用的是方式一,简单异步,发送消息")
    while True:
        runProducer()

5.4、プロデューサー2.py

# -*- coding: utf-8 -*-
import kafka_interface

def runProducer():
    usermessage = input('请输入你要发的消息,每条消息间用英文逗号隔开: ')
    ki = kafka_interface.KafkaInterface()
    ki.send_message_sync_producer(usermessage)  # 同步

if __name__ == '__main__':
    print("你使用的是方式二,同步,发送消息")
    while True:
        runProducer()

5.5、プロデューサー3.py

# -*- coding: utf-8 -*-
import kafka_interface

def runProducer():
    usermessage = input('请输入你要发的消息,每条消息间用英文逗号隔开: ')
    ki = kafka_interface.KafkaInterface()
    ki.send_message_asyn_producer_callback(usermessage)  # 异步加回调

if __name__ == '__main__':
    print("你使用的是方式三,异步加回调,发送消息")
    while True:
        runProducer()

5.6、consumer1.py

# -*- coding: utf-8 -*-
import kafka_interface

def runConsumer():
    ki = kafka_interface.KafkaInterface()
    ki.receive_message()

if __name__ == '__main__':
    while True:
        runConsumer()

5.7. テスト

ここに画像の説明を挿入
例証します:

1. テストするときは、1 つの端末を使用して、最初に Consumer1.py を実行し、それをそこに保持し、次に別の端末を使用して、Producer1.py、Producer2.py、Producer3.py を順番に実行できます。別のプロデューサに切り替えても、コンシューマは常にデータを消費できることがわかります。これは、コンシューマがサブスクライブしたパラメータが変更されていないためです。プロデューサが送信するとき、送信メソッドとメッセージを除きます。コンテンツ、その他のパラメータは変更されていません。これはブローカーと同じです。

2. 上の写真のバスケットは、直前のテストのスクリーンショットです。オフセットが 200 を超えていることがわかります。オフセットが 0 から始まらないのはなぜですか? 送信ブローカーが変更されていないためです。パート 4 は何度もテストされています。パート 5 のコード ロジックは多少変更されていますが、送信されるブローカーは変更されていないため、送信されたメッセージは順番に最後に追加されます。したがって、200 を超えるオフセットが存在します。ここではテスト直後に表示されました。


6、DockerFileとDocker Compose

6.1 相違点

dockerfile の役割は、イメージを最初から構築することです。インストールや動作に必要な環境やプログラムコードなどが含まれています。この作成プロセスは dockerfile を使用して行われます。Dockerfile - docker build コマンド用に準備され、独立したイメージの作成に使用され、docker-compose でリアルタイム ビルドをビルドするために使用することもできます docker-compose.yml - docker-compose 用に準備されたスクリプトで、複数のコンテナを同時に管理でき
ますそれらの間の関係、公式イメージを使用するか自分でビルドするか、さまざまなネットワーク ポートの定義、ストレージ スペースの定義などを含む時間。

docker-compose はコンテナを調整します。たとえば、php ミラー、mysql ミラー、nginx ミラーがあるとします。docker-composeがない場合は、起動するたびに各コンテナの起動パラメータ、環境変数、コンテナの名前を入力し、異なるコンテナのリンクパラメータを指定するという一連の操作が必要となり、非常に面倒です。docker-composer を使用した後は、これらのコマンドを docker-composer.yml ファイルに一度に記述することができ、環境全体 (3 つのコンテナーを含む) を起動するたびに、 docker-composer を入力するだけで済みます。 up コマンドは次のとおりです。 Ok。

6.2 docker-compose のインストール

1.「ルーキー チュートリアル」への参照が失敗しました:

sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

Docker Compose は GitHub に保存されており、安定しておらず、次のエラーが報告されます。

ここに画像の説明を挿入
そして、それを次のコマンドに置き換えます。

sudo curl -L https://get.daocloud.io/docker/compose/releases/download/v2.4.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

そして、次のエラーが報告されました。

ここに画像の説明を挿入

2. 解決策: wget コマンドに置き換えます。

# 1、安装
sudo wget https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m) -O /usr/local/bin/docker-compose

ここに画像の説明を挿入

# 2、设置文件可执行权限
chmod +x /usr/local/bin/docker-compose
# 3、查看是否安装成功
docker-compose version

ここに画像の説明を挿入

6.3 docker-compose を使用して Kafka コンテナを作成する

ミラー イメージはここにプルされているため、docker fileファイルを使用してミラー イメージを作成する必要はありません。

1. テスト ディレクトリを作成し、そこに切り替えます。

# 创建一个测试目录
mkdir composetest
# 切换至该目录
cd composetest

2. テスト ディレクトリにdocker-compose_1.yml次の名前の。

vim docker-compose_1.yml

次に、次の内容を貼り付けます。「Docker を使用した Kafka クラスターの構築の概要」を参照してください。

# 指定本 yml 依从的 compose 哪个版本制定的
version: "3"

services:
  zookeeper_1:
    image: wurstmeister/zookeeper
    container_name: zookeeper_1
    ports:
      - 2181:2181

  kafka_1:
    image: wurstmeister/kafka
    container_name: kafka_1
    ports:
      - 9093:9093
    depends_on:
      - zookeeper_1
    environment:
      KAFKA_BROKER_ID: 0
      KAFKA_ZOOKEEPER_CONNECT: zookeeper_1:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9093
      KAFKA_LISTENERS: PLAINTEXT://localhost:9093
      KAFKA_NUM_PARTITIONS: 3
      KAFKA_DEFAULT_REPLICATION_FACTOR: 2
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    

3.docker-compose_1.ymlファイルを実行します (docker-compose コマンドのパラメーターについては、「docker-compose パラメーターの説明」を参照してください)。

# 执行 docker-compose_1.yml 文件
sudo docker-compose -f docker-compose_1.yml up -d

2 つのコンテナー、zookeeper_1 と kafka_1 が正常に作成されたことがわかります。

ここに画像の説明を挿入
同時に、sudo docker ps -aコマンドを、次のように表示されます。

ここに画像の説明を挿入

4. 以下は、ほとんどのブログを集めた yml ファイルの概要とパラメーターの説明です。
参考:
「Kafka の Docker デプロイメント」
「Docker Kafka の Python 操作」
「Kafka の Docker-compose デプロイメント」
「使用説明書」 Kafka の Docker イメージ」(wurstmeister/kafka)》

version: "3"

services:
  zookeeper:
    image: wurstmeister/zookeeper
    # 有此参数,容器名称就以此参数为准,否则就以“当前文件夹名_上方”为准。
    # 比如这里有此参数,容器名为zookeeper,如果无此参数,容器名则为composetest_zookeeper
    container_name: zookeeper
    # 映射到宿主机的端口:zookeeper端口号
    ports:
      - 2181:2181

  kafka_0:
    image: wurstmeister/kafka
    container_name: kafka_0
    # 映射到宿主机的端口:kafka端口号
    ports:
      - 9092:9092
    # zookeeper要先于kakfa_0先启动
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 0
      # KAFKA_ADVERTISED_HOST_NAME: localhost
      KAFKA_ZOOKEEPER_CONNECT: zookeeper_1:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_LOG_DIRS: /kafka/kafka-logs-backend
      # KAFKA_AUTO_CREATE_TOPICS_ENABLE: true
      # KAFKA_DELETE_TOPIC_ENABLE: true
      # KAFKA_NUM_PARTITIONS: 3
      KAFKA_DEFAULT_REPLICATION_FACTOR: 2
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      # - kafka-data:/kafka
    # restart: always
    
  kafka_1:
    image: wurstmeister/kafka
    container_name: kafka_1
    ports:
      - 9093:9093
    depends_on:
      - zookeeper
      # - kafka_0
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper_1:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9093
      KAFKA_LISTENERS: PLAINTEXT://localhost:9093
      KAFKA_NUM_PARTITIONS: 3
      KAFKA_DEFAULT_REPLICATION_FACTOR: 2
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
  
  kafka_2:
    image: wurstmeister/kafka
    container_name: kafka_2
    ports:
      - 9094:9094
    depends_on:
      - zookeeper
      # - kafka_1
    environment:
      KAFKA_BROKER_ID: 2
      KAFKA_ZOOKEEPER_CONNECT: zookeeper_1:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9094
      KAFKA_LISTENERS: PLAINTEXT://localhost:9094
      KAFKA_NUM_PARTITIONS: 3
      KAFKA_DEFAULT_REPLICATION_FACTOR: 2
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

version: "3" : この yml が準拠する compose のバージョンを指定します。

サービス:

  • image:wurstmeister/kafka : 画像ソースは wurstmeister/kafka です。Hub.docker.com Web サイトで、最も多くのスターが付いた kafka イメージは、wurstmeister/kafka です。

  • 環境(環境変数)の説明:

    • KAFKA_ADVERTISED_HOST_NAME: クライアントがアクセスするブローカーのアドレス。
    • KAFKA_BROKER_ID: Kafka クラスターでは、各 Kafka がそれ自体を区別するための BROKER_ID を持っているため、この ID はクラスターの一意の識別子です。指定しない場合は、独自に生成されます。
    • KAFKA_ZOOKEEPER_CONNECT: 対応するzookeeper接続情報を設定します。同じdocker compose内にあるため、サービス名をホスト接続情報として使用できます。
    • KAFKA_ADVERTISED_LISTENERS: kafka のアドレス ポートを Zookeeper に登録します。この IP アドレスは、クライアントが現在のノードにアクセスできる IP を示す、特定のマシン IP に従って変更する必要があります。ネットワーク カードの IP が変更された場合、この IP アドレスの設定は場所も変更する必要があります。
    • KAFKA_LISTENERS: kafka のプロトコルとリスニング ポートを設定し、kafka の現在のノードがマシンのどのネットワーク カードを監視しているかを示します。この場所の IP アドレスを 0.0.0.0 として入力すると、監視されているすべてのネットワーク カードの情報を示すことができます。
    • KAFKA_LOG_DIRS: ログ データが保存されるディレクトリを指定します。デフォルトの場所については、この記事の 5 番目の、kafka ログのデフォルトの保存場所を表示する方法を参照してください。
    • KAFKA_AUTO_CREATE_TOPICS_ENABLE: true は、kafka が自動的にトピックを作成し、プロデューサーとコンシューマーによって接続された TOPIC の自​​動作成がないことを意味します。
    • KAFKA_DELETE_TOPIC_ENABLE: truekafka はトピックの削除を有効にし、自動作成が有効で、削除も有効にします。それ以外の場合は、削除後に自動的に作成されます。
  • ボリューム (マウントされたボリューム) の説明:
    まず、何がマウントされているかを理解する必要があります。次の 2 つの記事を確認するだけで十分です。

    「マウントって何?Linux のマウント」の説明は非常に簡単です。
    「Docker入門と速攻Dockerの使い方 ボリュームマウントの使い方(3)」も非常にわかりやすく説明されています。

    • /var/run/docker.sock:/var/run/docker.sock:ドッカーの靴下を引っ掛けます。これは、docker ps や docker port などのコマンドをコンテナ内で実行し、ホスト マシン上で実行したものと同じ結果が得られるようにするための非常に重要なパラメータです。詳細については、「docker の /var/」を参照してください。 run/docker.sockパラメーター"
    • kafka-data:/kafka: 永続化のために Kafka ログ情報をマウントします。データの永続化が必要ない場合は、このマウントの手順を削除できます。
  • restart: always : docker を再起動するときに、関連するコンテナーを自動的に起動できるようにします。たとえば、Docker で Nginx を実行します。Nginx は現在一般的に使用されている Web サーバーであり、停電やホストの再起動などの予期せぬイベントから自動的に回復することを期待しています。ただし、インターネット上の一部のブログでは、docker が kafka をインストールするときにこのパラメーターを追加しましたが、一部のブログでは追加しませんでした。ここに追加する必要はありません。Docker コンテナの再起動戦略は通常、運用環境で使用され、開発環境と実験環境は無視できます。「docker の再起動時に関連コンテナを自動的に起動する」を実装する必要がある場合は、パラメーターを追加するだけで済みます。詳細については、Baidu を参照してください。


7. Docker にデプロイされた kakfa ログ ファイルの場所を表示する方法

通常、kafka を Linux にデプロイし、そのログ ファイルのデフォルトの場所は です/tmp/kafka-logsdockerにデプロイされたkafkaのログファイルはどこにありますか?

Kafka ログが保存されている場所を表示するには、まず exec コマンドを使用してコンテナに入り、次に kafka パスの下の config フォルダー (kafka\config) を開いてから、server.properties を開いて log.dirs を表示します。

ここに画像の説明を挿入

閲覧した結果は以下の通りで、ログが保存されている場所は となっていることが分かります/kafka/kafka-logs-63d8a927b72c

ここに画像の説明を挿入
上記のパスに従って、次のように確認してみると、実際に見つかりました。

ここに画像の説明を挿入

Kafka のデフォルト データは 7 日間保存され、server.properties を通じて表示または変更することもできます。

ここに画像の説明を挿入

8. Kafka サーバーは複数のプロデューサーをどのように区別しますか

8.1 説明

複数のプロデューサーが同じトピックにメッセージを送信する場合、ブローカーは異なるプロデューサーをどのように区別するのでしょうか?

client_id 属性に依存します。

Kafka では、プロデューサーを構成するときに client.id パラメーターを設定しない場合、Kafka はプロデューサーのデフォルトのクライアント ID を生成します。

開発者がプロ​​デューサーに特定の client.id を設定しない場合、次の問題が発生する可能性があります。

1. プロデューサーを識別するのは簡単ではありません。Kafka クラスターで複数のプロデューサーを実行し、それらが同じデフォルトのクライアント ID を使用する場合、異なるプロデューサーからのメッセージを識別するのは困難です。

2. 管理が容易ではない: Kafka クラスター管理ツールを使用して Kafka クラスターを監視および管理する場合、特定の client.id がないとプロデューサーを管理および識別することが困難になります。

3. Kafka のベスト プラクティスへの違反: Kafka のベスト プラクティスの 1 つは、各プロデューサーに一意のクライアント ID を割り当てることであり、これは管理性とスケーラビリティの向上に役立ちます。

したがって、Kafka プロデューサを構成するときは常に client.id パラメータを設定して、各プロデューサが一意のクライアント ID を持つようにすることをお勧めします。クライアント ID を手動で指定したくない場合は、UUID モジュールなどの一意の Python ライブラリを使用して一意のクライアント ID を生成できます。

具体的なコードは次のとおりです。

import uuid
from kafka import KafkaProducer

# 指定Kafka集群的地址
bootstrap_servers = ['localhost:9092']

# 创建KafkaProducer对象,并使用UUID生成唯一的客户端ID
producer = KafkaProducer(
    bootstrap_servers=bootstrap_servers,
    client_id=uuid.uuid4().hex
)

8.2 client_id を設定しない実験

client_id プロパティを表示する方法。

# client_id = producer.client_id  # 错误,未设置此属性时没有此属性
client_id = producer.config['client_id']

具体的な実験内容は以下の通りです。

1. パート 6 では、コードの 3 つの送信メソッドがそれぞれ追加されます。

print("producer的id为:{}".format(self.producer.config['client_id']))

再度実行すると、下の図で client_id 属性がアクティブに定義されていない場合、文字列が自動的に割り当てられることがわかりますが、このとき、3 つのメソッドは実際には同じプロデューサーですが、メッセージは異なる方法で送信されます。 。

ここに画像の説明を挿入
2. 上記に基づいて、2 番目のプロデューサーを定義して最初のプロデューサーと同じメッセージを送信し、最後に最初のメソッドを実行してメッセージを送信します。2 つの異なる client_id が自動的に生成されていることがわかります。

# -*- coding: utf-8 -*-
# producer1.py
import kafka_interface


def runProducer():
    usermessage = input('请输入你要发的消息,每条消息间用英文逗号隔开: ')
    # 定义第一个生产者
    ki = kafka_interface.KafkaInterface()
    ki.send_message_asyn_producer(usermessage)  # 简单异步,即发送并忘记
    #定义第二个生产者,并发送与第一个生产者一样的消息
    ki_2 = kafka_interface.KafkaInterface()
    ki_2.send_message_asyn_producer(usermessage)  # 简单异步,即发送并忘记


if __name__ == '__main__':
    print("你使用的是方式一,简单异步,发送消息")
    while True:
        runProducer()

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_40967086/article/details/130197241