1 可用的Kafka .NET客户端

作为一个.NET Developer,自然想要在.NET项目中集成Kafka实现发布订阅功能。那么,目前可用的Kafka客户端有哪些呢?

目前.NET圈子主流使用的是 Confluent.Kafka

confluent-kafka-dotnet : https://github.com/confluentinc/confluent-kafka-dotnet

其他主流的客户端还有rdkafka-dotnet项目,但是其已经被并入confluent-kakfa-dotnet项目进行维护了。

因此,推荐使用confluent-kafka-dotnet,其配置友好,功能也更全面。

NCC千星项目CAP的Kafka扩展包(DotNetCore.CAP.Kafka)内部也是基于Confluent.Kafka来实现的:

接下来,本文就来在.NET Core项目下通过Confluent.Kafka和CAP两个主流开源项目来操作一下Kafka,实现一下发布订阅的功能。

2 基于Confluent.Kafka的示例

要完成本文示例,首先得有一个启动好的Kafka Broker服务。关于如何搭建Kafka,请参考上一篇:通过Docker部署Kafka集群

安装相关组件

在.NET Core项目中新建一个类库,暂且命名为EDT.Kafka.Core,安装Confluent.Kafka组件:

PM>Install-Package Confluent.Kafka

编写KafkaService

编写IKafkaService接口:

namespace EDT.Kafka.Core
{
public interface IKafkaService
{
Task PublishAsync<T>(string topicName, T message) where T : class; Task SubscribeAsync<T>(IEnumerable<string> topics, Action<T> messageFunc, CancellationToken cancellationToken = default) where T : class;
}
}

编写KafkaService实现类:

