【C#】Kafka for .NET 入坑记录

【C#】Kafka for .NET 入坑记录

一、有关Kafka

Kafka 是一个分布式、多副本、基于Zookeeper的消息流处理平台。Kafka 最初由Linkedin开发,为了解决 data pipeline 问题。2011年捐献给 Apache基金会,后来成为Apache的顶级开源项目。

Kafka 的未来:实时数据流平台。(出自饶军)

1-1、基本概念

  • 消息:Kafka 中的数据单元,也被称为记录。
  • 批次:消息会分批次写入Kafka,批次就是指一组消息。
  • 主题:消息的种类,也可以说一个主题代表一类消息。
  • 分区:物理上最小的存储单元。一个主题可以分为多个分区,每个分区内消息是有序的。
  • 生产者:生产消息,发送到主题。
  • 消费者:从主题获取消息,处理消息。
  • 消费者群组:由一个或多个消费者组成的群体。在同一个消费组中,一个消息只能被一个消息者消息。
  • 偏移量:一个不断自增的整数值,分区中一条消息的唯一标示符。可表示消费者在分区的位置。
  • broker:一个独立的Kafka服务器。
  • broker集群:由一个或多个broker组成的集群。
  • 副本:消息的备份。Kafka定义了两类副本(领导者副本、追随者副本)。
  • 重平衡:其个消费者实例挂掉后,其他消息者实例自动重新分配的过程。

相关词汇:

消息 - message
分区 - partition
主题 - topic
生产者 - producer
消费者 - consumer
消费者群组 - consumer group
偏移量 - consumer offset
缓存代理 - broker
副本 - replica
领导者副本 - leader replica
追随者副本 - follower replica
重平衡 - rebalance

1-2、Kafka发行版本:

  • Apache Kafak:也称社区版 Kafka。
  • Confluent Kafka:Confluent 公司提供的 Kafka。
    2014年,Kafka的3个创始人 Jay Kreps、Naha Narkhede 和 Jun Rao 离开 LinkedIn,创立了Confluent公司,专注于提供基于 Kafka 的企业流处理解决方案。Confluent Kafka 提供了一些 Apache Kafka 没有的高级特性,如跨数据中心备份、Schema 注册中心以及集群监控工具等。
  • Cloudera/Hortonworks Kafka:Cloudera 提供的 CDH Kafka 和 Hortonworks 提供的 HDP Kafka。集成了 Apache Kafka,通过便捷化的界面操作将 Kafka 的安装、运维、管理、监控全部统一在控制台中。

二、Technology Libraries

2-1、Confluent.Kafka

引用库

<PackageReference Include="Confluent.Kafka" Version="2.8.0" />
const string topic = "test";
const string host = "localhost:9092";

#region 消费者

var t1 = Task.Run(async () =>
 {
	 var cfg = new ConsumerConfig()
	 {
		 GroupId = $"test-consumer-group-console",
		 BootstrapServers = host,
		 AutoOffsetReset = AutoOffsetReset.Earliest,
		 EnableAutoCommit = true  // 自动提交
	 };

	 using var builder = new ConsumerBuilder<Ignore, string>(cfg).Build();
	 builder.Subscribe(topic);

	 var cts = new CancellationTokenSource();
	 Console.CancelKeyPress += (s, e) =>
	 {
		 e.Cancel = true;
		 cts.Cancel();
	 };
	 try
	 {
		 while (true)
		 {
			 try
			 {
				 var consumer = builder.Consume(cts.Token);
				 Console.WriteLine($"Message: {consumer.Message.Value} received from {consumer.TopicPartitionOffset}");

				 // builder.Commit(consumer); // 手动提交
			 }
			 catch (ConsumeException e)
			 {
				 Console.WriteLine($"Error occured: {e.Error.Reason}");
			 }

			 await Task.Yield();
		 }
	 }
	 catch (OperationCanceledException ex)
	 {
		 builder.Close();
		 Console.WriteLine($"ex: {ex}");
	 }
 });

#endregion 消费者

#region 生产者

var t2 = Task.Run(async () =>
{
	var cfg = new ProducerConfig
	{
		BootstrapServers = host,
	};
	var cts = new CancellationTokenSource();
	Console.CancelKeyPress += (s, e) =>
	{
		e.Cancel = true;
		cts.Cancel();
	};

	// 生产者
	using var producer = new ProducerBuilder<Null, string>(cfg).Build();
	while (true)
	{
		if (cts.IsCancellationRequested) break;

		try
		{
			await producer.ProduceAsync(topic, new Message<Null, string>
			{
				Value = JsonConvert.SerializeObject(new KafkaMessage
				{
					Ts = DateTimeOffset.Now,
					Text = new Faker("en").Address.FullAddress()
				})
			});
		}
		catch (ProduceException<Null, KafkaMessage> ex)
		{
			Console.WriteLine($"ex: {ex.Error.Reason}");
		}

		await Task.Delay(TimeSpan.FromMilliseconds(200));
	}
});

#endregion 生产者

Console.ReadLine();
await Task.WhenAll(t1, t2);

2-2、DotNetCore.CAP.Kafka

CAP 是一种处理分布式事务的解决方案,能够保证任务情况下事件消息都不会丢失。可作 EventBus 来使用。

引用库

<PackageReference Include="DotNetCore.CAP.InMemoryStorage" Version="8.3.2" />
<PackageReference Include="DotNetCore.CAP.Kafka" Version="8.3.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
var services = new ServiceCollection();
services.AddLogging(config =>
{
   config.SetMinimumLevel(LogLevel.Debug);
   config.AddConsole();
});

