如何在Kafka中为同一组ID使用不同编程语言的多个使用者

时间:2019-12-13 12:01:06

标签: c# node.js apache-kafka kafka-consumer-api

我想用Kafka(多种编程语言)为一个主题创建负载平衡。所以我做了以下。

  1. 创建了一个包含4个分区的主题。
  2. 在C#中创建了一个生产者(每秒产生一条消息)
  3. 在C#中创建了一个使用者(consumer1)(使用者组:testConsumerGrp)
  4. 在NodeJ(消费者组:testConsumerGrp)中又创建了一个消费者(consumer2)

我在C#中使用了confluent.kafka,在NodeJ中使用了kafkajs

我打开生产者并保持运行。

  1. 如果我仅运行C#使用者,则可以正常工作。
  2. 如果我仅运行NodeJs使用者,则可以正常工作。
  3. 如果我运行多个C#使用者(仅c#并且少于4个实例),则可以正常工作。
  4. 如果我运行多个NodeJ使用者(仅NodeJ且少于4个实例),则可以正常工作。
  5. 如果我运行一个C#和一个NodeJs使用者,那么我会遇到Inconsistent group protocol错误

我们不能为同一个消费群体使用两种编程语言吗?

C#的生产者-Windows形式

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Confluent.Kafka;

namespace KafkaProducer
{
    public partial class frmProducer : Form
    {
        const string TOPIC = "testTopic";
        private IProducer<Null, string> pBuilder;

        public frmProducer()
        {
            InitializeComponent();
        }

        private async void timer1_Tick(object sender, EventArgs e)
        {
            try
            {
                // instead of sending some value, we send current DateTime as value
                var dr = await pBuilder.ProduceAsync(TOPIC, new Message<Null, string> { Value = DateTime.Now.ToLongTimeString() });

                // once done, add the value into list box
                listBox1.Items.Add($"{dr.Value} - Sent to Partition: {dr.Partition.Value}");
                listBox1.TopIndex = listBox1.Items.Count - 1;
            }
            catch (ProduceException<Null, string> err)
            {
                MessageBox.Show($"Failed to deliver msg: {err.Error.Reason}");
            }
        }

        private void frmProducer_Load(object sender, EventArgs e)
        {
            ProducerConfig config = new ProducerConfig { BootstrapServers = "localhost:9092" };
            pBuilder = new ProducerBuilder<Null, string>(config).Build();

            timer1.Enabled = true;
        }

        private void frmProducer_FormClosing(object sender, FormClosingEventArgs e)
        {
            timer1.Enabled = false;
            pBuilder.Dispose();
        }
    }
}

使用C#的用户-Windows窗体

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Confluent.Kafka;

namespace KafkaConsumer
{
    public partial class frmConsumer : Form
    {
        CancellationTokenSource cts = new CancellationTokenSource();

        public frmConsumer()
        {
            InitializeComponent();
        }

        private void StartListen()
        {
            var conf = new ConsumerConfig
            {
                GroupId = "test-consumer-group",
                BootstrapServers = "localhost:9092",
                AutoOffsetReset = AutoOffsetReset.Earliest
            };

            using (var c = new ConsumerBuilder<Ignore, string>(conf).Build())
            {
                c.Subscribe("testTopic");

                //TopicPartitionTimestamp tpts = new TopicPartitionTimestamp("testTopic", new Partition(), Timestamp.  )
                //c.OffsetsForTimes()

                try
                {
                    while (true)
                    {
                        try
                        {
                            var cr = c.Consume(cts.Token);

                            // Adding the consumed values into the UI
                            listBox1.Invoke(new Action(() =>
                            {
                                listBox1.Items.Add($"{cr.Value} - from Partition: {cr.Partition.Value}" );
                                listBox1.TopIndex = listBox1.Items.Count - 1;
                            }));
                        }
                        catch (ConsumeException err)
                        {
                            MessageBox.Show($"Error occured: {err.Error.Reason}");
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    // Ensure the consumer leaves the group cleanly and final offsets are committed.
                    c.Close();
                }
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            cts.Cancel();
        }

        private async void frmConsumer_Load(object sender, EventArgs e)
        {
            await Task.Run(() => StartListen());
        }
    }
}

NodeJ中的消费者

const { Kafka } = require("kafkajs");

const kafka = new Kafka({
  clientId: 'my-app',
  brokers: ["localhost:9092"]
});

const consumer = kafka.consumer({ groupId: "test-consumer-group" });

const run = async () => {
  // Consuming
  await consumer.connect();
  await consumer.subscribe({ topic: "testTopic", fromBeginning: false });

  await consumer.run({
    eachMessage: async ({ topic, partition, message }) => {
      console.log(message.value.toString() + " - from Partition " + partition);
    }
  });
};

run().catch(console.error);

如果我同时运行C#和NodeJs使用者,则会出现Inconsistent group protocol错误。

如何在Kafka中使用来自不同编程语言的多个使用者?

2 个答案:

答案 0 :(得分:0)

简短答案

这可能与您想像的不同语言无关。发生这种情况的原因是两个消费者客户端(及其库)的协议不同。

尝试在两个消费者客户端中设置以下属性:

partition.assignment.strategy = round-robin

注意:我刚刚提供了常规属性,因此您需要查看针对客户的语言特定版本。您甚至可以将其设置为range,但保持一致。

说明如下

通读Kafka's wiki上的协议以找出Inconsistent group protocol的根本原因-结果是在以下情况下返回此结果:

  1. 有一个活跃的消费群体,其中有活跃/活跃的消费者
  2. 然后有一个新的消费者加入了一个与当前组的协议类型不兼容的协议类型(或一组协议)的组

现在,ConsumerGroupProtocolMetadata中可能有各个方面,但是partition.assignment.strategy似乎在您使用的客户端库中确实有所不同。

dotnet clientlibrdkafka的包装,将上述属性的值默认为range。这是reference

其中

根据{{​​3}}的

kafkajs将其默认设置为round-robin-从而导致不一致。

希望这会有所帮助。

答案 1 :(得分:0)

我知道这来得太晚了,但这是因为同一个组命名

当您启动 C# 客户端时,它会为其使用者创建一个组。

例如group-1(group-1-consumer-1、group-1-consumer-2 等) - 这些名称是自动分配的,所以不要打扰。我认为您可以手动设置这些,但不建议这样做以避免潜在的名称冲突。

现在,当您启动它时,您无法从不同的组运行器(来自另一个微服务)添加相同的组。 看看 Lalit 从 Kafka wiki 引用的内容:

<块引用>

有一个活跃的消费者组,有活跃/正在运行的消费者

现在,当您启动 nodeJs 时,您应该使用不同的组名,因为很可能会使用该数据执行其他任务。

是的,您可以为两个组订阅相同的主题,因为 Kafka 会为每个组以及他们离开的位置保留一个偏移量。