Window Kafka使用(.Net)

一:环境搭建(windows)

安装jdk1.8,新建环境变量
JAVA_HOME D:\Program_Files\Java\jdk1.8.0_131

1.1 安装zookeeper
下载最新版zookeeper,http://www.apache.org/dyn/closer.cgi/zookeeper/
修改系统环境变量,在Path后添加 ;D:\Program_Files\apache-zookeeper-3.7.0\bin
复制conf\zoo_sample.cfg,重命名zoo.cfg,修改zoo.cfg中的dataDir,并添加一行dataLogDir
dataDir=D:/Program_Files/apache-zookeeper-3.7.0/data
dataLogDir=D:/Program_Files/apache-zookeeper-3.7.0/log

启动zkServer,cmd -> zkServer 

2.2 安装kafka
下载最新版kafka,http://kafka.apache.org/downloads
添加系统环境变量,在Path后添加 ;D:\Program_Files\kafka_2.13-2.8.0\bin\windows

解压到指定路径,如:E:\kafka_2.12-0.10.2.0
修改kafka_2.13-2.8.0\config目录下的server.properties中 log.dirs的值
log.dirs=D:/Program_Files/kafka_2.13-2.8.0/kafka-logs

启动kafka,cmd

d:
cd D:\Program_Files\kafka_2.13-2.8.0
.\bin\windows\kafka-server-start.bat .\config\server.properties

 

 

二:测试
运行cmd命令行,创建一个topic,命令如下:
kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
再打开一个cmd,创建一个Producer,命令如下:
kafka-console-producer.bat --broker-list localhost:9092 --topic test
再打开一个cmd,创建一个Customer,命令如下:
kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic test --from-beginning

 

程序添加Nuget

 

using Confluent.Kafka;
using System;
using System.Collections.Generic;
using System.Threading;

namespace KafKaNet
{
    class Program
    {
        // localhost
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(Consumer));

            Produce();
        }

        /// <summary>
        /// 生产者
        /// </summary>
        public static void Produce()
        {
            var config = new ProducerConfig { BootstrapServers = "localhost:9092" };

            Action<DeliveryReport<Null, string>> handler = r =>
                Console.WriteLine(!r.Error.IsError
                    ? $"Delivered message to {r.TopicPartitionOffset}"
                    : $"Delivery Error: {r.Error.Reason}");


            using (var p = new ProducerBuilder<Null, string>(config).Build())
            {
                try
                {
                    for (var i = 1; i <= 10; i++)
                    {
                        p.Produce("test", new Message<Null, string> { Value = $"my111 message: {i}" }, handler);
                    }

                    p.Flush(TimeSpan.FromSeconds(3)); // 超时时间
                }
                catch (ProduceException<Null, string> e)
                {
                    Console.WriteLine($"Delivery failed: {e.Error.Reason}");
                }
            }
            Console.WriteLine("Done!");
            Console.ReadKey();
        }

        /// <summary>
        /// 消费者
        /// </summary>
        public static void Consumer(object obj)
        {
            var conf = new ConsumerConfig
            {
                GroupId = "test-consumer-group",
                BootstrapServers = "localhost:9092",
                AutoOffsetReset = AutoOffsetReset.Earliest, 
                EnableAutoCommit = false // 设置非自动偏移,业务逻辑完成后手动处理偏移,防止数据丢失
            };

            using (var c = new ConsumerBuilder<Ignore, string>(conf).Build())
            {
                c.Subscribe("test");
                 
                try
                {
                    while (true)
                    {
                        try
                        { 
                            var consume = c.Consume();
                            string receiveMsg = consume.Message.Value;
                            Console.WriteLine($"Consumed message '{receiveMsg}' at: '{consume.TopicPartitionOffset}'.");
                             
                            if (true)
                            {
                                c.Commit(new List<TopicPartitionOffset>() { consume.TopicPartitionOffset }); //手动提交偏移
                            }
                        }
                        catch (ConsumeException e)
                        {
                            Console.WriteLine($"Error occured: {e.Error.Reason}");
                        }
                    }
                }
                catch (OperationCanceledException)
                { 
                    c.Close();
                }
            }
        }
    }
}

 

kafka存在问题

1. 数据丢失

2. 重复消费问题(已经消费了数据,但是offset没来得及提交)

 

消费者选择自动提交还是手动提交

问题一:已经消费的数据对于kakfa来说,会将该消费组里面的offset值进行修改。什么时候去修改的?
当数据被消费者拿去之后就自动提交,而不关注消费者是否成功消费,如果消费者端出现故障,就会导致此条数据没有被消费,继续运行就会导致此数据丢失
如果消费者成功消费此数据,而在提交的过程的出现故障,会导致再次发送此数据到消费者,继续运行就会到此数据重复提交
kafka没有提供一个绝对安全的方案,只提供了多种选择来供用户解决此类问题


问题二:比如消费者拿到数据之后,存入到mysql数据库中。但是mysql数据库这个时候,连接不上,抛出异常。但是在处理的时候已经进行提交了,那么kafka上的offset值已经修改了,可是mysql数据库中没有数据--数据丢失

解决方案:可以将数据在处理完成之后,再进行提交,然后进行offset值的修改。默认情况下是自动提交,需要修改为手动提交。


问题三:如果在处理代码中正常处理了,但是在提交的offset请求时,没有连接到kafka或者出翔了故障,那么该次请求,是失败的。那么下次进行读取同一个分区的数据时,会从已经处理掉的offset值在进行处理一次,那么在hbase中就会产生两条一样的数据,也就是重复提交 

posted @ 2021-06-26 13:11  CHHC  阅读(144)  评论(0编辑  收藏  举报