Pulsar 4の詳しい解説―Pulsarの高度な機能(例:Go言語)

Pulsarの詳しい説明 - インデックスディレクトリ

1. パーティションテーマ

簡単な説明:
Pulsar は、異なるノードおよびクラスター間でメッセージを確実に配信できるようにする分散メッセージング システムです。
パーティション化されたトピックは、スケーラビリティとパフォーマンスを向上させるために使用される Pulsar の重要な機能の 1 つです。

1.1 パーティショントピックとは何ですか?

Pulsar では、トピックを複数のパーティションに分割できます。各パーティションは、独自のメッセージ ストレージおよび処理メカニズムを備えた独立したメッセージ キューです。トピックをパーティション化すると、メッセージのプロデューサーとコンシューマー間での並列処理と負荷分散が向上します。

1.2 パーティション戦略

パーティショニング ポリシーは、メッセージをさまざまなパーティションに分散する方法を決定するルールです。Pulsar は、いくつかの組み込みパーティショニング戦略を提供しており、カスタム パーティショニング戦略もサポートしています。

一般的な組み込みのパーティショニング戦略をいくつか示します。

  • ラウンド ロビン パーティション分割戦略:メッセージを異なるパーティションに順番に配布して、メッセージが均等に分散されるようにします。

  • ハッシュ パーティション化戦略:メッセージ コンテンツのハッシュを使用してメッセージが属するパーティションを決定すると、特定のメッセージが常に同じパーティションに送信されるようになります。

  • 範囲パーティション化戦略:キー値の範囲に基づいてメッセージをさまざまなパーティションに割り当てます。これは、範囲ごとにメッセージをクエリする場合に非常に便利です。

1.3 パーティショニングとパフォーマンス

パーティション テーマによってもたらされるパフォーマンスの向上は、次の側面に反映されます。

  • 並列処理:トピックを複数のパーティションに分割することで、より高度な並列処理を実現できます。複数のプロデューサーは異なるパーティションに並行してメッセージを送信でき、複数のコンシューマーは異なるパーティションから並行してメッセージを受信できます。

  • 負荷分散:パーティション化されたトピックにより、メッセージを異なるパーティションに均等に分散できるため、負荷分散が向上します。これは、クラスター内で Pulsar インスタンスを水平方向にスケーリングする場合に重要です。

1.4 例

パーティション テーマの使用方法を説明するために、次の例が使用されます。

package main

import (
	"context"
	"fmt"
	"github.com/apache/pulsar/pulsar-client-go/pulsar"
)

func main() {
    
    
	client, err := pulsar.NewClient(pulsar.ClientOptions{
    
    
		URL: "pulsar://localhost:6650",
	})
	if err != nil {
    
    
		fmt.Println("Error creating Pulsar client:", err)
		return
	}
	defer client.Close()

	topic := "my-partitioned-topic"
	producer, err := client.CreateProducer(pulsar.ProducerOptions{
    
    
		Topic:          topic,
		Partitioning:   pulsar.HashingSchemeMurmur3,
		MessageRouting: pulsar.MessageRoutingModeCustomPartition,
	})
	if err != nil {
    
    
		fmt.Println("Error creating producer:", err)
		return
	}
	defer producer.Close()

	// 生产消息
	for i := 0; i < 10; i++ {
    
    
		message := &pulsar.ProducerMessage{
    
    
			Payload: []byte(fmt.Sprintf("Message %d", i)),
		}
		_, err := producer.Send(context.Background(), message)
		if err != nil {
    
    
			fmt.Println("Error sending message:", err)
			return
		}
		fmt.Printf("Produced: %s\n", message.Payload)
	}

	// 消费消息
	consumer, err := client.Subscribe(pulsar.ConsumerOptions{
    
    
		Topic:            topic,
		SubscriptionName: "my-subscription",
		SubscriptionType: pulsar.Shared,
	})
	if err != nil {
    
    
		fmt.Println("Error creating consumer:", err)
		return
	}
	defer consumer.Close()

	for i := 0; i < 10; i++ {
    
    
		msg, err := consumer.Receive(context.Background())
		if err != nil {
    
    
			fmt.Println("Error receiving message:", err)
			return
		}
		fmt.Printf("Consumed: %s\n", msg.Payload())
		consumer.Ack(msg)
	}
}

