前言
- kafka中文教程地址
https://www.orchome.com/kafka/index
一、kafka核心概念
- 概念
Kafka是消息队列。简称:MQ。MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。其中较为成熟的MQ产品有IBM WEBSPHERE MQ等等…
二、kafka应用场景
在微服务系统中,微服务之间通信,主要是通过Http或者gRPC通信。由于http/gRPC通信方式是同步通信,如果遇到了高并发,同步通信就会导致微服务系统性能瓶颈,所以,为了解决微服务性能瓶颈问题。需要将同步通信换成异步通信方式。因此。就选用使用消息队列。
消息队列的代表技术,就是Kafka。
三、kafka微服务落地
- 条件
- jdk
- kafka 版本:2.8.1
- kafkatool_64bit.exe
- 百度网盘下载地址
链接:https://pan.baidu.com/s/10S8YP0rQT3CCIa2_dQ_Rcw
提取码:joyx
- 百度网盘下载地址
- webapi Demo项目
- 技术选型
- RabbitMQ 适用于消息数据在10G以内
- kafka 适用于用在海量消息场景中[数据达到TP级]
- 步骤
-
安装JDK
-
安装kafkatool_64bit工具
-
配置
#在kafka根目录下的[/config/zookeeper.properties] dataDir=/tmp/zookeeper clientPort=2181 maxClientCnxns=0 admin.enableServer=false -
运行zookeeper [注册中心]
#在kafka根目录下的[/bin/windows]文件下启动zookeeper: zookeeper-server-start.bat ../../config/zookeeper.properties运行结果如下:

-
运行kafka
- 配置文件
#在kafka根目录下的[/config/server.properties]配置持久化消息的文件 log.dirs=/tmp/kafka-logs #其他配置为默认即可 - 运行 kafka
运行结果如下:#在kafka根目录下的[/bin/windows]文件下启动kafka: kafka-server-start.bat ../../config/server.properties

