kafka 相信都有听说过,不管有没有用过,在江湖上可以说是大名鼎鼎,就像天龙八部里的乔峰。国际惯例,先介绍生平事迹   简介

Kafka 是由 Apache软件基金会 开发的一个开源流处理平台,由 Scala Java 编写。Kafka是一种高吞吐量的 分布式 ,支持分区(partition),多副本(replica)的 发布订阅消息系统 。与其他MQ最大不同是Topic 具有分区(Partition)的概念,消息出队的速度也比其他MQ快。

 

特性及适用场景
  • 高吞吐量、低延迟
  • 可扩展性:集群支持热扩展
  • 持久性、可靠性
  • 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
  • 高并发:支持数千个客户端同时读写
常用场景
  • 日志收集
  • 消息系统:生产者和消费者、缓存消息等。
  • 用户活动跟踪:流网页、搜索、点击等活动
  • 运营指标
  • 工作流处理
  • 对实时性要求不高的数据处理

 

 

 

 

Kafka基础概念

 

Topic

Kafka 中可将消息分类,每一类的消息称为一个 Topic(主题),消费者可以对不同的 Topic 进行不同的处理。Topic相当于传统消息系统MQ中的一个队列queue,producer端发送的message必须指定是发送到哪个topic,但是不需要指定topic下的哪个partition,因为kafka会把收到的message进行load balance,均匀的分布在这个topic下的不同的partition上

Broker

每个 Broker(代理) 即一个 Kafka 服务实例,多个 Broker 构成一个 Kafka 集群,生产者发布的消息将保存在 Broker 中,消费者将从 Broker 中拉取消息进行消费。

 

producer

生产者

consumer

消费者

Partition

分区,Kafka 中比较特色的部分,一个 Topic 可以分为多个 Partition,每个 Partition 是一个有序的队列,Partition 中的每条消息都存在一个有序的偏移量(Offest) ,同一个 Consumer Group 中,只有一个 Consumer 实例可消费某个 Partition 的消息。

 

持久化

Kafka会把消息持久化到本地文件系统中每个 Topic 将消息分成多 Partition,每个 Partition 在存储层面是 append log 文件。任何发布到此 Partition 的消息都会被直接追加到 log 文件的尾部,每条消息在文件中的位置称为 Offest(偏移量),Partition 是以文件的形式存储在文件系统中,log 文件根据 Broker 中的配置保留一定时间后删除来释放磁盘空间。

由于message的写入持久化是顺序写入的,因此message在被消费的时候也是按顺序被消费的,保证partition的message是顺序消费的。

 

 

看到上面的一堆特性,巴拉巴拉,一顿吹,道理我都懂,怎么操作,还是没看到效果。

别急,接下来就上代码,这个是不能少的。保证你们拿去就能用

 

 

上代码,demo测试 先创建两个接口,写好基础类库,后面直接应用就行了,我这里就直接放一起了
 /// <summary>
    /// 消费者
    /// </summary>
    public interface IKafkaConsumer : IDisposable
    {
        /// <summary>
        /// 消费数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        T Consume<T>() where T : class;
    }

  public interface IKafkaProducer : IDisposable
    {
        /// <summary>
        /// 发布消息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="operateType"></param>
        /// <returns></returns>
        bool Produce<T>(string key, T data, int operateType) where T : class;
    }

  实现方法

using Confluent.Kafka;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;

namespace Kafka
{
    public class KafkaConsumer : IKafkaConsumer
    {
        private bool disposeHasBeenCalled = false;
        private readonly object disposeHasBeenCalledLockObj = new object();

        private readonly IConsumer<string, string> _consumer;

        /// <summary>
        /// 构造函数,初始化配置
        /// </summary>
        /// <param name="config">配置参数</param>
        /// <param name="topic">主题名称</param>
        public KafkaConsumer(ConsumerConfig config, string topic)
        {
            _consumer = new ConsumerBuilder<string, string>(config).Build();

            _consumer.Subscribe(topic);
        }