この例では、パーティション化されたトピックを作成し、ハッシュ パーティション化戦略を通じてさまざまなパーティションにメッセージを送信します。次に、これらのメッセージを受信するコンシューマーを作成します。これにより、分散環境でメッセージが均等に生成および消費されることが保証されます。

2. メッセージの遅延

遅延メッセージを使用すると、メッセージの送信後に配信を遅らせることができます。これは、処理に時間がかかるタスクや、特定の時間にトリガーされるイベントを処理する場合に非常に役立ちます。

2.1 遅延メッセージの構成

Pulsar は柔軟な遅延メッセージ構成を提供し、特定のニーズに応じて遅延時間を設定できます。

以下に主要な構成パラメータをいくつか示します。

  • 遅延時間:メッセージの送信後に待機する必要がある時間をミリ秒単位で定義します。ビジネス ニーズに応じて、さまざまな遅延時間を設定できます。

  • 遅延メッセージのストレージ:遅延メッセージは通常、時間通りにトリガーされるようにするために別のストレージ メカニズムを必要とします。Pulsar は、遅延メッセージを専用の遅延ストレージに保存します。

2.2 遅延メッセージの使用シナリオ

遅延メッセージはさまざまなシナリオで使用できます。代表的な使用例には次のようなものがあります。

  • スケジュールされたタスク:遅延時間を設定することで、特定のビジネス ロジックの実行やスケジュールされた間隔での通知の送信など、スケジュールされたトリガー タスクを実装できます。

  • 再試行メカニズム:失敗したタスクを処理する場合、メッセージを遅延させることで単純な再試行メカニズムを実装できます。タスクが失敗した場合、後で再試行できるように、再送信を遅らせるようにメッセージを設定できます。

  • スケジューリング システム:遅延メッセージを使用して、遅延時間を設定してタスクの実行をスケジュールする単純なスケジューリング システムを構築することもできます。

2.3 例

簡単な Go 言語のコード例を通じて、遅延メッセージの使用法を説明してみましょう。

package main

import (
	"context"
	"fmt"
	"github.com/apache/pulsar/pulsar-client-go/pulsar"
	"time"
)

func main() {
    
    
	client, err := pulsar.NewClient(pulsar.ClientOptions{
    
    
		URL: "pulsar://localhost:6650",
	})
	if err != nil {
    
    
		fmt.Println("Error creating Pulsar client:", err)
		return
	}
	defer client.Close()

	topic := "my-delayed-topic"
	producer, err := client.CreateProducer(pulsar.ProducerOptions{
    
    
		Topic: topic,
	})
	if err != nil {
    
    
		fmt.Println("Error creating producer:", err)
		return
	}
	defer producer.Close()

	// 发送延迟消息
	message := &pulsar.ProducerMessage{
    
    
		Payload: []byte("Delayed Message"),
	}
	message.SetDeliverAfter(time.Second * 30) // 设置30秒的延迟时间

	_, err = producer.Send(context.Background(), message)
	if err != nil {
    
    
		fmt.Println("Error sending delayed message:", err)
		return
	}
	fmt.Printf("Sent delayed message with ID: %s\n", message.ID())
}

この例では、トピックを作成し、プロデューサーを使用して、送信後 30 秒後に配信される遅延メッセージを送信します。これにより、遅延処理が必要ないくつかのシナリオをシミュレートできます。

3. メッセージの圧縮

メッセージを圧縮すると、メッセージ配信中にデータのサイズが削減されるため、ネットワーク帯域幅の使用量が削減され、伝送効率が向上します。

Pulsar は複数の圧縮アルゴリズムをサポートしており、特定のニーズに応じて構成できます。

3.1 サポートされている圧縮アルゴリズム