namespace EDT.Kafka.Core
{
public class KafkaService : IKafkaService
{
public static string KAFKA_SERVERS = "127.0.0.1:9091"; public async Task PublishAsync<T>(string topicName, T message) where T : class
{
var config = new ProducerConfig
{
BootstrapServers = KAFKA_SERVERS,
BatchSize = 16384, // 修改批次大小为16K
LingerMs = 20 // 修改等待时间为20ms
};
using (var producer = new ProducerBuilder<string, string>(config).Build())
{
await producer.ProduceAsync(topicName, new Message<string, string>
{
Key = Guid.NewGuid().ToString(),
Value = JsonConvert.SerializeObject(message)
}); ;
}
} public async Task SubscribeAsync<T>(IEnumerable<string> topics, Action<T> messageFunc, CancellationToken cancellationToken = default) where T : class
{
var config = new ConsumerConfig
{
BootstrapServers = KAFKA_SERVERS,
GroupId = "Consumer",
EnableAutoCommit = false, // 禁止AutoCommit
Acks = Acks.Leader, // 假设只需要Leader响应即可
AutoOffsetReset = AutoOffsetReset.Earliest // 从最早的开始消费起
};
using (var consumer = new ConsumerBuilder<Ignore, string>(config).Build())
{
consumer.Subscribe(topics);
try
{
while (true)
{
try
{
var consumeResult = consumer.Consume(cancellationToken);
Console.WriteLine($"Consumed message '{consumeResult.Message?.Value}' at: '{consumeResult?.TopicPartitionOffset}'.");
if (consumeResult.IsPartitionEOF)
{
Console.WriteLine($" - {DateTime.Now:yyyy-MM-dd HH:mm:ss} 已经到底了:{consumeResult.Topic}, partition {consumeResult.Partition}, offset {consumeResult.Offset}.");
continue;
}
T messageResult = null;
try
{
messageResult = JsonConvert.DeserializeObject<T>(consumeResult.Message.Value);
}
catch (Exception ex)
{
var errorMessage = $" - {DateTime.Now:yyyy-MM-dd HH:mm:ss}【Exception 消息反序列化失败,Value:{consumeResult.Message.Value}】 :{ex.StackTrace?.ToString()}";
Console.WriteLine(errorMessage);
messageResult = null;
}
if (messageResult != null/* && consumeResult.Offset % commitPeriod == 0*/)
{
messageFunc(messageResult);
try
{
consumer.Commit(consumeResult);
}
catch (KafkaException e)
{
Console.WriteLine(e.Message);
}
}
}
catch (ConsumeException e)
{
Console.WriteLine($"Consume error: {e.Error.Reason}");
}
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Closing consumer.");
consumer.Close();
}
} await Task.CompletedTask;
}
}
}

为了方便后续的演示,在此项目中再创建一个类 EventData:

public class EventData
{
public string TopicName { get; set; } public string Message { get; set; } public DateTime EventTime { get; set; }
}

编写Producer

新建一个Console项目,暂且命名为:EDT.Kafka.Demo.Producer,其主体内容如下:

namespace EDT.Kafka.Demo.Producer
{
public class Program
{
static async Task Main(string[] args)
{
KafkaService.KAFKA_SERVERS = "kafka1:9091,kafka2:9092,kafka3:9093";
var kafkaService = new KafkaService();
for (int i = 0; i < 50; i++)
{
var eventData = new EventData
{
TopicName = "testtopic",
Message = $"This is a message from Producer, Index : {i + 1}",
EventTime = DateTime.Now
};
await kafkaService.PublishAsync(eventData.TopicName, eventData);
}
Console.WriteLine("Publish Done!");
Console.ReadKey();
}
}
}

编写Consumer

新建一个Console项目,暂且命名为:EDT.Kafka.Demo.Consumer,其主体内容如下:

namespace EDT.Kafka.Demo.Consumer
{
public class Program
{
static async Task Main(string[] args)
{
KafkaService.KAFKA_SERVERS = "kafka1:9091,kafka2:9092,kafka3:9093";
var kafkaService = new KafkaService();
var topics = new List<string> { "testtopic" };
await kafkaService.SubscribeAsync<EventData>(topics, (eventData) =>
{
Console.WriteLine($" - {eventData.EventTime: yyyy-MM-dd HH:mm:ss} 【{eventData.TopicName}】- > 已处理");
});
}
}
}

测试Pub/Sub效果

将Producer和Consumer两个项目都启动起来,可以看到当Consumer消费完50条消息并一一确认之后,Producer这边就算发布结束。

3 基于DotNetCore.CAP的示例

模拟场景说明

假设我们有两个微服务,一个是Catalog微服务,一个是Basket微服务,当Catalog微服务产生了Product价格更新的事件,就会将其发布到Kafka,Basket微服务作为消费者就会订阅这个消息然后更新购物车中对应商品的最新价格。

Catalog API

新建一个ASP.NET Core WebAPI项目,然后分别安装以下组件:

PM>Install Package DotNetCore.CAP
PM>Install Package DotNetCore.CAP.MongoDB
PM>Install Package DotNetCore.CAP.Kafka

在Startup中的ConfigureServices方法中注入CAP:

public void ConfigureServices(IServiceCollection services)
{ ......
services.AddCap(x =>
{
x.UseMongoDB("mongodb://account:password@mongodb-server:27017/products?authSource=admin");
x.UseKafka("kafka1:9091,kafka2:9092,kafka3:9093");
});
}

新建一个ProductController,实现一个Update产品价格的接口,在其中通过CapPublisher完成发布消息到Kafka:

namespace EDT.Demo.Catalog.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class ProductController : ControllerBase
{
private static readonly IList<Product> Products = new List<Product>
{
new Product { Id = "0001", Name = "电动牙刷A", Price = 99.90M, Introduction = "暂无介绍" },
new Product { Id = "0002", Name = "电动牙刷B", Price = 199.90M, Introduction = "暂无介绍" },
new Product { Id = "0003", Name = "洗衣机A", Price = 2999.90M, Introduction = "暂无介绍" },
new Product { Id = "0004", Name = "洗衣机B", Price = 3999.90M, Introduction = "暂无介绍" },
new Product { Id = "0005", Name = "电视机A", Price = 1899.90M, Introduction = "暂无介绍" },
}; private readonly ICapPublisher _publisher;
private readonly IMapper _mapper; public ProductController(ICapPublisher publisher, IMapper mapper)
{
_publisher = publisher;
_mapper = mapper;
} [HttpGet]
public IList<ProductDTO> Get()
{
return _mapper.Map<IList<ProductDTO>>(Products); ;
} [HttpPut]
public async Task<IActionResult> UpdatePrice(string id, decimal newPrice)
{
// 业务代码
var product = Products.FirstOrDefault(p => p.Id == id);
product.Price = newPrice; // 发布消息
await _publisher.PublishAsync("ProductPriceChanged",
new ProductDTO { Id = product.Id, Name = product.Name, Price = product.Price}); return NoContent();
}
}
}