        /// <summary>
        /// 消费
        /// </summary>
        /// <returns></returns>
        public T Consume<T>() where T : class
        {
            try
            {
                var result = _consumer.Consume(TimeSpan.FromSeconds(1));
                if (result != null)
                {
                    if (typeof(T) == typeof(string))
                        return (T)Convert.ChangeType(result.Value, typeof(T));

                    return JsonConvert.DeserializeObject<T>(result.Value);
                }
            }
            catch (ConsumeException e)
            {
                Console.WriteLine($"consume error: {e.Error.Reason}");
            }
            catch (Exception e)
            {
                Console.WriteLine($"consume error: {e.Message}");
            }

            return default;
        }

        /// <summary>
        /// 释放
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            lock (disposeHasBeenCalledLockObj)
            {
                if (disposeHasBeenCalled) { return; }
                disposeHasBeenCalled = true;
            }

            if (disposing)
            {
                _consumer?.Close();
            }
        }
    }

}

  

 public class KafkaProducer : IKafkaProducer
    {
        private bool disposeHasBeenCalled = false;
        private readonly object disposeHasBeenCalledLockObj = new object();

        private readonly IProducer<string, string> _producer;
        private readonly string _topic;

        /// <summary>
        /// 构造函数,初始化配置
        /// </summary>
        /// <param name="config">配置参数</param>
        /// <param name="topic">主题名称</param>
        public KafkaProducer(ProducerConfig config, string topic)
        {
            _producer = new ProducerBuilder<string, string>(config).Build();
            _topic = topic;
        }

        /// <summary>
        /// 发布消息
        /// </summary>
        /// <typeparam name="T">数据实体</typeparam>
        /// <param name="key">数据key,partition分区会根据key</param>
        /// <param name="data">数据</param>
        /// <param name="operateType">操作类型[增、删、改等不同类型]</param>
        /// <returns></returns>
        public bool Produce<T>(string key, T data, int operateType) where T : class
        {
            var obj = JsonConvert.SerializeObject(new
            {
                Type = operateType,
                Data = data
            });

            try
            {
                var result = _producer.ProduceAsync(_topic, new Message<string, string>
                {
                    Key = key,
                    Value = obj
                }).ConfigureAwait(false).GetAwaiter().GetResult();

#if DEBUG

                Console.WriteLine($"Topic: {result.Topic} Partition: {result.Partition} Offset: {result.Offset}");
#endif
                return true;

            }
            catch (ProduceException<string, string> e)
            {
                Console.WriteLine($"Delivery failed: {e.Error.Reason}");
            }
            catch (Exception e)
            {
                Console.WriteLine($"Delivery failed: {e.Message}");
            }

            return false;
        }

        /// <summary>
        /// 释放
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            lock (disposeHasBeenCalledLockObj)
            {
                if (disposeHasBeenCalled) { return; }
                disposeHasBeenCalled = true;
            }

            if (disposing)
            {
                _producer?.Dispose();
            }
        }
    }

  

再写两个测试方法,一个发送消息,一个接收消息,控制台就好 注意  kafka 通过 topic 来接收消息 new KafkaProducer(config, "topic-c"))  发送方和接收方的topic要一致
 static void Main(string[] args)
        {
            var config = new ProducerConfig
            {
                BootstrapServers = "localhost:9092",
                Acks = Acks.All
            };
             //发送消息
        
            using (var kafkaProducer = new KafkaProducer(config, "topic-d"))
            {
                var result = kafkaProducer.Produce<object>("a", new { name = "猪八戒3" }, 1);

            }
            Console.WriteLine("消息发送成功");
        }        








static void Main(string[] args)
        {
            var config = new ConsumerConfig
            {
                BootstrapServers = "localhost:9092",
                GroupId = "test",
                AutoOffsetReset = AutoOffsetReset.Earliest
            };

            string text;
            Console.WriteLine("接受中......");
            while ((text = Console.ReadLine()) != "q")
            {
               //接受消息
                using (var kafkaProducer = new KafkaConsumer(config, "topic-d"))
                {
                    var result = kafkaProducer.Consume<object>();
                    if (result != null)
                    {
                        Console.WriteLine(result.ToString());
                    }

                }
            }

        }

 上结果、

 

 

可以看到,消息已经收到了。这个demo里,消费端要一直处于正常状态才行,才能消费生产者得信息

    

本文版权归作者和博客园共有,来源网址:欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。