Pulsar は、以下を含むさまざまな一般的な圧縮アルゴリズムをサポートしていますが、これらに限定されません。

  • LZ4:圧縮率は低いですが速度が非常に速い高速圧縮アルゴリズムで、高速伝送が必要なシナリオでの使用に適しています。

  • Snappy: LZ4 と同様に、Snappy は CPU リソースが限られている環境に最適な高速圧縮アルゴリズムです。

  • Zstandard:高い圧縮率と中程度の速度を備えたアルゴリズムで、比較的十分な帯域幅リソースがあるシナリオに適しています。

3.2 圧縮によるパフォーマンスへの影響

メッセージを圧縮すると、データ送信のサイズをある程度削減できますが、特にプロデューサー側とコンシューマー側での圧縮および解凍プロセス中に、パフォーマンスのオーバーヘッドも発生します。したがって、メッセージ圧縮を使用する場合、圧縮率とパフォーマンスのオーバーヘッドの間にはトレードオフの関係があります。

圧縮パフォーマンスに影響を与える主な側面は次のとおりです。

  • CPU リソースの消費:圧縮および解凍プロセスには特定の CPU リソースが必要です。高スループットのプロデューサーおよびコンシューマーのシナリオでは、システムの CPU 使用率を考慮する必要があります。

  • 遅延の増加:圧縮と解凍により、多少の遅延が発生します。遅延の影響を受けやすいアプリケーションでは、メッセージ圧縮を有効にするかどうかを慎重に選択する必要があります。

3.3 例

Pulsar でメッセージ圧縮を使用する方法を示す例:

package main

import (
	"context"
	"fmt"
	"github.com/apache/pulsar/pulsar-client-go/pulsar"
)

func main() {
    
    
	client, err := pulsar.NewClient(pulsar.ClientOptions{
    
    
		URL: "pulsar://localhost:6650",
	})
	if err != nil {
    
    
		fmt.Println("Error creating Pulsar client:", err)
		return
	}
	defer client.Close()

	topic := "my-compressed-topic"
	producer, err := client.CreateProducer(pulsar.ProducerOptions{
    
    
		Topic:       topic,
		Compression: pulsar.LZ4, // 设置压缩算法
	})
	if err != nil {
    
    
		fmt.Println("Error creating producer:", err)
		return
	}
	defer producer.Close()

	// 发送压缩消息
	message := &pulsar.ProducerMessage{
    
    
		Payload: []byte("Compressed Message"),
	}
	_, err = producer.Send(context.Background(), message)
	if err != nil {
    
    
		fmt.Println("Error sending compressed message:", err)
		return
	}
	fmt.Printf("Sent compressed message with ID: %s\n", message.ID())
}

この例では、トピックを作成し、圧縮アルゴリズムとして LZ4 を選択したプロデューサーを使用して圧縮メッセージを送信します。

4. メッセージの重複排除

メッセージの重複排除により、システムはメッセージの処理時に重複メッセージの処理を回避し、同じメッセージを受信したときに重複した結果が生成されないようにすることができます。

Pulsar は、組み込みのメッセージ重複排除メカニズムを提供し、ユーザー定義の重複排除メソッドもサポートします。

4.1 重複排除メカニズム

Pulsar のメッセージ重複排除メカニズムは、メッセージの一意の識別子 (メッセージ ID) に基づいています。各メッセージには一意の ID があり、Pulsar でメッセージの一意性を識別するために使用されます。メッセージ重複排除メカニズムは、メッセージ ID をチェックすることによって、メッセージが処理されたかどうかを判断します。

4.2 重複排除の実装方法

メッセージ重複排除の実装には通常、次の手順が含まれます。

  • 一意の ID を生成する:メッセージを送信するときは、メッセージに対して一意の ID を生成する必要があります。この ID は、メッセージの内容、タイムスタンプなどに基づいて生成できます。

  • 処理されたメッセージの ID を保存する:処理されたメッセージの ID をシステムに保存する必要があります。永続ストア (データベースなど) を使用してこれらの ID を保存すると、システムが再起動された場合でも、処理されたメッセージの記録が確実に保持されます。

  • メッセージ処理時の ID の確認:メッセージの処理時に、システムは、処理されたメッセージのレコードにメッセージの ID がすでに存在するかどうかを確認します。存在する場合、それは重複メッセージとみなされ、直接破棄することも、それに応じて処理することもできます。