Basket API

参照Catalog API项目创建ASP.NET Core WebAPI项目,并安装对应组件,在ConfigureServices方法中注入CAP。新建一个BasketController,用于订阅Kafka对应Topic:ProductPriceChanged 的消息。

namespace EDT.Demo.Basket.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class BasketController : ControllerBase
{
private static readonly IList<MyBasketDTO> Baskets = new List<MyBasketDTO>
{
new MyBasketDTO { UserId = "U001", Catalogs = new List<Catalog>
{
new Catalog { Product = new ProductDTO { Id = "0001", Name = "电动牙刷A", Price = 99.90M }, Count = 2 },
new Catalog { Product = new ProductDTO { Id = "0005", Name = "电视机A", Price = 1899.90M }, Count = 1 },
}
},
new MyBasketDTO { UserId = "U002", Catalogs = new List<Catalog>
{
new Catalog { Product = new ProductDTO { Id = "0002", Name = "电动牙刷B", Price = 199.90M }, Count = 2 },
new Catalog { Product = new ProductDTO { Id = "0004", Name = "洗衣机B", Price = 3999.90M }, Count = 1 },
}
}
}; [HttpGet]
public IList<MyBasketDTO> Get()
{
return Baskets;
} [NonAction]
[CapSubscribe("ProductPriceChanged")]
public async Task RefreshBasketProductPrice(ProductDTO productDTO)
{
if (productDTO == null)
return; foreach (var basket in Baskets)
{
foreach (var catalog in basket.Catalogs)
{
if (catalog.Product.Id == productDTO.Id)
{
catalog.Product.Price = productDTO.Price;
break;
}
}
} await Task.CompletedTask;
}
}
}

测试效果

同时启动Catalog API 和 Basket API两个项目。首先,通过Swagger在Basket API中查看所有用户购物车中的商品的价格,可以看到,0002的商品是199.9元。

然后,通过Swagger在Catalog API中更新Id为0002的商品的价格至499.9元。

最后,通过Swagger在Basket API中查看所有用户购物车中的商品的价格,可以看到,0002的商品已更新至499.9元。

4 总结

本文总结了.NET Core如何通过对应客户端操作Kafka,基于Confluent.Kafka项目和CAP项目可以方便的实现发布订阅的效果。

参考资料

阿星Plus,《.NET Core下使用Kafka

麦比乌斯皇,《.NET使用Kafka小结

极客时间,胡夕《Kafka核心技术与实战》

B站,尚硅谷《Kafka 3.x入门到精通教程》

作者:周旭龙

出处:https://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

Kafka入门实战教程(3).NET Core操作Kafka的更多相关文章

  1. Kafka入门实战教程(7):Kafka Streams

    1 关于流处理 流处理平台(Streaming Systems)是处理无限数据集(Unbounded Dataset)的数据处理引擎,而流处理是与批处理(Batch Processing)相对应的.所 ...

  2. 转 Kafka入门经典教程

    Kafka入门经典教程 http://www.aboutyun.com/thread-12882-1-1.html 问题导读 1.Kafka独特设计在什么地方?2.Kafka如何搭建及创建topic. ...

  3. Kafka入门经典教程

      本帖最后由 desehawk 于 2015-5-3 00:45 编辑问题导读 1.Kafka独特设计在什么地方?2.Kafka如何搭建及创建topic.发送消息.消费消息?3.如何书写Kafka程 ...

  4. Kafka入门经典教程【转】

    问题导读 1.Kafka独特设计在什么地方?2.Kafka如何搭建及创建topic.发送消息.消费消息?3.如何书写Kafka程序?4.数据传输的事务定义有哪三种?5.Kafka判断一个节点是否活着有 ...

  5. [入门帮助] Kafka入门经典教程

    问题导读 1.Kafka独特设计在什么地方?2.Kafka如何搭建及创建topic.发送消息.消费消息?3.如何书写Kafka程序?4.数据传输的事务定义有哪三种?5.Kafka判断一个节点是否活着有 ...

  6. ZooKeeper入门实战教程(一)-介绍与核心概念

    1.ZooKeeper介绍与核心概念1.1 简介ZooKeeper最为主要的使用场景,是作为分布式系统的分布式协同服务.在学习zookeeper之前,先要对分布式系统的概念有所了解,否则你将完全不知道 ...

  7. Kafka学习之(四)PHP操作Kafka

    简单测试 环境:Centos6.4,PHP7,kafka服务器IP:192.168.9.154,PHP服务器:192.168.9.157 在192.168.9.157创建目录和文件. //生产者 &l ...

  8. 《OD大数据实战》Kafka入门实例

    官网: 参考文档: Kafka入门经典教程 Kafka工作原理详解 一.安装zookeeper 1. 下载zookeeper-3.4.5-cdh5.3.6.tar.gz 下载地址为: http://a ...

  9. Kafka入门宝典(详细截图版)

    1.了解 Apache Kafka 1.1.简介 官网:http://kafka.apache.org/ Apache Kafka 是一个开源消息系统,由Scala 写成.是由Apache 软件基金会 ...

  10. kafka实战教程(python操作kafka),kafka配置文件详解

    kafka实战教程(python操作kafka),kafka配置文件详解 应用往Kafka写数据的原因有很多:用户行为分析.日志存储.异步通信等.多样化的使用场景带来了多样化的需求:消息是否能丢失?是 ...