services.AddCap(config =>
{
   config.UseInMemoryStorage();
   config.UseKafka(opt =>
   {
   	opt.Servers = "localhost:9092";
   	
   	// 自定义Headers, cap-msg-id 和 cap-msg-name 必选项
   	opt.CustomHeadersBuilder = (kr, sp) => new List<KeyValuePair<string, string>>
   	{
   		new KeyValuePair<string, string>("cap-msg-id",Guid.NewGuid().ToString()),
   		new KeyValuePair<string, string>("cap-msg-name","test"),
   		new KeyValuePair<string, string>("kafka.offset",kr.Offset.ToString()),
   	};
   });
   config.FailedRetryInterval = 10; // 重试的间隔时间,默认是60s
   config.DefaultGroupName = "test-group";
});

services.AddTransient<ICapSubscribe, SubscriberService>();

var sp = services.BuildServiceProvider();

var cts = new CancellationTokenSource();
Console.CancelKeyPress += (s, e) =>
{
   e.Cancel = true;
   cts.Cancel();
};

// 启动CAP
var bootstrap = sp.GetRequiredService<IBootstrapper>();
await bootstrap.BootstrapAsync(cts.Token);
await Task.Delay(500);

// 生产者
ICapPublisher capBus = sp.GetRequiredService<ICapPublisher>();
while (true)
{
   if (cts.IsCancellationRequested) break;

   // 推送消息
   capBus.Publish("test", new KafkaMessage
   {
   	Ts = DateTimeOffset.Now,
   	Text = new Faker().Address.FullAddress()
   });
   	
   await Task.Delay(TimeSpan.FromMilliseconds(200));
}

await bootstrap.DisposeAsync();
Console.ReadLine();

消费者

internal class SubscriberService : ICapSubscribe
{
   [CapSubscribe("test")]
   public void ReceiveMessage(KafkaMessage msg, [FromCap] CapHeader header)
   {
   	if (msg != null)
   	{
   		Console.WriteLine($"Message: {msg.Ts} | {msg.Text}");
   	}
   }
} 

2-3、MassTransit.Kafka

MassTransit 基于消息驱动的分布式应用框架,可作为分布式应用的消息总线,也可以用作单体应用的事件总线。

引用库

<PackageReference Include="MassTransit.Kafka" Version="8.3.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
var services = new ServiceCollection();

services.AddMassTransit(config =>
{
  const string topic = "test";
  const string groupId = "test-consumer-group";
  const string host = "localhost:9092";

  config.UsingInMemory((context, cfg) => cfg.ConfigureEndpoints(context));

  config.AddRider(rider =>
  {
  	// 注册生产者
  	rider.AddProducer<KafkaMessage>(topic);
  	// 注册消费者
  	rider.AddConsumer<KafkaMessageConsumer>();

  	// Kafka连接配置
  	rider.UsingKafka((context, k) =>
  	{
  		k.Host(host);
  		// 配置 topic
  		k.TopicEndpoint<KafkaMessage>(topic, groupId, e =>
  		{
  			e.AutoOffsetReset = AutoOffsetReset.Earliest;
  			e.ConfigureConsumer<KafkaMessageConsumer>(context);
  			e.CreateIfMissing();
  		});
  	});
  });
});

var sp = services.BuildServiceProvider();

var cts = new CancellationTokenSource();
Console.CancelKeyPress += (s, e) =>
{
  e.Cancel = true;
  cts.Cancel();
};

var bus = sp.GetRequiredService<IBusControl>();
bus.Start(TimeSpan.FromSeconds(10.0));
try
{
  // 生产者
  var producer = sp.GetRequiredService<ITopicProducer<KafkaMessage>>();
  while (true)
  {
  	if (cts.IsCancellationRequested) break;
  	try
  	{
  		// 推送消息
  		string value = new Faker().Address.FullAddress();
  		await producer.Produce(new KafkaMessage
  		{
  			Ts = DateTimeOffset.Now,
  			Text = value
  		});

  		await Task.Delay(TimeSpan.FromMilliseconds(200));
  	}
  	catch (ProduceException<Ignore, KafkaMessage> pex)
  	{
  		Console.WriteLine(pex.Error.Reason);
  	}
  }
}
catch (OperationCanceledException cce)
{
}
finally
{
  await bus.StopAsync();
}

Console.ReadLine();

消费者

internal class KafkaMessageConsumer : IConsumer<KafkaMessage>
{
 public Task Consume(ConsumeContext<KafkaMessage> context)
 {
 	var ctx = (context.ReceiveContext as KafkaReceiveContext<Ignore, KafkaMessage>);
 	Console.WriteLine($"Message: {context.Message.Ts} | {context.Message.Text}, Offset: {ctx?.Offset}");

 	return Task.CompletedTask;
 }
}

2-4、Streamiz.Kafka.Net

Kafka Streams 流式计算

// kafka连接配置
var config = new StreamConfig<StringSerDes, StringSerDes>();
config.ApplicationId = "test-app";
config.BootstrapServers = "localhost:9092";

StreamBuilder builder = new StreamBuilder();

builder.Stream<string, string>("test") // 输入 topic (订阅 topic = test)
	// 定义处理过程
	.MapValues((value, context) =>
	{
		return $"{value} ---- 1111 ---- {context.Offset}";
	})
	// 定义处理过程
	.MapValues((value, context) =>
	{
		return $"{value} ---- 2222 ---- {context.Offset}";
	})
	// 定义处理过程
	// ...
	// 定义处理过程
	.To("test-output");  // 输出 topic (把处理结束发到 topic = test-output)

Topology t = builder.Build();
KafkaStream stream = new KafkaStream(t, config);

await stream.StartAsync();

Console.ReadLine();

stream.Dispose();

输入:a
输出:a ---- 1111 ---- 4910 ---- 2222 ---- 4910

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值