基于 Kafka 0.9 版本 API 实现的 .Net 驱动

Jusfr 原创,文章所用代码已给出,转载请注明来自博客园

Kafka 拥有我们需要的特性
1. 分布式易于水平扩展
2. 可以被不同消费者获取,而不是出队和消失;
3. 基于磁盘的消息存储和常量写入,这意味着极大的消息容量和性能保障

我们的业务一直使用 MSMQ 作为消息中间件,初期确实满足了需求;此后业务增长,我们选择 Kafka 作为替代并基于其 0.9 版本API实现了 .NET 驱动见于 Github 。

已经完成的功能
1. Core api、Group Membership API、Administrative API;
2. Topic-Broker 路由;
3. 基于配置的 Partition-Consumer 路由;

部分设计点
Kafka 是自描述的,用户可以使用空数组发起 TopicMetadata() 请求查看当前的 Broker 与 Topic;

Kafka 集群使用也非常方便,使用 Router 对象()而不是 Connection)连接到任何一台 Broker即可开始工作;

Kafka 认为high-level-api consumer 需要处理 Offset 自动推进和 Partition 均衡,但官方实现是通过 Zookeeper 实现的,本质上不是单纯的客户端编程;

Offset 的自动推进必然有策略问题:每处理一条消息就提交 OffsetCommit 会有性能问题;按条件触发即定时或已处理数量触发而有 Crash 时数据丢失的问题,我们认为官方的 ZookeeperConsumerConnector 的实现非常复杂且掩盖了过多细节, Chuye.Kafka选择交由消费者结合具体业务自主管理。

多 Partition 下消费者选取合适 Partition 进行读写的问题,官方的语言是"A simple consumer client can be implemented by simply requiring that the partitions be specified in config, though this will not allow dynamic reassignment of partitions should that consumer fail. We hope to address this gap in the next major release." Chuye.Kafka 实现了一个甚于配置的路由规则。

Chuye.Kafka 内建Connection 对象的生命周期应与应用生命周期相同,因为其引用了 Socket 池与 Buffer 池,这两者是性能瓶颈所在,你应该使用单例的 Connection;不必担心 Route() 方法创建了新的 Connection 实例——它只是简单替换了 Broker 节点。

使用

项目零依赖,你可以使用 NuGet package PM> Install-Package Chuye.Kafka ,也可以选择自行编译;

示例

$/doc 里包含了两个 LINQPad 脚本 Chuye.Kafka-high-level-api.linq、 Chuye.Kafka-low-level-api.linq,修改为自己的服务器节点即可使用。以下是片断,更多示例及 Group Membership API 见于文档。

Metadata

1     var section = new KafkaConfigurationSection("jusfr.kafka", 9092);
2     //var demoTopics = new String[] { }; // Empty array to grab all metadata
3     var demoTopics = new String[] { "demoTopic" };
5     var connection = new Router(section);
6     connection.TopicMetadata(demoTopics).Dump("Metadata");

Producer

 1     var section = new KafkaConfigurationSection("jusfr.kafka", 9092);
 2     const String demoTopic = "demoTopic";
 4     var producer = new Producer(new Router(section));
 5     producer.Strategy = AcknowlegeStrategy.Written;
 6     const Int32 count = 10;
 7     for (int i = 0; i < count; i++) {
 8         var message = String.Concat(Guid.NewGuid().ToString("n"), "#", i);
 9         producer.Post(demoTopic, message);
10     }

Fetch

1     var section = new KafkaConfigurationSection("jusfr.kafka", 9092);
2     section.Buffer.ResponseBufferSize = 10 * 1024;
3     const String demoTopic = "demoTopic";
5     var consumer = new Consumer(new Router(section));
6     consumer.Fetch(demoTopic, 0).Dump("Fetch");

Offset

1     var section = new KafkaConfigurationSection("jusfr.kafka", 9092);
2     const String demoTopic = "demoTopic";
4     var consumer = new Consumer(new Router(section));
5     var earliest = consumer.Offset(demoTopic, OffsetTimeOption.Earliest);
6     var latest = consumer.Offset(demoTopic, OffsetTimeOption.Latest);
7     new { earliest, latest }.Dump();

OffsetCommit

1     var section = new KafkaConfigurationSection("jusfr.kafka", 9092);
2     const String demoTopic = "demoTopic";
3     const String demoConsumerGroup = "demoConsumerGroup";
4     var consumer = new Consumer(new Router(section));
5     consumer.OffsetCommit(demoTopic, demoConsumerGroup, 6); //your value

OffsetFetch

1     var section = new KafkaConfigurationSection("jusfr.kafka", 9092);
2     const String demoTopic = "demoTopic";
3     const String demoConsumerGroup = "demoConsumerGroup";
4     var consumer = new Consumer(new Connection(section));
5     consumer.OffsetFetch(demoTopic, demoConsumerGroup).Dump("OffsetFetch");

配置

configSections 中添加 <section name="chuye.kafka" type="Chuye.Kafka.KafkaConfigurationSection, Chuye.Kafka" /> 及Borker节点、Buffer、Partition 路由(可选),示例可见于源码 。配置并非必须,你可以构造出 KafkaConfigurationSection 对象传递入 Connection 或者 Router 即可。

 1   <configSections>
 2     <section name="chuye.kafka" type="Chuye.Kafka.KafkaConfigurationSection, Chuye.Kafka" />
 3   </configSections>
 4   <chuye.kafka>
 5     <broker host="jusfr.kafka" port="9092" />
 6     <buffer maxBufferPoolSize="1048576" maxBufferSize="65536" requestBufferSize="4096" responseBufferSize="65536" />
 7     <topicPartitions>
 8       <add topic="demoTopic1" partition="0" />
 9       <add topic="demoTopic2" partition="1" />
10     </topicPartitions>
11   </chuye.kafka>

 

结合 Router对象的使用,根据此配置,客户端对 demoTopic1 的读写将落入分区 0 所在的 broker,对 demoTopic2 的读写将落入分区 1 所在的 broker。

未完成的
1. 消息压缩
2. Partition-Consumer 的负载均衡能力,此功能依赖 Zookeeper API;
3. 对多 Partition 的更有扩展性的处理而不完全依赖于配置;
4. Group Membership API 的完善;
5. 符合 Kafka 语义的 high-level-api consumer
6. ACL

这里作为 Kafka 系列文件的开篇,篇幅及理解限制内容有限,欢迎交流和指正。

Jusfr 原创,文章所用代码已给出,转载请注明来自博客园

你可能感兴趣的:(基于 Kafka 0.9 版本 API 实现的 .Net 驱动)