随机推荐

  1. Linux脚本-自动ping网址列表

    背景 公司某一项业务需要管理多种类硬件,有一些硬件的管理功能没有实现前台展示,检测和硬件之间的网络连接状况需要通过ping每个ip地址来单独实现.在需要大规模调试网络的时候,每个硬件单独ping就显得 ...

  2. Visual Studio 2017 导出 ASP.NET Core 项目模版项目文件为空

    问题重现 VS 2017 针对 ASP.NET Core 导出模版功能有问题 解决办法 visual-studio-2017-templates-and-the-missing-content 目前官 ...

  3. 《HarmonyOS Next开发进阶:打造功能完备的Todo应用华章》

    章节 6:日期选择器与日期处理 目标 学习如何使用DatePicker组件. 理解日期格式化和日期计算. 内容 日期选择器基础 使用DatePicker组件. 处理日期选择事件. 日期格式化 格式化日 ...

  4. Browser-use:基于 Python 的智能浏览器自动化 AI 工具调研与实战

    Browser-use:基于 Python 的智能浏览器自动化 AI 工具调研与实战 一.概述 Browser-use 是一个旨在将 AI "智能体"(Agents)与真实浏览器进 ...

  5. C中输入输出

    引入一个概念,对于计算机来说,外来数据都是输入,经过计算机处理的结果并进行显示的就是输出.在linux里面,一切都是文件,就连输入输出,都可以划归到"文件"一类,而为了管理这些文件 ...

  6. 让 LLM 来评判 | 技巧与提示

    这是 让 LLM 来评判 系列文章的第六篇,敬请关注系列文章: 基础概念 选择 LLM 评估模型 设计你自己的评估 prompt 评估你的评估结果 奖励模型相关内容 技巧与提示 LLM 评估模型已知偏 ...

  7. Chrome谷歌浏览器常用快捷键、开发技巧

    谷歌浏览器作为常用的开发工具,熟悉常用的快捷键,不仅方便快捷,也能间接提高不少工作效率.以下是谷歌浏览器常用快捷键和开发技巧. 标签页和窗口快捷键 1. Ctrl + n 打开新窗口 2. Ctrl ...

  8. 加减法计算在RB中的应用(比如计算库存)(should be equal as integers指令的使用)

    订单测试过程中,对库存的校验是很关键的步骤 下面这个案例即实现对订单前后库存检查.公式计算.结果匹配,输出测试结果.具体脚本如下图 步骤如下: 1.获取订单前的库存 2.订单流程 3.获取订单后的库存 ...

  9. LeetCode 热题 100

    1. 两数之和 1. 两数之和 class Solution { public int[] twoSum(int[] nums, int target) { int n = nums.length; ...

  10. 利用Edge浏览器扩展获取账号密码等敏感性信息

    免责声明:本文所涉及的技术仅供学习和参考,严禁使用本文内容从事违法行为和未授权行为,如因个人原因造成不良后果,均由使用者本人负责,作者及本博客不承担任何责任. 前言 edge扩展作为edge浏览器丰富 ...