4.3 例

例は、Pulsar でメッセージ重複排除を使用する方法を示しています。

package main

import (
	"context"
	"fmt"
	"github.com/apache/pulsar/pulsar-client-go/pulsar"
)

func main() {
    
    
	client, err := pulsar.NewClient(pulsar.ClientOptions{
    
    
		URL: "pulsar://localhost:6650",
	})
	if err != nil {
    
    
		fmt.Println("Error creating Pulsar client:", err)
		return
	}
	defer client.Close()

	topic := "my-dedup-topic"
	producer, err := client.CreateProducer(pulsar.ProducerOptions{
    
    
		Topic: topic,
	})
	if err != nil {
    
    
		fmt.Println("Error creating producer:", err)
		return
	}
	defer producer.Close()

	// 发送消息
	messageContent := "Deduplicated Message"
	message := &pulsar.ProducerMessage{
    
    
		Payload: []byte(messageContent),
	}

	// 在生产者端生成唯一 ID,这里使用消息内容的哈希值作为 ID
	messageID := fmt.Sprintf("%x", message.Payload)
	message.SetOrderingKey(messageID)

	// 发送消息
	_, err = producer.Send(context.Background(), message)
	if err != nil {
    
    
		fmt.Println("Error sending message:", err)
		return
	}
	fmt.Printf("Sent message with ID: %s\n", messageID)

	// 在消费者端处理消息时,检查消息的 ID 是否已经存在于已处理消息的记录中
	consumer, err := client.Subscribe(pulsar.ConsumerOptions{
    
    
		Topic:            topic,
		SubscriptionName: "my-subscription",
		SubscriptionType: pulsar.Shared,
	})
	if err != nil {
    
    
		fmt.Println("Error creating consumer:", err)
		return
	}
	defer consumer.Close()

	msg, err := consumer.Receive(context.Background())
	if err != nil {
    
    
		fmt.Println("Error receiving message:", err)
		return
	}

	// 获取消息的 ID
	receivedMessageID := fmt.Sprintf("%x", msg.Payload())
	fmt.Printf("Received message with ID: %s\n", receivedMessageID)

	// 判断是否已经处理过该消息
	if isMessageProcessed(receivedMessageID) {
    
    
		fmt.Println("Message already processed. Discarding...")
	} else {
    
    
		// 处理消息的业务逻辑
		fmt.Println("Processing message...")
		// TODO: 处理消息

		// 记录已处理消息的 ID
		recordProcessedMessage(receivedMessageID)

		// 确认消息已经被处理
		consumer.Ack(msg)
	}
}

// 模拟已处理消息的记录
var processedMessages = make(map[string]bool)

func isMessageProcessed(messageID string) bool {
    
    
	return processedMessages[messageID]
}

func recordProcessedMessage(messageID string) {
    
    
	processedMessages[messageID] = true
}

この例では、プロデューサー側でメッセージの一意の ID を生成し、それをメッセージの OrderingKey として設定しました。

コンシューマはメッセージを受信した後、処理されたメッセージのレコードにメッセージの ID がすでに存在するかどうかを確認することで、メッセージが処理されたかどうかを判断します。

メッセージが処理された場合は直接破棄でき、処理されていない場合は特定のビジネス処理を実行できます。これにより、システムはメッセージを処理するときに同じメッセージを 2 回処理することがなくなります。

5. マルチテナントのサポート

マルチテナントのサポートにより、同じ Pulsar クラスター内の異なるテナントの独立した名前空間とリソースの分離が可能になり、テナント間のデータとリソースが相互に干渉しなくなります。

5.1 テナント分離メカニズム

Pulsar は、テナント分離メカニズムを使用して、異なるテナント間のデータとメタデータが確実に相互に分離されるようにします。

