kafka - Confluent.Kafka
上个章节我们讲了kafka的环境安装(这里),现在主要来了解下Kafka使用,基于.net实现kafka的消息队列应用,本文用的是Confluent.Kafka,版本0.11.6
1、安装:
在NuGet程序包中搜索“Confluent.Kafka”下载安装即可
2、producer发送消息:
using System;
using System.Collections.Generic;
using System.Text;
using Confluent.Kafka;
using Confluent.Kafka.Serialization; namespace KafKa
{
/// <summary>
/// Kafka消息生产者
/// </summary>
public sealed class KafkaProducer
{
/// <summary>
/// 生产消息并发送消息
/// </summary>
/// <param name="broker">kafka的服务器地址</param>
/// <param name="topic">kafka的消息主题名称</param>
/// <param name="partion">分区</param>
/// <param name="message">需要传送的消息</param>
public bool Produce(string broker, string topic, int partion, string message)
{
bool result = false;
if (string.IsNullOrEmpty(broker) || string.IsNullOrWhiteSpace(broker) || broker.Length <= )
{
throw new ArgumentNullException("Kafka消息服务器地址不能为空!");
} if (string.IsNullOrEmpty(topic) || string.IsNullOrWhiteSpace(topic) || topic.Length <= )
{
throw new ArgumentNullException("消息所属的主题不能为空!");
} if (string.IsNullOrEmpty(message) || string.IsNullOrWhiteSpace(message) || message.Length <= )
{
throw new ArgumentNullException("消息内容不能为空!");
} var config = new Dictionary<string, object>
{
{ "bootstrap.servers", broker }
};
using (var producer = new Producer<Null, string>(config, null, new StringSerializer(Encoding.UTF8)))
{
var deliveryReport = producer.ProduceAsync(topic, null, message, partion);
deliveryReport.ContinueWith(task =>
{
if (task.Result.Error.Code == ErrorCode.NoError)
{
result = true;
}
//可以在控制台使用以下语句
//Console.WriteLine("Producer:" + producer.Name + "\r\nTopic:" + topic + "\r\nPartition:" + task.Result.Partition + "\r\nOffset:" + task.Result.Offset + "\r\nMessage:" + task.Result.Value);
}); producer.Flush(TimeSpan.FromSeconds());
}
return result;
}
}
}
3、consumer接收消息:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Confluent.Kafka;
using Confluent.Kafka.Serialization; namespace KafKa
{
/// <summary>
/// Kafka消息消费者
/// </summary>
public sealed class KafkaConsumer
{
#region 私有字段 private bool isCancelled; #endregion #region 构造函数 /// <summary>
/// 构造函数,初始化IsCancelled属性
/// </summary>
public KafkaConsumer()
{
isCancelled = false;
} #endregion #region 属性 /// <summary>
/// 是否应该取消继续消费Kafka的消息,默认值是false,继续消费消息
/// </summary>
public bool IsCancelled
{
get { return isCancelled; }
set { isCancelled = value; }
} #endregion #region 同步版本 /// <summary>
/// 指定的组别的消费者开始消费指定主题的消息
/// </summary>
/// <param name="broker">Kafka消息服务器的地址</param>
/// <param name="topic">Kafka消息所属的主题</param>
/// <param name="groupID">Kafka消费者所属的组别</param>
/// <param name="action">可以对已经消费的消息进行相关处理</param>
public void Consume(string broker, string topic, string groupID, Action<ConsumerResult> action = null)
{
if (string.IsNullOrEmpty(broker) || string.IsNullOrWhiteSpace(broker) || broker.Length <= )
{
throw new ArgumentNullException("Kafka消息服务器的地址不能为空!");
} if (string.IsNullOrEmpty(topic) || string.IsNullOrWhiteSpace(topic) || topic.Length <= )
{
throw new ArgumentNullException("消息所属的主题不能为空!");
} if (string.IsNullOrEmpty(groupID) || string.IsNullOrWhiteSpace(groupID) || groupID.Length <= )
{
throw new ArgumentNullException("用户分组ID不能为空!");
} var config = new Dictionary<string, object>
{
{ "bootstrap.servers", broker },
{ "group.id", groupID },
{ "enable.auto.commit", true }, // this is the default
{ "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)))
{
if (action != null)
{
consumer.OnMessage += (_, message) => {
ConsumerResult messageResult = new ConsumerResult();
messageResult.Broker = broker;
messageResult.Topic = message.Topic;
messageResult.Partition = message.Partition;
messageResult.Offset = message.Offset.Value;
messageResult.Message = message.Value; //执行外界自定义的方法
action(messageResult);
};
} 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); //引发反序列化错误或消费消息出现错误!= NoError。
consumer.OnConsumeError += (_, message) => Console.WriteLine("Error consuming from topic/partition/offset " + message.Topic + "/" + message.Partition + "/" + message.Offset + ": " + message.Error); 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);
}; // Raised when the consumer's current assignment set has been revoked.
//当消费者的当前任务集已被撤销时引发。
consumer.OnPartitionsRevoked += (_, partitions) =>
{
Console.WriteLine("Revoked Partitions:" + partitions);
// If you don't add a handler to the OnPartitionsRevoked event,the below .Unassign call happens automatically. If you do, you must call .Unassign explicitly in order for the consumer to stop consuming messages from it's previously assigned partitions.
//如果您未向OnPartitionsRevoked事件添加处理程序,则下面的.Unassign调用会自动发生。 如果你为它增加了事件处理程序,你必须明确地调用.Usessign以便消费者停止从它先前分配的分区中消费消息。
consumer.Unassign();
}; //consumer.OnStatistics += (_, json) => Console.WriteLine("Statistics: " + json); consumer.Subscribe(topic); //Console.WriteLine("Subscribed to:" + consumer.Subscription); while (!IsCancelled)
{
consumer.Poll(TimeSpan.FromMilliseconds());
}
}
} #endregion #region 异步版本 /// <summary>
/// 指定的组别的消费者开始消费指定主题的消息
/// </summary>
/// <param name="broker">Kafka消息服务器的地址</param>
/// <param name="topic">Kafka消息所属的主题</param>
/// <param name="groupID">Kafka消费者所属的组别</param>
/// <param name="action">可以对已经消费的消息进行相关处理</param>
public void ConsumeAsync(string broker, string topic, string groupID, Action<ConsumerResult> action = null)
{
if (string.IsNullOrEmpty(broker) || string.IsNullOrWhiteSpace(broker) || broker.Length <= )
{
throw new ArgumentNullException("Kafka消息服务器的地址不能为空!");
} if (string.IsNullOrEmpty(topic) || string.IsNullOrWhiteSpace(topic) || topic.Length <= )
{
throw new ArgumentNullException("消息所属的主题不能为空!");
} if (string.IsNullOrEmpty(groupID) || string.IsNullOrWhiteSpace(groupID) || groupID.Length <= )
{
throw new ArgumentNullException("用户分组ID不能为空!");
} ThreadPool.QueueUserWorkItem(KafkaAutoCommittedOffsets, new ConsumerSetting() { Broker = broker, Topic = topic, GroupID = groupID, Action = action });
} #endregion #region 两种提交Offsets的版本 /// <summary>
/// Kafka消息队列服务器自动提交offset
/// </summary>
/// <param name="state">消息消费者信息</param>
private void KafkaAutoCommittedOffsets(object state)
{
ConsumerSetting setting = state as ConsumerSetting; var config = new Dictionary<string, object>
{
{ "bootstrap.servers", setting.Broker },
{ "group.id", setting.GroupID },
{ "enable.auto.commit", true }, // this is the default
{ "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)))
{
if (setting.Action != null)
{
consumer.OnMessage += (_, message) =>
{
ConsumerResult messageResult = new ConsumerResult();
messageResult.Broker = setting.Broker;
messageResult.Topic = message.Topic;
messageResult.Partition = message.Partition;
messageResult.Offset = message.Offset.Value;
messageResult.Message = message.Value; //执行外界自定义的方法
setting.Action(messageResult);
};
} //consumer.OnStatistics += (_, json)=> Console.WriteLine("Statistics: {json}"); //可以写日志
//consumer.OnError += (_, error)=> Console.WriteLine("Error:"+error); //可以写日志
//consumer.OnConsumeError += (_, msg) => Console.WriteLine("Error consuming from topic/partition/offset {msg.Topic}/{msg.Partition}/{msg.Offset}: {msg.Error}"); consumer.Subscribe(setting.Topic); while (!IsCancelled)
{
consumer.Poll(TimeSpan.FromMilliseconds());
}
}
} /// <summary>
/// Kafka消息队列服务器手动提交offset
/// </summary>
/// <param name="state">消息消费者信息</param>
private void KafkaManuallyCommittedOffsets(object state)
{
ConsumerSetting setting = state as ConsumerSetting; var config = new Dictionary<string, object>
{
{ "bootstrap.servers", setting.Broker },
{ "group.id", setting.GroupID },
{ "enable.auto.commit", false },//不是自动提交的
{ "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.OnError += (_, error) => Console.WriteLine("Error:"+error); //可以写日志
// Raised on deserialization errors or when a consumed message has an error != NoError.
//consumer.OnConsumeError += (_, error)=> Console.WriteLine("Consume error:"+error); consumer.Subscribe(setting.Topic); Message<Ignore, string> message = null; while (!isCancelled)
{
if (!consumer.Consume(out message, TimeSpan.FromMilliseconds()))
{
continue;
} if (setting.Action != null)
{
ConsumerResult messageResult = new ConsumerResult();
messageResult.Broker = setting.Broker;
messageResult.Topic = message.Topic;
messageResult.Partition = message.Partition;
messageResult.Offset = message.Offset.Value;
messageResult.Message = message.Value; //执行外界自定义的方法
setting.Action(messageResult);
} if (message.Offset % == )
{
var committedOffsets = consumer.CommitAsync(message).Result;
//Console.WriteLine("Committed offset:"+committedOffsets);
}
}
}
} #endregion
}
}
4、新建Producer控制台发送消息
using System;
using KafKa; namespace ConsoleProducer
{
class Program
{
static void Main(string[] args)
{
while (true)
{
var message = Console.ReadLine();
var producer = new KafkaProducer();
producer.Produce("localhost:9092", "test", , message);
} Console.ReadKey();
}
}
}
5、新建Consumer控制台接收消息
using System;
using System.Collections.Generic;
using KafKa; namespace ConsoleConsumer
{
class Program
{
static void Main(string[] args)
{
var dts = new List<TimeSpan>(); var consumer = new KafkaConsumer();
consumer.ConsumeAsync("localhost:9092", "test", "", result =>
{
Console.WriteLine(result.Message);
}); Console.ReadKey();
}
}
}
通过以上步骤运行producer控制台,发送消息回车,在consumer控制台就可以接收到消息了。
那么我们如何通过多个consumer来消费消息呢,kafka默认采用的是range分配方法,即平均分配分区。首先注意在创建topic的命令行时创建多个分区(--partitions 5),这里我们创建了5个分区,在发送消息时选择不同的分区发送(0-5),打开5个consumer控制台(注意要同一个分组),我们会发现5个consumer会分别消费对应分区的消息
kafka - Confluent.Kafka的更多相关文章
- DataPipeline联合Confluent Kafka Meetup上海站
Confluent作为国际数据“流”处理技术领先者,提供实时数据处理解决方案,在市场上拥有大量企业客户,帮助企业轻松访问各类数据.DataPipeline作为国内首家原生支持Kafka解决方案的“iP ...
- 基于Confluent.Kafka实现的Kafka客户端操作类使用详解
一.引言 有段时间没有写东西了,当然不是没得写,还有MongoDB的系列没有写完呢,那个系列还要继续.今天正好是周末,有点时间,来写新东西吧.最近公司用了Kafka做为消息的中间件,最开始写的那个版本 ...
- Kafka Confluent
今天我们要讲的大数据公司叫作Confluent,这个公司是前LinkedIn员工出来后联合创办的,而创业的基础是一款叫作Apache Kafka的开源软件. Confluen联合创始人Jun Rao即 ...
- c# .net 使用Confluent.Kafka针对kafka进行生产和消费
首先说明一点,像Confluent.Kafka这种开源的组件,三天两头的更新.在搜索引擎搜索到的结果往往用不了,浪费时间.建议以后遇到类似的情况直接看官网给的Demo. 因为搜索引擎搜到的文章,作者基 ...
- python confluent kafka客户端配置kerberos认证
kafka的认证方式一般有如下3种: 1. SASL/GSSAPI 从版本0.9.0.0开始支持 2. SASL/PLAIN 从版本0.10.0.0开始支持 3. SASL/SCRAM-SHA- ...
- Apache Kafka安全| Kafka的需求和组成部分
1.目标 - 卡夫卡安全 今天,在这个Kafka教程中,我们将看到Apache Kafka Security 的概念 .Kafka Security教程包括我们需要安全性的原因,详细介绍加密.有了这 ...
- CentOS 7部署Kafka和Kafka集群
CentOS 7部署Kafka和Kafka集群 注意事项 需要启动多个shell脚本交互客户端进行验证,运行中的客户端不要停止. 准备工作: 安装java并设置java环境变量,在`/etc/prof ...
- Kafka(3)--kafka消息的存储及Partition副本原理
消息的存储原理: 消息的文件存储机制: 前面我们知道了一个 topic 的多个 partition 在物理磁盘上的保存路径,那么我们再来分析日志的存储方式.通过 [root@localhost ~]# ...
- Kafka记录-Kafka简介与单机部署测试
1.Kafka简介 kafka-分布式发布-订阅消息系统,开发语言-Scala,协议-仿AMQP,不支持事务,支持集群,支持负载均衡,支持zk动态扩容 2.Kafka的架构组件 1.话题(Topic) ...
随机推荐
- 【原创】大叔经验分享(85)ssh秘钥之创建和使用
一 创建秘钥 1 Macbook $ ssh-keygen -t rsaGenerating public/private rsa key pair.Enter file in which to sa ...
- VisualSVN 关于权限(第1篇)
总结权限的规则: 1.子目录权限完全覆盖父目录权限.以子目录的权限为最终.仓库本身就是祖宗,所有的子目录继承他的权限,所有仓库本身必须增加可访问权限,要么everyone ,要么增加全部团队成员. 2 ...
- js 获取input type="file" 选择的文件大小、文件名称、上次修改时间、类型等信息
文件名的传递 ---全路径获取 $('#file').change(function(){ $('#em').text($('#file').val()); }); 文件名的传递 ---只获取文件名 ...
- Tornado目录
第一篇:白话tornado源码之一个脚本引发的血案 第二篇:白话tornado源码之待请求阶段 第三篇:白话tornado源码之请求来了 第四篇:白话tornado源码之褪去模板外衣的前戏 第五篇:白 ...
- jade继承
代码的复用是jade非常突出的一个设计目标,jade不仅仅通过mixin来复用代码,他在文件的组织能力上也非常突出,说白了,就是继承和包涵 block定义的方式和调用的方式 block desc ...
- linux tcp server bind
如果不是系统管理员: bind()函数 返回失败
- 大数据之路week06--day01(VMware的下载与安装、安装CentOS)
好了,从今天开始就开始正式的进入大数据道路的轨道上了,当然了,Java 也是需要不断地在日后进行反复地学习,熟练掌握.(这里我要说一下,Java种还有一些I/O流.Lambda表达式和一些常用工具类有 ...
- 注册表操作 Microsoft.Win32.Registry与RegistryKey类
一.注册表操作简介 Registry 类,RegistryKey 类提供了操作注册表的接口 RegistryValueKind:用于指定操作注册表的数据类型 一.注册表巢 在注册表中,最上面的节点是注 ...
- browsersync简单使用和原理分析
1. 静态文件模式 browser-sync start --server --files "css/*.css" "*.html" 2. 代理模式 brows ...
- list交集
在C#语言程序设计中,List集合是常用的集合数据类型,在涉及集合类型的运算中,有时候我们需要计算2个List集合中共有的数据,即对2个List集合求交集运算.此时可以使用C#语言提供的Interse ...