基于Confluent.Kafka实现的Kafka客户端操作类使用详解
一、引言
有段时间没有写东西了,当然不是没得写,还有MongoDB的系列没有写完呢,那个系列还要继续。今天正好是周末,有点时间,来写新东西吧。最近公司用了Kafka做为消息的中间件,最开始写的那个版本不是很好,我就要来优化它,所以就抽了一些时间来研究Kafka。很多概念性的东西就不写了,今天主要是上干货,主要是代码,今天就把Kafka的消费者和生产者的代码贴出来,以供大家参考,当然这个是代码样板,最后我也会把地址贴出来。以后有时间我会把我自己实现的Kafka消息的生产者和消费者的代码贴出来。好了,话不多说,言归正传。
说明一点,如果想调试这里的代码,必须引入Confluent.Kafka这个dll才可以,直接在Visual Studio 项目的 Nuget 里面可以查找,直接安装就可以了。
二、消息的生产者(Kafka消息的Producer)
大多数的消息中间件都包含三个部分,一个是消息的生产者,一个是存放消息的队列,另外一个就是消息的消费者,我们就按着这个顺序,我就先把消息生产者的代码写出来。直接上代码,其实不是很难,里面有很多备注,只要有基本的概念理解起来还是很容易的。
第一个版本,同步版本!
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using Confluent.Kafka;
using Confluent.Kafka.Serialization; namespace Confluent.Kafka.Examples.Producer
{
public class Program
{
public static void Main(string[] args)
{
if (args.Length != )
{
Console.WriteLine("Usage: .. brokerList topicName");
return;
} string brokerList = args[];
string topicName = args[]; var config = new Dictionary<string, object> { { "bootstrap.servers", brokerList } }; using (var producer = new Producer<string, string>(config, new StringSerializer(Encoding.UTF8), new StringSerializer(Encoding.UTF8)))
{
var cancelled = false;
Console.CancelKeyPress += (_, e) => {
e.Cancel = true; // 阻止进程退出
cancelled = true;
}; while (!cancelled)
{
Console.Write("> "); string text;
try
{
text = Console.ReadLine();
}
catch (IOException)
{
// IO 异常抛出的时候设置此值ConsoleCancelEventArgs.Cancel == true.
break;
}
if (text == null)
{
break;
} string key = null;
string val = text; // 如果指定了键和值,则拆分行.
int index = text.IndexOf(" ");
if (index != -)
{
key = text.Substring(, index);
val = text.Substring(index + );
} // 在下面的异步生产请求上调用.Result会导致它阻塞,直到它完成。 通常,您应该避免同步生成,因为这会对吞吐量产生巨大影响。对于这个交互式控制台的例子,这是我们想要的。
var deliveryReport = producer.ProduceAsync(topicName, key, val).Result;
Console.WriteLine(
deliveryReport.Error.Code == ErrorCode.NoError
? "delivered to: "+deliveryReport.TopicPartitionOffset
: "failed to deliver message: "+deliveryReport.Error.Reason
);
} // 由于我们是同步的生产消息,此时不会有消息在传输并且也不需要等待消息到达的确认应答, 销毁生产者之前我们是不需要调用 producer.Flush 方法, 就像正常使用一样。
}
}
}
}
第二个版本,异步版本,推荐使用
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using Confluent.Kafka;
using Confluent.Kafka.Serialization; namespace Confluent.Kafka.Examples.Producer
{
public class Program
{
public static void Main(string[] args)
{
if (args.Length != )
{
Console.WriteLine("Usage: .. brokerList topicName");
return;
} string brokerList = args[];
string topicName = args[];
string message="我就是要传输的消息内容"; //这是以异步方式生产消息的代码实例
var config = new Dictionary<string, object> { { "bootstrap.servers", brokerList } };
using (var producer = new Producer<Null, string>(config, null, new StringSerializer(Encoding.UTF8)))
{
var deliveryReport = producer.ProduceAsync(topicName, null, message);
deliveryReport.ContinueWith(task =>
{
Console.WriteLine("Producer: "+producer.Name+"\r\nTopic: "+topicName+"\r\nPartition: "+task.Result.Partition+"\r\nOffset: "+task.Result.Offset);
}); producer.Flush(TimeSpan.FromSeconds());
}
}
}
}
好了,上面给出了两个版本的消息生产者的代码,一个是同步版本,第二个是异步版本的,推荐使用异步版本的代码实现。
三、消息的消费者(Kafka消息的Consumer)
在消息的生产者中已经说明了消息中间件的三个部分,第一个是消息的生产者,没有消息的生产者,就没有消息的消费者了,巧妇难为无米之炊吧。在上一节我们已经写了消息生产者的代码,这一节,我们主要来贴出消息消费者的代码。代码同样很简单,注释也很全。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Confluent.Kafka.Serialization; /// <summary>
/// 演示如何使用Consumer客户端.
/// </summary>
namespace Confluent.Kafka.Examples.Consumer
{
public class Program
{
/// <summary>
// 在这个例子中:
/// - offsets 是自动提交的。
/// - consumer.Poll / OnMessage 是用于消息消费的。
/// - 没有为轮询循环创建(Poll)二外的线程,当然可以创建
/// </summary>
public static void Run_Poll(string brokerList, List<string> topics)
{
var config = new Dictionary<string, object>
{
{ "bootstrap.servers", brokerList },
{ "group.id", "csharp-consumer" },
{ "enable.auto.commit", true }, // 默认值
{ "auto.commit.interval.ms", },
{ "statistics.interval.ms", },
{ "session.timeout.ms", },
{ "auto.offset.reset", "smallest" }
}; using (var consumer = new Consumer<Ignore, string>(config, null, new StringDeserializer(Encoding.UTF8)))
{
// 注意: 所有事件处理程序的执行都是在主线程中执行的,就是同步的。 //当成功消费了消息就会触发该事件
consumer.OnMessage += (_, msg) => Console.WriteLine("Topic: "+msg.Topic+" Partition: "+msg.Partition+" Offset: "+msg.Offset+" "+msg.Value); consumer.OnPartitionEOF += (_, end) => Console.WriteLine("Reached end of topic "+end.Topic+" partition "+end.Partition+", next message will be at offset "+end.Offset); //当然发生了严重错误,比如,连接丢失或者Kafka服务器无效就会触发该事件
consumer.OnError += (_, error) => Console.WriteLine("Error: "+error); //当反序列化有错误,或者消费的过程中发生了错误,即error != NoError,就会触发该事件
consumer.OnConsumeError += (_, msg)
=> Console.WriteLine("Error consuming from topic/partition/offset "+msg.Topic+"/"+msg.Partition+"/"+msg.Offset+": "+msg.Error); //成功提交了Offsets会触发该事件
consumer.OnOffsetsCommitted += (_, commit) => Console.WriteLine(commit.Error ? "Failed to commit offsets: "+commit.Error : "Successfully committed offsets: "+commit.Offsets); // 当消费者被分配一组新的分区时触发该事件
consumer.OnPartitionsAssigned += (_, partitions) =>
{
Console.WriteLine("Assigned partitions:"+partitions+" "+member id: "+consumer.MemberId);
// 如果您未向OnPartitionsAssigned事件添加处理程序,则会自动执行以下.Assign调用。 如果你这样做,你必须明确地调用.Assign以便消费者开始消费消息。
//开始从分区中消息消息
consumer.Assign(partitions);
}; // 当消费者的当前分区集已被撤销时引发该事件。
consumer.OnPartitionsRevoked += (_, partitions) =>
{
Console.WriteLine("Revoked partitions:"+partitions);
// 如果您未向OnPartitionsRevoked事件添加处理程序,则下面的.Unassign调用会自动发生。 如果你这样做了,你必须明确地调用.Usessign以便消费者停止从它先前分配的分区中消费消息。 //停止从分区中消费消息
consumer.Unassign();
}; consumer.OnStatistics += (_, json) => Console.WriteLine("Statistics: "+json); consumer.Subscribe(topics); Console.WriteLine("Subscribed to:"+consumer.Subscription); var cancelled = false;
Console.CancelKeyPress += (_, e) => {
e.Cancel = true; // 组织进程退出
cancelled = true;
}; Console.WriteLine("Ctrl-C to exit.");
while (!cancelled)
{
consumer.Poll(TimeSpan.FromMilliseconds());
}
}
} /// <summary>
/// 在这实例中
/// - offsets 是手动提交的。
/// - consumer.Consume方法用于消费消息
/// (所有其他事件仍由事件处理程序处理)
/// -没有为了 轮询(消耗)循环 创建额外的线程。
/// </summary>
public static void Run_Consume(string brokerList, List<string> topics)
{
var config = new Dictionary<string, object>
{
{ "bootstrap.servers", brokerList },
{ "group.id", "csharp-consumer" },
{ "enable.auto.commit", false },
{ "statistics.interval.ms", },
{ "session.timeout.ms", },
{ "auto.offset.reset", "smallest" }
}; using (var consumer = new Consumer<Ignore, string>(config, null, new StringDeserializer(Encoding.UTF8)))
{
// 注意:所有事件处理都是在主线程中处理的,也就是说同步的 consumer.OnPartitionEOF += (_, end)
=> Console.WriteLine("Reached end of topic "+end.Topic+" partition "+end.Partition+", next message will be at offset "+end.Offset); consumer.OnError += (_, error)=> Console.WriteLine("Error: "+error); // 当反序列化有错误,或者消费的过程中发生了错误,即error != NoError,就会触发该事件
consumer.OnConsumeError += (_, error)=> Console.WriteLine("Consume error: "+error); // 当消费者被分配一组新的分区时触发该事件
consumer.OnPartitionsAssigned += (_, partitions) =>
{
Console.WriteLine("Assigned partitions:"+partitions+" "+member id: "+consumer.MemberId);
// 如果您未向OnPartitionsAssigned事件添加处理程序,则会自动执行以下.Assign调用。 如果你这样做,你必须明确地调用.Assign以便消费者开始消费消息。
//开始从分区中消息消息
consumer.Assign(partitions);
}; // 当消费者的当前分区集已被撤销时引发该事件。
consumer.OnPartitionsRevoked += (_, partitions) =>
{
Console.WriteLine("Revoked partitions:"+partitions);
// 如果您未向OnPartitionsRevoked事件添加处理程序,则下面的.Unassign调用会自动发生。 如果你这样做了,你必须明确地调用.Usessign以便消费者停止从它先前分配的分区中消费消息。 //停止从分区中消费消息
consumer.Unassign();
}; consumer.OnStatistics += (_, json) => Console.WriteLine("Statistics: "+json); consumer.Subscribe(topics); Console.WriteLine("Started consumer, Ctrl-C to stop consuming"); var cancelled = false;
Console.CancelKeyPress += (_, e) => {
e.Cancel = true; // 防止进程退出
cancelled = true;
}; while (!cancelled)
{
if (!consumer.Consume(out Message<Ignore, string> msg, TimeSpan.FromMilliseconds()))
{
continue;
} Console.WriteLine("Topic: "+msg.Topic+" Partition: "+msg.Partition+" Offset: "+msg.Offset+" "+msg.Value); if (msg.Offset % == )
{
var committedOffsets = consumer.CommitAsync(msg).Result;
Console.WriteLine("Committed offset: "+committedOffsets);
}
}
}
} /// <summary>
/// 在这个例子中
/// - 消费者组功能(即.Subscribe +offset提交)不被使用。
/// - 将消费者手动分配给分区,并始终从特定偏移量(0)开始消耗。
/// </summary>
public static void Run_ManualAssign(string brokerList, List<string> topics)
{
var config = new Dictionary<string, object>
{
// 即使您不打算使用任何使用者组功能,也必须在创建使用者时指定group.id属性。
{ "group.id", new Guid().ToString() },
{ "bootstrap.servers", brokerList },
// 即使消费者没有订阅该组,也可以将分区偏移量提交给一个组。 在这个例子中,自动提交被禁用以防止发生这种情况。
{ "enable.auto.commit", false }
}; using (var consumer = new Consumer<Ignore, string>(config, null, new StringDeserializer(Encoding.UTF8)))
{
//总是从0开始消费
consumer.Assign(topics.Select(topic => new TopicPartitionOffset(topic, , Offset.Beginning)).ToList()); // 引发严重错误,例如 连接失败或所有Kafka服务器失效。
consumer.OnError += (_, error) => Console.WriteLine("Error: "+error); // 这个事件是由于在反序列化出现错误,或者在消息消息的时候出现错误,也就是 error != NoError 的时候引发该事件
consumer.OnConsumeError += (_, error) => Console.WriteLine("Consume error: "+error); while (true)
{
if (consumer.Consume(out Message<Ignore, string> msg, TimeSpan.FromSeconds()))
{
Console.WriteLine("Topic: "+msg.Topic+" Partition: "+msg.Partition+" Offset: "+msg.Offset+" "+msg.Value);
}
}
}
} private static void PrintUsage()=> Console.WriteLine("Usage: .. <poll|consume|manual> <broker,broker,..> <topic> [topic..]"); public static void Main(string[] args)
{
if (args.Length < )
{
PrintUsage();
return;
} var mode = args[];
var brokerList = args[];
var topics = args.Skip().ToList(); switch (mode)
{
case "poll":
Run_Poll(brokerList, topics);
break;
case "consume":
Run_Consume(brokerList, topics);
break;
case "manual":
Run_ManualAssign(brokerList, topics);
break;
default:
PrintUsage();
break;
}
}
}
}
以上代码也有两个版本,第一个版本是自动提交Offset,第二个版本是人工提交Offset,但是代码没有分开写,只是不同的版本用了不同的方法。
四、结束
好了,今天就写到这里了,这是一个引子,所有代码都是真实有效的,我已经全部测试过,所以大家可以放心使用或者改造成自己的消息的生产者和消息消费者的实现。原文的地址如下,https://github.com/confluentinc/confluent-kafka-dotnet/tree/master/examples ,内容差不多。不忘初心,继续努力吧。
基于Confluent.Kafka实现的Kafka客户端操作类使用详解的更多相关文章
- C# Oracle数据库操作类实例详解
本文所述为C#实现的Oracle数据库操作类,可执行超多常用的Oracle数据库操作,包含了基础数据库连接.关闭连接.输出记录集.执行Sql语句,返回带分页功能的dataset .取表里字段的类型和长 ...
- [转帖]技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解
技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解 http://www.52im.net/thread-1309-1-1.html 本文来自腾讯资深研发工程师罗成的技术分享, ...
- python操作redis用法详解
python操作redis用法详解 转载地址 1.redis连接 redis提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用 ...
- Android中实现java与PHP服务器(基于新浪云免费云平台)http通信详解
Android中实现java与PHP服务器(基于新浪云免费云平台)http通信详解 (本文转自: http://blog.csdn.net/yinhaide/article/details/44756 ...
- [转]使用python来操作redis用法详解
转自:使用python来操作redis用法详解 class CommRedisBase(): def __init__(self): REDIS_CONF = {} connection_pool = ...
- IOS数据库操作SQLite3使用详解(转)
iPhone中支持通过sqlite3来访问iPhone本地的数据库.具体使用方法如下1:添加开发包libsqlite3.0.dylib首先是设置项目文件,在项目中添加iPhone版的sqlite3的数 ...
- C语言对文件的操作函数用法详解2
fopen(打开文件) 相关函数 open,fclose 表头文件 #include<stdio.h> 定义函数 FILE * fopen(const char * path,const ...
- C语言对文件的操作函数用法详解1
在ANSIC中,对文件的操作分为两种方式,即: 流式文件操作 I/O文件操作 一.流式文件操作 这种方式的文件操作有一个重要的结构FILE,FILE在stdio.h中定义如下: typedef str ...
- iBatis——自动生成DAO层接口提供操作函数(详解)
iBatis——自动生成DAO层接口提供操作函数(详解) 在使用iBatis进行持久层管理时,发现在使用DAO层的updateByPrimaryKey.updateByPrimaryKeySelect ...
随机推荐
- hexo发表博文
3.4创建博客文章与发布 在hexo 目录下终端命令: $ hexo new '文件名' //会在source/_posts创建一个文件名.md文件 这就可以使用markdown编辑器开始写自己的博客 ...
- 抢红包js程序
https://www.cnblogs.com/miid/p/5192235.html <!DOCTYPE html> <html> <head> <meta ...
- java中Integer和int的区别(转)
int和Integer的区别 1.Integer是int的包装类,int则是java的一种基本数据类型 2.Integer变量必须实例化后才能使用,而int变量不需要 3.Integer实际是对象的引 ...
- BN和滑动平均
BN目的是使得每层训练的输出结果在同一分布下,实验证明不仅可以加速收敛速度,还可以提高准确度 因为如果想要计算所有图像的均值与方差,显然不太现实,所以每次计算每个batch的方差与均值,为了使得每个b ...
- hdu2669-Romantic-(扩展欧几里得定理)
Romantic Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Su ...
- hdu3189-Just Do It-(埃氏筛+唯一分解定理)
Just Do It Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- pipy国内镜像的网址
pipy国内镜像目前有:豆瓣 http://pypi.douban.com/simple/阿里云 http://mirrors.aliyun.com/pypi/simple/中国科技大学 https: ...
- create-react-app之Invalid Host Header
[create-react-app之Invalid Host Header] 1.When you enable the `proxy` option, you opt into a more str ...
- WPF双向绑定
需求: 思想批量保存数据. 思路: 看了一下MVVM.发现只需要实现前台和后台数据的同步即可.也就是前台的文本框内容变化时后台的对象的属性也要变化就可以了. 参考: http://www.cnblog ...
- 解题(LevenshteinInstance--Levenshtein距离)
题目描述 Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数.许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符.编辑距离 ...