- 配置文件
-
项目配置
- nuget安装
Confluent.Kafka - 核心代码
-
生产者代码
var producerConfig = new ProducerConfig { BootstrapServers = "127.0.0.1:9092", MessageTimeoutMs = 50000, //失败重试 EnableIdempotence = true }; var builder = new ProducerBuilder<string, string>(producerConfig); using (var producer = builder.Build()) { try { //数据对象 var data = new { name = "小明", sex = "男", old = 12 }; string dataJson = JsonConvert.SerializeObject(data); //参数1:队列名称 //参数2:消息数据 var dr = producer.ProduceAsync("QueueName", new Message<string, string> { Key = "data", Value = dataJson }).GetAwaiter().GetResult(); Console.WriteLine("------------消息发送成功!----------------"); } catch (ProduceException<string, string> ex) { Console.WriteLine($"消息发送失败!;data:" + ex.Error.Reason); } } -
算法
//在生产者中设置算法
builder.SetDefaultPartitioner([函数名称\委托]]);- 轮询算法
static int requestCount = 0;
public Partition RoundRobinPartitioner(string tipic,int partitionCount,ReadOnlySpan<byte>keyData,bool keyIsNull)
{
int partition = requestCount%partitionCount;
requestCount++;
return new Partition(partition);
}
- 轮询算法
-
创建分区
public async Task CreatePartition(string topic,int partitionCount)
{
AdminClientConfig adminClientConfig = new AdminClientConfig(){
BootstrapServers = “127.0.0.1:9092”
};
var build = new AdminClientBuild(adminClientConfig).Build();build.CreatePartitionsAsync(new PartitionsSpecification[]{ new PartitionsSpecification(){Topic = topic,IncreaseTo = partitionCount} }).Wait(); await Task.CompletedTask; } -
消费者代码
-
备注
建议使用关系型数据库存储分区、偏移量和主题名称,以下代码使用的是redis存储的便偏移量private IDistributedCache distributedCache; public 构造函数(IDistributedCache _distributedCache) { distributedCache = _distributedCache; } //消费者代码 Console.WriteLine("------------消息消费中--------"); //偏移量定义 Offset offset = 0; Task.Run(() => { // 创建主题代码未加 CreateTopic("QueueName"); var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "temp", //消息确认 true:自动确认 false:手动确认 //手动确认 EnableAutoCommit = true }; var builder = new ConsumerBuilder<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); //从Redis中获取偏移量 string offset = distributedCache.GetString("QueueName"); if (string.IsNullOrEmpty(offset)) { offset = "0"; } 重置偏移量 TopicPartitionOffset对象参数:1、分区对象[队列明名称,分区] 3、偏移量 consumer.Assign(new TopicPartitionOffset(new TopicPartition("QueueName",0),int.Parse(offset))); while (true) { try { //消费后 确认消息 var result = consumer.Consume(); //存储偏移量 distributedCache.SetString("QueueName",result.Offset.Value.ToString()); //业务 string key = result.Key; string value = result.Value; Console.WriteLine($"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); } catch (Exception ex) { Console.WriteLine($"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine();
-
-
创建主题
public async Task CreateTopic(string topicName) { AdminClientConfig adminClientConfig = new AdminClientConfig(){ BootstrapServers = "127.0.0.1:9092" }; var build = new AdminClientBuild(adminClientConfig).Build(); build.CreateTopicsAsync(new TopicSpecification[]{ new TopicSpecification(){Name = topicName [主题名称]} }).Wait(); await Task.CompletedTask; }
-
- nuget安装
-
- 配置属性含义
属性含义 数据类型 备注 类型 BootstrapServers string kafka连接字符串地址 生产者 消费者 MessageTimeoutMs int 超时时间 生产者 GroupId string 组ID 消费者 EnableAutoCommit bool true:自动确认 false:手动确认 消费者 EnableIdempotence bool 失败重试 true:重试 false:不重试 生产者
四、kafka核心功能落地
-
手动确认消息
- 场景
当消费者端消费消息后,业务逻辑执行失败了,消息需要使用手动确认。
如果使用自动确认,当消费者端消费消息后,自动去kafka确认消息,不管业务的成功与失败,这样会导致消息丢失。 - 生产者
- 实例代码
var producerConfig = new ProducerConfig { BootstrapServers = "127.0.0.1:9092", MessageTimeoutMs = 50000 }; var builder = new ProducerBuilder\<string, string>(producerConfig); using (var producer = builder.Build()) { try { //数据对象 var data = new { name = "小明", sex = "男", old = 12 }; string dataJson = JsonConvert.SerializeObject(data); var dr = producer.ProduceAsync("QueueName", new Message\<string, string> { Key = "data", Value = dataJson }).GetAwaiter().GetResult(); Console.WriteLine("------------消息发送成功!----------------"); } catch (ProduceException\<string, string> ex) { Console.WriteLine(\$"消息发送失败!;data:" + ex.Error.Reason); } }
- 实例代码
- 消费者
- 代码
var consumerConfig = new ConsumerConfig { .......... //消息确认 true:自动确认 false:手动确认 //自动确认的 EnableAutoCommit = false }; * 实例代码 Console.WriteLine("------------消息消费中--------"); Task.Run(() => { var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "temp", //消息确认 true:自动确认 false:手动确认 //自动确认的 EnableAutoCommit = false }; var builder = new ConsumerBuilder\<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); while (true) { try { //消费后 确认消息 var result = consumer.Consume(); //业务 string key = result.Key; string value = result.Value; Console.WriteLine(`$"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); //手动提交 consumer.StoreOffset(result); } catch (Exception ex) { Console.WriteLine($`"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine();
- 代码
- 场景
-
手动确认消息–偏移量[重复消费]
- 什么是偏移量
偏移量就是消息的序号。 - 偏移量生成过程
生产者第一次发送一个消息到kafka,kafka会给这个消息生成一个序号后,kafka再将消息发送给消费者端,消费者端收到消息消费后通过Commit或者StoreOffset函数提交给给kafka,kafka收到后给当前消息的序号加一在生成一个序号,再次等待生产者端第二次发送消息到kafka将消息放在新的序号里面,kafka再将消息发送给消费者端,消费者端收到消息消费后通过Commit或者StoreOffset函数提交给给kafka,kafka收到后给当前消息的序号加一在生成一个序号,以此类推… - 场景
如果消费者端消息消费后,业务也执行成功,准备使用Commit或者StoreOffset函数提交给kafka,kafka突然宕机了,无法在当前的序号[比如:当前序号 1]加一,重启kafka后会造成[当前序号为1]的消息会重复消费。 - 解决方案 [示例代码中未实现]
- 将当前的偏移量记录下来,然后重置偏移量,如果使用重置偏移量的方式来解决消息重复消费的问题,可以不用使用手动提交,直接改为自动提交即可。
- 使用Redis的方式来存储
- 缺陷
无法保证业务与数据库同时成功。- 方案
使用数据库来存储偏移量[核心:通过数据库事务来操作。]。
- 方案
- 缺陷
- 生产者
- 实例代码
var producerConfig = new ProducerConfig { BootstrapServers = "127.0.0.1:9092", MessageTimeoutMs = 50000 }; var builder = new ProducerBuilder\<string, string>(producerConfig); using (var producer = builder.Build()) { try { //数据对象 var data = new { name = "小明", sex = "男", old = 12 }; string dataJson = JsonConvert.SerializeObject(data);0 var dr = producer.ProduceAsync("QueueName", new Message\<string, string> { Key = "data", Value = dataJson }).GetAwaiter().GetResult(); Console.WriteLine("------------消息发送成功!----------------"); } catch (ProduceException\<string, string> ex) { Console.WriteLine(\$"消息发送失败!;data:" + ex.Error.Reason); } }
- 实例代码
- 消费者
- 实例代码
- Nuget包
Microsoft.Extensions.Caching.Redis - 在Startup类中的ConfigureServices函数中注册Redis服务
services.AddDistributedRedisCache(options=>{ options.Configuration = "localhost:6379" }); - 控制代码
private IDistributedCache distributedCache; public 构造函数(IDistributedCache \_distributedCache) { distributedCache = \_distributedCache; } //消费者代码 Console.WriteLine("------------消息消费中--------"); //偏移量定义 Offset offset = 0; Task.Run(() => { var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "temp", //消息确认 true:自动确认 false:手动确认 //手动确认 EnableAutoCommit = true }; var builder = new ConsumerBuilder<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); //从Redis中获取偏移量 string offset = distributedCache.GetString("QueueName"); if (string.IsNullOrEmpty(offset)) { offset = "0"; } //重置偏移量 //TopicPartitionOffset对象参数:1、分区对象[队列明名称,分区] 3、偏移量 consumer.Assign(new TopicPartitionOffset(new TopicPartition("QueueName",0),int.Parse(offset))); while (true) { try { //消费后 确认消息 var result = consumer.Consume(); ///存储偏移量 distributedCache.SetString("QueueName",result.Offset.Value.ToString()); //业务 string key = result.Key; string value = result.Value; Console.WriteLine($"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); } catch (Exception ex) { Console.WriteLine($"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine();
- Nuget包
- 实例代码
- 什么是偏移量
-
kafka宕机后如何保证消息不丢失的
- 默认配置是消息持久化,存储在磁盘中,存储路径:/tmp/kafka-logs/文件名.log[配置KafKa的配置文件[server.properties]]
-
订阅发布[广播消费] [组 GroupId]
- 使用场景
生产者发送消息到队列,多个服务消费同一个消息。 - 独立偏移量的组成
- 主题+分区+groupId
- 代码实现
- 生产者
var producerConfig = new ProducerConfig { BootstrapServers = "127.0.0.1:9092", MessageTimeoutMs = 50000 }; var builder = new ProducerBuilder\<string, string>(producerConfig); using (var producer = builder.Build()) { try { //数据对象 var data = new { name = "小明", sex = "男", old = 12 }; string dataJson = JsonConvert.SerializeObject(data);0 var dr = producer.ProduceAsync("QueueName", new Message\<string, string> { Key = "data", Value = dataJson }).GetAwaiter().GetResult(); Console.WriteLine("------------消息发送成功!----------------"); } catch (ProduceException\<string, string> ex) { Console.WriteLine(\$"消息发送失败!;data:" + ex.Error.Reason); } } - 消费者
- 代码
var consumerConfig = new ConsumerConfig { ...... GroupId = "temp",// 如果其他服务也想同时消费一个服务,其他服务在同一个主题之下设置不同的组ID即可。 ..... }; - 实例代码
- 订单服务
private IDistributedCache distributedCache; public 构造函数(IDistributedCache \_distributedCache) { distributedCache = \_distributedCache; } //消费者代码 Console.WriteLine("------------消息消费中--------"); //偏移量定义 Offset offset = 0; Task.Run(() => { var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "temp", //消息确认 true:自动确认 false:手动确认 //手动确认 EnableAutoCommit = true }; var builder = new ConsumerBuilder<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); //从Redis中获取偏移量 string offset = distributedCache.GetString("QueueName"); if (string.IsNullOrEmpty(offset)) { offset = "0"; } //重置偏移量 //TopicPartitionOffset对象参数:1、分区对象[队列明名称,分区] 3、偏移量 consumer.Assign(new TopicPartitionOffset(new TopicPartition("QueueName",0),int.Parse(offset))); while (true) { try { //消费后 确认消息 var result = consumer.Consume(); ///存储偏移量 distributedCache.SetString("QueueName",result.Offset.Value.ToString()); //业务 string key = result.Key; string value = result.Value; Console.WriteLine($"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); } catch (Exception ex) { Console.WriteLine($"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine(); - 短信服务
private IDistributedCache distributedCache; public 构造函数(IDistributedCache \_distributedCache) { distributedCache = \_distributedCache; } //消费者代码 Console.WriteLine("------------消息消费中--------"); //偏移量定义 Offset offset = 0; Task.Run(() => { var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "sms", //消息确认 true:自动确认 false:手动确认 //手动确认 EnableAutoCommit = true }; var builder = new ConsumerBuilder<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); //从Redis中获取偏移量 string offset = distributedCache.GetString("QueueName"); if (string.IsNullOrEmpty(offset)) { offset = "0"; } //重置偏移量 //TopicPartitionOffset对象参数:1、分区对象[队列明名称,分区] 3、偏移量 consumer.Assign(new TopicPartitionOffset(new TopicPartition("QueueName",0),int.Parse(offset))); while (true) { try { //消费后 确认消息 var result = consumer.Consume(); ///存储偏移量 distributedCache.SetString("QueueName",result.Offset.Value.ToString()); //业务 string key = result.Key; string value = result.Value; Console.WriteLine($"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); } catch (Exception ex) { Console.WriteLine($"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine();
- 订单服务
- 代码
- 生产者
- 使用场景
五、kafka分区落地
- 分区
一个消费者只能对应一个分区。 - 场景
- 解决大量消息堆积的问题。
- 作用
- 解决消息堆积的问题
- 存储海量的数据
- 代码实现
- 创建分区
public async Task CreatePartition(string topic,int partitionCount) { AdminClientConfig adminClientConfig = new AdminClientConfig(){ BootstrapServers = "127.0.0.1:9092" }; var build = new AdminClientBuild(adminClientConfig).Build(); build.CreatePartitionsAsync(new PartitionsSpecification\[]{ new PartitionsSpecification(){Topic = topic,IncreaseTo = partitionCount} }).Wait(); await Task.CompletedTask; } - 轮询算法
static int requestCount = 0; public Partition RoundRobinPartitioner(string tipic,int partitionCount,ReadOnlySpan<byte>keyData,bool keyIsNull) { int partition = requestCount%partitionCount; requestCount++; return new Partition(partition); } - 生产者
- 代码
//设置轮询算法 builder.SetDefaultPartitioner(\[算法 委托函数]); - 实例代码
var producerConfig = new ProducerConfig { BootstrapServers = "127.0.0.1:9092", MessageTimeoutMs = 50000 }; var builder = new ProducerBuilder\<string, string>(producerConfig); //设置轮询算法 builder.SetDefaultPartitioner(RoundRobinPartitioner); using (var producer = builder.Build()) { try { //数据对象 var data = new { name = "小明", sex = "男", old = 12 }; string dataJson = JsonConvert.SerializeObject(data);0 var dr = producer.ProduceAsync("QueueName", new Message\<string, string> { Key = "data", Value = dataJson }).GetAwaiter().GetResult(); Console.WriteLine("------------消息发送成功!----------------"); } catch (ProduceException\<string, string> ex) { Console.WriteLine(\$"消息发送失败!;data:" + ex.Error.Reason); } }
- 代码
- 消费者
- 订单服务 [5001]
private IDistributedCache distributedCache; public 构造函数(IDistributedCache \_distributedCache) { distributedCache = \_distributedCache; } //消费者代码 Console.WriteLine("------------消息消费中--------"); //偏移量定义 Offset offset = 0; Task.Run(() => { var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "temp", //消息确认 true:自动确认 false:手动确认 //手动确认 EnableAutoCommit = true }; var builder = new ConsumerBuilder<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); //从Redis中获取偏移量 string offset = distributedCache.GetString("QueueName"); if (string.IsNullOrEmpty(offset)) { offset = "0"; } //获取分区 ..... [代码未加] //重置偏移量 //TopicPartitionOffset对象参数:1、分区对象[队列明名称,分区] 3、偏移量 //consumer.Assign(new TopicPartitionOffset(new TopicPartition("QueueName",0[分区动态从数据库获取]),int.Parse(offset))); while (true) { try { //消费后 确认消息 var result = consumer.Consume(); //存储分区,代码未加 ........ ///存储偏移量 //distributedCache.SetString("QueueName",result.Offset.Value.ToString()); //业务 string key = result.Key; string value = result.Value; Console.WriteLine($"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); } catch (Exception ex) { Console.WriteLine($"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine(); - 订单服务 [5002]
private IDistributedCache distributedCache; public 构造函数(IDistributedCache \_distributedCache) { distributedCache = \_distributedCache; } //消费者代码 Console.WriteLine("------------消息消费中--------"); //偏移量定义 Offset offset = 0; Task.Run(() => { var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "temp", //消息确认 true:自动确认 false:手动确认 //手动确认 EnableAutoCommit = true }; var builder = new ConsumerBuilder<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); //从Redis中获取偏移量 string offset = distributedCache.GetString("QueueName"); if (string.IsNullOrEmpty(offset)) { offset = "0"; } //获取分区 ..... [代码未加] //重置偏移量 //TopicPartitionOffset对象参数:1、分区对象[队列明名称,分区] 3、偏移量 //consumer.Assign(new TopicPartitionOffset(new TopicPartition("QueueName",0[分区动态从数据库获取]),int.Parse(offset))); while (true) { try { //消费后 确认消息 var result = consumer.Consume(); //存储分区,代码未加 ........ ///存储偏移量 //distributedCache.SetString("QueueName",result.Offset.Value.ToString()); //业务 string key = result.Key; string value = result.Value; Console.WriteLine($"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); } catch (Exception ex) { Console.WriteLine($"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine();
- 订单服务 [5001]
- 创建分区
- 指定的分区消费消息
- 生产者
var producerConfig = new ProducerConfig { BootstrapServers = "127.0.0.1:9092", MessageTimeoutMs = 50000 }; var builder = new ProducerBuilder\<string, string>(producerConfig); using (var producer = builder.Build()) { try { //数据对象 var data = new { name = "小明", sex = "男", old = 12 }; string dataJson = JsonConvert.SerializeObject(data);0 var dr = producer.ProduceAsync("QueueName", new Message\<string, string> { Key = "data", Value = dataJson }).GetAwaiter().GetResult(); Console.WriteLine("------------消息发送成功!----------------"); } catch (ProduceException\<string, string> ex) { Console.WriteLine(\$"消息发送失败!;data:" + ex.Error.Reason); } } - 消费者
- 代码
//重置偏移量可以指定分区 consumer.Assign(new TopicPartitionOffset(new TopicPartition("\[主题名称]",\[分区]),\[偏移量]); - 示例代码
private IDistributedCache distributedCache; public 构造函数(IDistributedCache \_distributedCache) { distributedCache = \_distributedCache; } //消费者代码 Console.WriteLine("------------消息消费中--------"); //偏移量定义 Offset offset = 0; Task.Run(() => { var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "temp", //消息确认 true:自动确认 false:手动确认 //手动确认 EnableAutoCommit = true }; var builder = new ConsumerBuilder<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); //从Redis中获取偏移量 string offset = distributedCache.GetString("QueueName"); if (string.IsNullOrEmpty(offset)) { offset = "0"; } //重置偏移量 //TopicPartitionOffset对象参数:1、分区对象[队列明名称,分区] 3、偏移量 consumer.Assign(new TopicPartitionOffset(new TopicPartition("QueueName",0),int.Parse(offset))); while (true) { try { //消费后 确认消息 var result = consumer.Consume(); //存储分区 ............. ///存储偏移量 distributedCache.SetString("QueueName",result.Offset.Value.ToString()); //业务 string key = result.Key; string value = result.Value; Console.WriteLine($"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); } catch (Exception ex) { Console.WriteLine($"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine();
- 代码
- 生产者
- 指定的分区发送消息
- 生产者
- 代码
//参数1:队列名称
//参数2:分区TopicPartition topicPartition = new TopicPartition("\[队列名称]",\[分区]); - 实例代码
var producerConfig = new ProducerConfig { BootstrapServers = "127.0.0.1:9092", MessageTimeoutMs = 50000 }; var builder = new ProducerBuilder\<string, string>(producerConfig); using (var producer = builder.Build()) { try { //数据对象 var data = new { name = "小明", sex = "男", old = 12 }; string dataJson = JsonConvert.SerializeObject(data); //参数1:队列名称 //参数2:分区 TopicPartition topicPartition = new TopicPartition("QueueName",1); var dr = producer.ProduceAsync(topicPartition, new Message\<string, string> { Key = "data", Value = dataJson }).GetAwaiter().GetResult(); Console.WriteLine("------------消息发送成功!----------------"); } catch (ProduceException\<string, string> ex) { Console.WriteLine(\$"消息发送失败!;data:" + ex.Error.Reason); } }
- 代码
- 消费者
private IDistributedCache distributedCache; public 构造函数(IDistributedCache \_distributedCache) { distributedCache = \_distributedCache; } //消费者代码 Console.WriteLine("------------消息消费中--------"); //偏移量定义 Offset offset = 0; Task.Run(() => { var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "temp", //消息确认 true:自动确认 false:手动确认 //手动确认 EnableAutoCommit = true }; var builder = new ConsumerBuilder<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); //从Redis中获取偏移量 string offset = distributedCache.GetString("QueueName"); if (string.IsNullOrEmpty(offset)) { offset = "0"; } //重置偏移量 TopicPartitionOffset对象参数:1、分区对象[队列明名称,分区] 3、偏移量 consumer.Assign(new TopicPartitionOffset(new TopicPartition("QueueName",0),int.Parse(offset))); while (true) { try { //消费后 确认消息 var result = consumer.Consume(); ///存储偏移量 distributedCache.SetString("QueueName",result.Offset.Value.ToString()); //业务 string key = result.Key; string value = result.Value; Console.WriteLine($"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); } catch (Exception ex) { Console.WriteLine($"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine();
- 生产者
六、kafka延迟消费落地
- 场景
自动取消订单。 - 实现
- 生产者
var producerConfig = new ProducerConfig { BootstrapServers = "127.0.0.1:9092", MessageTimeoutMs = 50000 }; var builder = new ProducerBuilder\<string, string>(producerConfig); using (var producer = builder.Build()) { try { //数据对象 var data = new { name = "小明", sex = "男", old = 12 }; string dataJson = JsonConvert.SerializeObject(data); //参数1:队列名称 //参数2:消息数据 var dr = producer.ProduceAsync("QueueName", new Message\<string, string> { Key = "data", Value = dataJson }).GetAwaiter().GetResult(); Console.WriteLine("------------消息发送成功!----------------"); } catch (ProduceException\<string, string> ex) { Console.WriteLine(\$"消息发送失败!;data:" + ex.Error.Reason); } } - 消费者
- 代码
//恢复消费 new Timer((s)=>{ consumer.Resume(new List<TopicPartition>{new TopicPartition(\[主题名称],分区) },null,Timeout.Infinite,Timeout.Infinite).Change(\[延迟时间\[单位:毫秒]],\[延迟时间\[单位:毫秒]]); }); //停止消息 consumer.Pause(new List<TopicPartition>{new TopicPartition(\[主题名称],分区) }); - 实例代码
private IDistributedCache distributedCache; public 构造函数(IDistributedCache \_distributedCache) { distributedCache = \_distributedCache; } //消费者代码 Console.WriteLine("------------消息消费中--------"); //偏移量定义 Offset offset = 0; Task.Run(() => { // 创建主题代码未加 CreateTopic("QueueName"); var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "temp", //消息确认 true:自动确认 false:手动确认 //手动确认 EnableAutoCommit = true, //批量消费获取最小值 FetchMinBytes = 0, //批量消费的最大值 FetchMaxBytes = 3000 }; var builder = new ConsumerBuilder<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); //从Redis中获取偏移量 string offset = distributedCache.GetString("QueueName"); if (string.IsNullOrEmpty(offset)) { offset = "0"; } //重置偏移量 TopicPartitionOffset对象参数:1、分区对象[队列明名称,分区] 3、偏移量 consumer.Assign(new TopicPartitionOffset(new TopicPartition("QueueName",0),int.Parse(offset))); while (true) { try { //恢复消费 new Timer((s)=>{ consumer.Resume(new List<TopicPartition>{new TopicPartition("QueueName",0) },null,Timeout.Infinite,Timeout.Infinite).Change(5000,5000); }); //停止消息 consumer.Pause(new List<TopicPartition>{new TopicPartition("QueueName",0) }); //批量获取消息消费后在确认消息 var result = consumer.Consume(); try { ///存储偏移量 distributedCache.SetString("QueueName",result.Offset.Value.ToString()); //业务 string key = result.Key; string value = result.Value; Console.WriteLine($"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); } cache (Exception ex) { } finally { consumer.Pause(new List<TopicPartition>{new TopicPartition("QueueName",0) }); } } catch (Exception ex) { Console.WriteLine($"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine();
- 代码
- 生产者
七 批量发送消息到队列
- 场景
假如发送100条消息到消息队列中,如何办证消息全部发送成功或全部发送失败,那就是使用事务来处理。 - 代码
producer.InitTransactions(TimeSpan.FormSeconds(60)); ..... producer.BeginTransaction(); .... producer.CommitTransaction(); - 生产者
var producerConfig = new ProducerConfig { BootstrapServers = "127.0.0.1:9092", MessageTimeoutMs = 50000, //失败重试 EnableIdempotence = true, TransactionalId= "" }; var builder = new ProducerBuilder\<string, string>(producerConfig); using (var producer = builder.Build()) { producer.InitTransactions(TimeSpan.FormSeconds(60)); try { //数据对象 var data = new { name = "小明", sex = "男", old = 12 }; string dataJson = JsonConvert.SerializeObject(data); //参数1:队列名称 //参数2:消息数据 producer.BeginTransaction(); for(int i= 0;i<=100;i++) { var dr = producer.ProduceAsync("QueueName", new Message\<string, string> { Key = "data", Value = dataJson }).GetAwaiter().GetResult(); Console.WriteLine("------------消息发送成功!----------------"); } producer.CommitTransaction(); } catch (ProduceException\<string, string> ex) { Console.WriteLine(\$"消息发送失败!;data:" + ex.Error.Reason); } } - 消费者
private IDistributedCache distributedCache; public 构造函数(IDistributedCache \_distributedCache) { distributedCache = \_distributedCache; } //消费者代码 Console.WriteLine("------------消息消费中--------"); //偏移量定义 Offset offset = 0; Task.Run(() => { var consumerConfig = new ConsumerConfig { BootstrapServers = "127.0.0.1:9092", AutoOffsetReset = AutoOffsetReset.Earliest, GroupId = "temp", //消息确认 true:自动确认 false:手动确认 //手动确认 EnableAutoCommit = true }; var builder = new ConsumerBuilder<string, string>(consumerConfig); using var consumer = builder.Build(); //订阅 consumer.Subscribe("QueueName"); //从Redis中获取偏移量 string offset = distributedCache.GetString("QueueName"); if (string.IsNullOrEmpty(offset)) { offset = "0"; } //重置偏移量 //TopicPartitionOffset对象参数:1、分区对象[队列明名称,分区] 3、偏移量 consumer.Assign(new TopicPartitionOffset(new TopicPartition("QueueName",0[从redis获取分区]),int.Parse(offset))); while (true) { try { //消费后 确认消息 var result = consumer.Consume(); //存储分区 ............. ///存储偏移量 distributedCache.SetString("QueueName",result.Offset.Value.ToString()); //业务 string key = result.Key; string value = result.Value; Console.WriteLine($"-----------key:{key},value :{value}"); Console.WriteLine("------------消费成功!--------------"); } catch (Exception ex) { Console.WriteLine($"消息消费失败!;data:" + ex.Message); } } }); Console.ReadLine();
Kafka核心概念与微服务落地实践
1921

被折叠的 条评论
为什么被折叠?



