• kafka


    上个章节我们讲了kafka的环境安装(这里),现在主要来了解下Kafka使用,基于.net实现kafka的消息队列应用,本文用的是Confluent.Kafka,版本0.11.6

    1、安装:

    在NuGet程序包中搜索“Confluent.Kafka”下载安装即可

    2、producer发送消息:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Text;
     4 using Confluent.Kafka;
     5 using Confluent.Kafka.Serialization;
     6 
     7 namespace KafKa
     8 {
     9     /// <summary>
    10     /// Kafka消息生产者
    11     /// </summary>
    12     public sealed class KafkaProducer
    13     {
    14         /// <summary>
    15         /// 生产消息并发送消息
    16         /// </summary>
    17         /// <param name="broker">kafka的服务器地址</param>
    18         /// <param name="topic">kafka的消息主题名称</param>
    19         /// <param name="partion">分区</param>
    20         /// <param name="message">需要传送的消息</param>
    21         public bool Produce(string broker, string topic, int partion, string message)
    22         {
    23             bool result = false;
    24             if (string.IsNullOrEmpty(broker) || string.IsNullOrWhiteSpace(broker) || broker.Length <= 0)
    25             {
    26                 throw new ArgumentNullException("Kafka消息服务器地址不能为空!");
    27             }
    28 
    29             if (string.IsNullOrEmpty(topic) || string.IsNullOrWhiteSpace(topic) || topic.Length <= 0)
    30             {
    31                 throw new ArgumentNullException("消息所属的主题不能为空!");
    32             }
    33 
    34             if (string.IsNullOrEmpty(message) || string.IsNullOrWhiteSpace(message) || message.Length <= 0)
    35             {
    36                 throw new ArgumentNullException("消息内容不能为空!");
    37             }
    38 
    39             var config = new Dictionary<string, object>
    40             {
    41                 { "bootstrap.servers", broker }
    42             };
    43             using (var producer = new Producer<Null, string>(config, null, new StringSerializer(Encoding.UTF8)))
    44             {
    45                 var deliveryReport = producer.ProduceAsync(topic, null, message, partion);
    46                 deliveryReport.ContinueWith(task =>
    47                 {
    48                     if (task.Result.Error.Code == ErrorCode.NoError)
    49                     {
    50                         result = true;
    51                     }
    52                     //可以在控制台使用以下语句
    53                     //Console.WriteLine("Producer:" + producer.Name + "
    Topic:" + topic + "
    Partition:" + task.Result.Partition + "
    Offset:" + task.Result.Offset + "
    Message:" + task.Result.Value);
    54                 });
    55 
    56                 producer.Flush(TimeSpan.FromSeconds(10));
    57             }
    58             return result;
    59         }
    60     }
    61 }
    View Code

    3、consumer接收消息:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Text;
      4 using System.Threading;
      5 using Confluent.Kafka;
      6 using Confluent.Kafka.Serialization;
      7 
      8 namespace KafKa
      9 {
     10     /// <summary>
     11     /// Kafka消息消费者
     12     /// </summary>
     13     public sealed class KafkaConsumer
     14     {
     15         #region 私有字段
     16 
     17         private bool isCancelled;
     18 
     19         #endregion
     20 
     21         #region 构造函数
     22 
     23         /// <summary>
     24         /// 构造函数,初始化IsCancelled属性
     25         /// </summary>
     26         public KafkaConsumer()
     27         {
     28             isCancelled = false;
     29         }
     30 
     31         #endregion
     32 
     33         #region 属性
     34 
     35         /// <summary>
     36         /// 是否应该取消继续消费Kafka的消息,默认值是false,继续消费消息
     37         /// </summary>
     38         public bool IsCancelled
     39         {
     40             get { return isCancelled; }
     41             set { isCancelled = value; }
     42         }
     43 
     44         #endregion
     45 
     46         #region 同步版本
     47 
     48         /// <summary>
     49         /// 指定的组别的消费者开始消费指定主题的消息
     50         /// </summary>
     51         /// <param name="broker">Kafka消息服务器的地址</param>
     52         /// <param name="topic">Kafka消息所属的主题</param>
     53         /// <param name="groupID">Kafka消费者所属的组别</param>
     54         /// <param name="action">可以对已经消费的消息进行相关处理</param>
     55         public void Consume(string broker, string topic, string groupID, Action<ConsumerResult> action = null)
     56         {
     57             if (string.IsNullOrEmpty(broker) || string.IsNullOrWhiteSpace(broker) || broker.Length <= 0)
     58             {
     59                 throw new ArgumentNullException("Kafka消息服务器的地址不能为空!");
     60             }
     61 
     62             if (string.IsNullOrEmpty(topic) || string.IsNullOrWhiteSpace(topic) || topic.Length <= 0)
     63             {
     64                 throw new ArgumentNullException("消息所属的主题不能为空!");
     65             }
     66 
     67             if (string.IsNullOrEmpty(groupID) || string.IsNullOrWhiteSpace(groupID) || groupID.Length <= 0)
     68             {
     69                 throw new ArgumentNullException("用户分组ID不能为空!");
     70             }
     71 
     72             var config = new Dictionary<string, object>
     73                 {
     74                     { "bootstrap.servers", broker },
     75                     { "group.id", groupID },
     76                     { "enable.auto.commit", true },  // this is the default
     77                     { "auto.commit.interval.ms", 5000 },
     78                     { "statistics.interval.ms", 60000 },
     79                     { "session.timeout.ms", 6000 },
     80                     { "auto.offset.reset", "smallest" }
     81                 };
     82 
     83 
     84             using (var consumer = new Consumer<Ignore, string>(config, null, new StringDeserializer(Encoding.UTF8)))
     85             {
     86                 if (action != null)
     87                 {
     88                     consumer.OnMessage += (_, message) => {
     89                         ConsumerResult messageResult = new ConsumerResult();
     90                         messageResult.Broker = broker;
     91                         messageResult.Topic = message.Topic;
     92                         messageResult.Partition = message.Partition;
     93                         messageResult.Offset = message.Offset.Value;
     94                         messageResult.Message = message.Value;
     95 
     96                         //执行外界自定义的方法
     97                         action(messageResult);
     98                     };
     99                 }
    100 
    101                 consumer.OnPartitionEOF += (_, end) => Console.WriteLine("Reached end of topic " + end.Topic + " partition " + end.Partition + ", next message will be at offset " + end.Offset);
    102 
    103                 consumer.OnError += (_, error) => Console.WriteLine("Error:" + error);
    104 
    105                 //引发反序列化错误或消费消息出现错误!= NoError。
    106                 consumer.OnConsumeError += (_, message) => Console.WriteLine("Error consuming from topic/partition/offset " + message.Topic + "/" + message.Partition + "/" + message.Offset + ": " + message.Error);
    107 
    108                 consumer.OnOffsetsCommitted += (_, commit) => Console.WriteLine(commit.Error ? "Failed to commit offsets:" + commit.Error : "Successfully committed offsets:" + commit.Offsets);
    109 
    110                 // 当消费者被分配一组新的分区时引发。
    111                 consumer.OnPartitionsAssigned += (_, partitions) =>
    112                 {
    113                     Console.WriteLine("Assigned Partitions:" + partitions + ", Member ID:" + consumer.MemberId);
    114                     //如果您未向OnPartitionsAssigned事件添加处理程序,则会自动执行以下.Assign调用。 如果你为它添加了事件处理程序,你必须明确地调用.Assign以便消费者开始消费消息。
    115                     consumer.Assign(partitions);
    116                 };
    117 
    118                 // Raised when the consumer's current assignment set has been revoked.
    119                 //当消费者的当前任务集已被撤销时引发。
    120                 consumer.OnPartitionsRevoked += (_, partitions) =>
    121                 {
    122                     Console.WriteLine("Revoked Partitions:" + partitions);
    123                     // 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.
    124                     //如果您未向OnPartitionsRevoked事件添加处理程序,则下面的.Unassign调用会自动发生。 如果你为它增加了事件处理程序,你必须明确地调用.Usessign以便消费者停止从它先前分配的分区中消费消息。
    125                     consumer.Unassign();
    126                 };
    127 
    128                 //consumer.OnStatistics += (_, json) => Console.WriteLine("Statistics: " + json);
    129 
    130                 consumer.Subscribe(topic);
    131 
    132                 //Console.WriteLine("Subscribed to:" + consumer.Subscription);
    133 
    134                 while (!IsCancelled)
    135                 {
    136                     consumer.Poll(TimeSpan.FromMilliseconds(100));
    137                 }
    138             }
    139         }
    140 
    141         #endregion
    142 
    143         #region 异步版本
    144 
    145         /// <summary>
    146         /// 指定的组别的消费者开始消费指定主题的消息
    147         /// </summary>
    148         /// <param name="broker">Kafka消息服务器的地址</param>
    149         /// <param name="topic">Kafka消息所属的主题</param>
    150         /// <param name="groupID">Kafka消费者所属的组别</param>
    151         /// <param name="action">可以对已经消费的消息进行相关处理</param>
    152         public void ConsumeAsync(string broker, string topic, string groupID, Action<ConsumerResult> action = null)
    153         {
    154             if (string.IsNullOrEmpty(broker) || string.IsNullOrWhiteSpace(broker) || broker.Length <= 0)
    155             {
    156                 throw new ArgumentNullException("Kafka消息服务器的地址不能为空!");
    157             }
    158 
    159             if (string.IsNullOrEmpty(topic) || string.IsNullOrWhiteSpace(topic) || topic.Length <= 0)
    160             {
    161                 throw new ArgumentNullException("消息所属的主题不能为空!");
    162             }
    163 
    164             if (string.IsNullOrEmpty(groupID) || string.IsNullOrWhiteSpace(groupID) || groupID.Length <= 0)
    165             {
    166                 throw new ArgumentNullException("用户分组ID不能为空!");
    167             }
    168 
    169             ThreadPool.QueueUserWorkItem(KafkaAutoCommittedOffsets, new ConsumerSetting() { Broker = broker, Topic = topic, GroupID = groupID, Action = action });
    170         }
    171 
    172         #endregion
    173 
    174         #region 两种提交Offsets的版本
    175 
    176         /// <summary>
    177         /// Kafka消息队列服务器自动提交offset
    178         /// </summary>
    179         /// <param name="state">消息消费者信息</param>
    180         private void KafkaAutoCommittedOffsets(object state)
    181         {
    182             ConsumerSetting setting = state as ConsumerSetting;
    183 
    184             var config = new Dictionary<string, object>
    185                 {
    186                     { "bootstrap.servers", setting.Broker },
    187                     { "group.id", setting.GroupID },
    188                     { "enable.auto.commit", true },  // this is the default
    189                     { "auto.commit.interval.ms", 5000 },
    190                     { "statistics.interval.ms", 60000 },
    191                     { "session.timeout.ms", 6000 },
    192                     { "auto.offset.reset", "smallest" }
    193                 };
    194 
    195             using (var consumer = new Consumer<Ignore, string>(config, null, new StringDeserializer(Encoding.UTF8)))
    196             {
    197                 if (setting.Action != null)
    198                 {
    199                     consumer.OnMessage += (_, message) =>
    200                     {
    201                         ConsumerResult messageResult = new ConsumerResult();
    202                         messageResult.Broker = setting.Broker;
    203                         messageResult.Topic = message.Topic;
    204                         messageResult.Partition = message.Partition;
    205                         messageResult.Offset = message.Offset.Value;
    206                         messageResult.Message = message.Value;
    207 
    208                         //执行外界自定义的方法
    209                         setting.Action(messageResult);
    210                     };
    211                 }
    212 
    213                 //consumer.OnStatistics += (_, json)=> Console.WriteLine("Statistics: {json}");
    214 
    215                 //可以写日志
    216                 //consumer.OnError += (_, error)=> Console.WriteLine("Error:"+error);
    217 
    218                 //可以写日志
    219                 //consumer.OnConsumeError += (_, msg) => Console.WriteLine("Error consuming from topic/partition/offset {msg.Topic}/{msg.Partition}/{msg.Offset}: {msg.Error}");
    220 
    221                 consumer.Subscribe(setting.Topic);
    222 
    223                 while (!IsCancelled)
    224                 {
    225                     consumer.Poll(TimeSpan.FromMilliseconds(100));
    226                 }
    227             }
    228         }
    229 
    230         /// <summary>
    231         /// Kafka消息队列服务器手动提交offset
    232         /// </summary>
    233         /// <param name="state">消息消费者信息</param>
    234         private void KafkaManuallyCommittedOffsets(object state)
    235         {
    236             ConsumerSetting setting = state as ConsumerSetting;
    237 
    238             var config = new Dictionary<string, object>
    239                 {
    240                     { "bootstrap.servers", setting.Broker },
    241                     { "group.id", setting.GroupID },
    242                     { "enable.auto.commit", false },//不是自动提交的
    243                     { "auto.commit.interval.ms", 5000 },
    244                     { "statistics.interval.ms", 60000 },
    245                     { "session.timeout.ms", 6000 },
    246                     { "auto.offset.reset", "smallest" }
    247                 };
    248 
    249             using (var consumer = new Consumer<Ignore, string>(config, null, new StringDeserializer(Encoding.UTF8)))
    250             {
    251                 //可以写日志
    252                 //consumer.OnError += (_, error) => Console.WriteLine("Error:"+error);
    253 
    254                 //可以写日志
    255                 // Raised on deserialization errors or when a consumed message has an error != NoError.
    256                 //consumer.OnConsumeError += (_, error)=> Console.WriteLine("Consume error:"+error);
    257 
    258                 consumer.Subscribe(setting.Topic);
    259 
    260                 Message<Ignore, string> message = null;
    261 
    262                 while (!isCancelled)
    263                 {
    264                     if (!consumer.Consume(out message, TimeSpan.FromMilliseconds(100)))
    265                     {
    266                         continue;
    267                     }
    268 
    269                     if (setting.Action != null)
    270                     {
    271                         ConsumerResult messageResult = new ConsumerResult();
    272                         messageResult.Broker = setting.Broker;
    273                         messageResult.Topic = message.Topic;
    274                         messageResult.Partition = message.Partition;
    275                         messageResult.Offset = message.Offset.Value;
    276                         messageResult.Message = message.Value;
    277 
    278                         //执行外界自定义的方法
    279                         setting.Action(messageResult);
    280                     }
    281 
    282                     if (message.Offset % 5 == 0)
    283                     {
    284                         var committedOffsets = consumer.CommitAsync(message).Result;
    285                         //Console.WriteLine("Committed offset:"+committedOffsets);
    286                     }
    287                 }
    288             }
    289         }
    290 
    291         #endregion
    292     }
    293 }
    View Code

    4、新建Producer控制台发送消息

     1 using System;
     2 using KafKa;
     3 
     4 namespace ConsoleProducer
     5 {
     6     class Program
     7     {
     8         static void Main(string[] args)
     9         {
    10             while (true)
    11             {
    12                 var message = Console.ReadLine();
    13                 var producer = new KafkaProducer();
    14                 producer.Produce("localhost:9092", "test", 0, message);
    15             }
    16 
    17             Console.ReadKey();
    18         }
    19     }
    20 }
    View Code

      

    5、新建Consumer控制台接收消息

     1 using System;
     2 using System.Collections.Generic;
     3 using KafKa;
     4 
     5 namespace ConsoleConsumer
     6 {
     7     class Program
     8     {
     9         static void Main(string[] args)
    10         {
    11             var dts = new List<TimeSpan>();
    12 
    13             var consumer = new KafkaConsumer();
    14             consumer.ConsumeAsync("localhost:9092", "test", "0", result =>
    15               {
    16                   Console.WriteLine(result.Message);
    17               });
    18 
    19             Console.ReadKey();
    20         }
    21     }
    22 }
    View Code

    通过以上步骤运行producer控制台,发送消息回车,在consumer控制台就可以接收到消息了。

    那么我们如何通过多个consumer来消费消息呢,kafka默认采用的是range分配方法,即平均分配分区。首先注意在创建topic的命令行时创建多个分区(--partitions 5),这里我们创建了5个分区,在发送消息时选择不同的分区发送(0-5),打开5个consumer控制台(注意要同一个分组),我们会发现5个consumer会分别消费对应分区的消息

  • 相关阅读:
    【科创人上海行】扶墙老师王福强:架构师创业要突破思维局限,技术人创业的三种模式,健康第一
    【科创人·独家】连续创业者高春辉的这六年:高强度投入打造全球领先的IP数据库
    中国确实需要大力扩充核武器
    SAP MM 可以通过STO在公司间转移质检库存?
    SAP MM 如何看一个采购申请是由APO系统创建后同步过来的?
    SAP MM 如何看一个Inbound Delivery单据相关的IDoc?
    SAP ECC & APO集成
    SAP MM 采购订单收货之后自动形成分包商库存?
    SAP MM 带有'Return'标记的STO,不能创建内向交货单?
    SAP MM 没有启用QM的前提下可以从QI库存里退货给Vendor?
  • 原文地址:https://www.cnblogs.com/xxinwen/p/10687636.html
Copyright © 2020-2023  润新知