主な分離メカニズムには次のものがあります。

  • 名前空間の分離: Pulsar は名前空間を分離の単位として使用します。各テナントは複数の名前空間を作成でき、各名前空間には独立したトピックとサブスクリプションがあります。

  • 認証と認可: Pulsar は、認証および認可メカニズムを使用して、接続要求を認証し、リソースへのアクセスを制御します。これにより、認証されたユーザーのみが、そのユーザーが属するテナントのリソースにアクセスできるようになります。

5.2 テナントのリソース管理

Pulsar では、テナント リソース管理には主にコンピューティング リソースとストレージ リソースの割り当てと制限が含まれます。これにより、テナントが相互のクラスター リソースの使用を妨げないようにすることができます。

テナントのリソース管理の主要な側面は次のとおりです。

  • リソース クォータ: Pulsar を使用すると、管理者はスループット、ストレージ スペースなどを含む各テナントのリソース クォータを設定できます。これにより、各テナントによるクラスター リソースの使用が制限されます。

  • 優先度:リソースが限られている場合に、優先度の高いテナントにより多くのリソースが割り当てられるように、テナントごとに異なる優先度を設定できます。

5.3 例

Pulsar でマルチテナント サポートを使用する方法を示す例:

package main

import (
	"context"
	"fmt"
	"github.com/apache/pulsar/pulsar-client-go/pulsar"
)

func main() {
    
    
	client, err := pulsar.NewClient(pulsar.ClientOptions{
    
    
		URL: "pulsar://localhost:6650",
	})
	if err != nil {
    
    
		fmt.Println("Error creating Pulsar client:", err)
		return
	}
	defer client.Close()

	// 定义租户和命名空间
	tenant := "my-tenant"
	namespace := "my-namespace"

	// 创建租户管理员
	admin, err := pulsar.NewAdminClient(pulsar.ClientOptions{
    
    
		URL: "http://localhost:8080",
	})
	if err != nil {
    
    
		fmt.Println("Error creating Pulsar admin client:", err)
		return
	}
	defer admin.Close()

	// 创建租户
	err = admin.Tenants().CreateTenant(context.Background(), tenant,
		&pulsar.TenantInfo{
    
    AllowedClusters: []string{
    
    "standalone"}})
	if err != nil {
    
    
		fmt.Println("Error creating tenant:", err)
		return
	}

	// 创建命名空间
	err = admin.Namespaces().CreateNamespace(context.Background(),
		pulsar.Namespace{
    
    
			Name: tenant + "/" + namespace,
		})
	if err != nil {
    
    
		fmt.Println("Error creating namespace:", err)
		return
	}

	// 使用租户和命名空间创建生产者和消费者
	topic := "persistent://" + tenant + "/" + namespace + "/my-topic"
	producer, err := client.CreateProducer(pulsar.ProducerOptions{
    
    
		Topic: topic,
	})
	if err != nil {
    
    
		fmt.Println("Error creating producer:", err)
		return
	}
	defer producer.Close()

	consumer, err := client.Subscribe(pulsar.ConsumerOptions{
    
    
		Topic:            topic,
		SubscriptionName: "my-subscription",
		SubscriptionType: pulsar.Shared,
	})
	if err != nil {
    
    
		fmt.Println("Error creating consumer:", err)
		return
	}
	defer consumer.Close()

	// 发送和接收消息
	message := &pulsar.ProducerMessage{
    
    
		Payload: []byte("Hello, Pulsar!"),
	}
	_, err = producer.Send(context.Background(), message)
	if err != nil {
    
    
		fmt.Println("Error sending message:", err)
		return
	}

	receivedMsg, err := consumer.Receive(context.Background())
	if err != nil {
    
    
		fmt.Println("Error receiving message:", err)
		return
	}

	fmt.Printf("Received message: %s\n", string(receivedMsg.Payload()))
}

この例では、テナントと名前空間を作成することでマルチテナントの分離を実装します。異なるテナントと名前空間を使用してプロデューサーとコンシューマーを作成し、それらの間のリソースとデータが相互に分離されていることを確認します。

おすすめ

転載: blog.csdn.net/weixin_49015143/article/details/135101116