kafka增加批量消费
This commit is contained in:
parent
0d4c780727
commit
eed68d0fe0
@ -90,7 +90,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
result.Data.ValidData = true;
|
result.Data.ValidData = true;
|
||||||
@ -136,7 +136,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
result.Data.ValidData = true;
|
result.Data.ValidData = true;
|
||||||
@ -224,7 +224,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
@ -316,7 +316,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
|
|
||||||
if (isManual)
|
if (isManual)
|
||||||
{
|
{
|
||||||
@ -384,7 +384,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
|
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
@ -420,7 +420,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
|
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
@ -456,7 +456,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
|
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
@ -491,7 +491,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
|
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
@ -527,7 +527,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
return result;
|
return result;
|
||||||
@ -585,7 +585,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
@ -662,7 +662,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
}
|
}
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
@ -699,7 +699,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
}
|
}
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
@ -735,7 +735,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
}
|
}
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
@ -769,7 +769,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
return result;
|
return result;
|
||||||
@ -804,7 +804,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
}
|
}
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
result.Msg = "操作成功";
|
result.Msg = "操作成功";
|
||||||
@ -867,7 +867,7 @@ namespace JiShe.CollectBus.EnergySystem
|
|||||||
Message = bytes,
|
Message = bytes,
|
||||||
Type = IssuedEventType.Data,
|
Type = IssuedEventType.Data,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Status = true;
|
result.Status = true;
|
||||||
|
|||||||
@ -178,7 +178,7 @@ namespace JiShe.CollectBus.Plugins
|
|||||||
//await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent);
|
//await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent);
|
||||||
|
|
||||||
|
|
||||||
await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent.Serialize());
|
await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginReceivedEventName, messageReceivedLoginEvent);
|
||||||
|
|
||||||
//await _producerBus.Publish( messageReceivedLoginEvent);
|
//await _producerBus.Publish( messageReceivedLoginEvent);
|
||||||
}
|
}
|
||||||
@ -227,7 +227,7 @@ namespace JiShe.CollectBus.Plugins
|
|||||||
};
|
};
|
||||||
//await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent);
|
//await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent);
|
||||||
|
|
||||||
await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent.Serialize());
|
await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatReceivedEventName, messageReceivedHeartbeatEvent);
|
||||||
//await _producerBus.Publish(messageReceivedHeartbeatEvent);
|
//await _producerBus.Publish(messageReceivedHeartbeatEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,7 +271,7 @@ namespace JiShe.CollectBus.Plugins
|
|||||||
MessageHexString = messageHexString,
|
MessageHexString = messageHexString,
|
||||||
DeviceNo = deviceNo,
|
DeviceNo = deviceNo,
|
||||||
MessageId = NewId.NextGuid().ToString()
|
MessageId = NewId.NextGuid().ToString()
|
||||||
}.Serialize());
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -382,7 +382,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading
|
|||||||
};
|
};
|
||||||
//_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg);
|
//_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg);
|
||||||
|
|
||||||
_ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg.Serialize());
|
_ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerOneMinuteIssuedEventName, tempMsg);
|
||||||
//_= _producerBus.Publish(tempMsg);
|
//_= _producerBus.Publish(tempMsg);
|
||||||
|
|
||||||
|
|
||||||
@ -448,7 +448,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading
|
|||||||
};
|
};
|
||||||
//_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg);
|
//_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg);
|
||||||
|
|
||||||
_ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg.Serialize());
|
_ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFiveMinuteIssuedEventName, tempMsg);
|
||||||
|
|
||||||
//_ = _producerBus.Publish(tempMsg);
|
//_ = _producerBus.Publish(tempMsg);
|
||||||
|
|
||||||
@ -514,7 +514,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading
|
|||||||
};
|
};
|
||||||
//_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg);
|
//_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg);
|
||||||
|
|
||||||
_ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg.Serialize());
|
_ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg);
|
||||||
|
|
||||||
//_ = _producerBus.Publish(tempMsg);
|
//_ = _producerBus.Publish(tempMsg);
|
||||||
|
|
||||||
@ -809,7 +809,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading
|
|||||||
}
|
}
|
||||||
int partition = DeviceGroupBalanceControl.GetDeviceGroupId(taskRecord.FocusAddress);
|
int partition = DeviceGroupBalanceControl.GetDeviceGroupId(taskRecord.FocusAddress);
|
||||||
|
|
||||||
await _producerService.ProduceAsync(topicName, partition, taskRecord.Serialize());
|
await _producerService.ProduceAsync(topicName, partition, taskRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AmmerterCreatePublishTask(int timeDensity, MeterTypeEnum meterType)
|
private async Task AmmerterCreatePublishTask(int timeDensity, MeterTypeEnum meterType)
|
||||||
@ -851,7 +851,7 @@ namespace JiShe.CollectBus.ScheduledMeterReading
|
|||||||
};
|
};
|
||||||
//_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg);
|
//_ = _producerBus.PublishDelayAsync(TimeSpan.FromMicroseconds(500), ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg);
|
||||||
|
|
||||||
_ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg.Serialize());
|
_ = _producerService.ProduceAsync(ProtocolConst.AmmeterSubscriberWorkerFifteenMinuteIssuedEventName, tempMsg);
|
||||||
|
|
||||||
//_ = _producerBus.Publish(tempMsg);
|
//_ = _producerBus.Publish(tempMsg);
|
||||||
|
|
||||||
|
|||||||
@ -26,20 +26,51 @@ namespace JiShe.CollectBus.Kafka.Attributes
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 任务数(默认是多少个分区多少个任务)
|
/// 任务数(默认是多少个分区多少个任务)
|
||||||
|
/// 如设置订阅指定Partition则任务数始终为1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int TaskCount { get; set; } = -1;
|
public int TaskCount { get; set; } = -1;
|
||||||
|
|
||||||
public KafkaSubscribeAttribute(string topic, string groupId = "default")
|
/// <summary>
|
||||||
|
/// 批量处理数量
|
||||||
|
/// </summary>
|
||||||
|
public int BatchSize { get; set; } = 100;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用批量处理
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableBatch { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批次超时时间
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan? BatchTimeout { get; set; }=null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 订阅主题
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="batchTimeout">batchTimeout格式:("00:05:00")</param>
|
||||||
|
public KafkaSubscribeAttribute(string topic, string groupId = "default", bool enableBatch = false, int batchSize = 100, string? batchTimeout = null)
|
||||||
{
|
{
|
||||||
this.Topic = topic;
|
this.Topic = topic;
|
||||||
this.GroupId = groupId;
|
this.GroupId = groupId;
|
||||||
|
this.EnableBatch = enableBatch;
|
||||||
|
this.BatchSize = batchSize;
|
||||||
|
this.BatchTimeout = batchTimeout != null? TimeSpan.Parse(batchTimeout): null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KafkaSubscribeAttribute(string topic, int partition, string groupId = "default")
|
/// <summary>
|
||||||
|
/// 订阅主题
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="batchTimeout">batchTimeout格式:("00:05:00")</param>
|
||||||
|
public KafkaSubscribeAttribute(string topic, int partition, string groupId = "default", bool enableBatch = false, int batchSize = 100, string? batchTimeout = null)
|
||||||
{
|
{
|
||||||
this.Topic = topic ;
|
this.Topic = topic;
|
||||||
this.Partition = partition;
|
this.Partition = partition;
|
||||||
this.GroupId = groupId;
|
this.GroupId = groupId;
|
||||||
|
this.TaskCount = 1;
|
||||||
|
this.EnableBatch = enableBatch;
|
||||||
|
this.BatchSize = batchSize;
|
||||||
|
this.BatchTimeout = batchTimeout != null ? TimeSpan.Parse(batchTimeout) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ using System.Collections.Concurrent;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NUglify.Html;
|
using NUglify.Html;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace JiShe.CollectBus.Kafka.Consumer
|
namespace JiShe.CollectBus.Kafka.Consumer
|
||||||
{
|
{
|
||||||
@ -19,6 +21,7 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly ConcurrentDictionary<Type, (object Consumer, CancellationTokenSource CTS)>
|
private readonly ConcurrentDictionary<Type, (object Consumer, CancellationTokenSource CTS)>
|
||||||
_consumerStore = new();
|
_consumerStore = new();
|
||||||
|
private class KafkaConsumer<TKey, TValue> where TKey : notnull where TValue : class { }
|
||||||
|
|
||||||
public ConsumerService(IConfiguration configuration, ILogger<ConsumerService> logger)
|
public ConsumerService(IConfiguration configuration, ILogger<ConsumerService> logger)
|
||||||
{
|
{
|
||||||
@ -38,6 +41,7 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
{
|
{
|
||||||
var config = BuildConsumerConfig(groupId);
|
var config = BuildConsumerConfig(groupId);
|
||||||
return new ConsumerBuilder<TKey, TValue>(config)
|
return new ConsumerBuilder<TKey, TValue>(config)
|
||||||
|
.SetValueDeserializer(new JsonSerializer<TValue>())
|
||||||
.SetLogHandler((_, log) => _logger.LogInformation($"消费者Log: {log.Message}"))
|
.SetLogHandler((_, log) => _logger.LogInformation($"消费者Log: {log.Message}"))
|
||||||
.SetErrorHandler((_, e) => _logger.LogError($"消费者错误: {e.Reason}"))
|
.SetErrorHandler((_, e) => _logger.LogError($"消费者错误: {e.Reason}"))
|
||||||
.Build();
|
.Build();
|
||||||
@ -54,7 +58,8 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
AutoOffsetReset = AutoOffsetReset.Earliest,
|
AutoOffsetReset = AutoOffsetReset.Earliest,
|
||||||
EnableAutoCommit = false, // 禁止AutoCommit
|
EnableAutoCommit = false, // 禁止AutoCommit
|
||||||
EnablePartitionEof = true, // 启用分区末尾标记
|
EnablePartitionEof = true, // 启用分区末尾标记
|
||||||
AllowAutoCreateTopics= true // 启用自动创建
|
AllowAutoCreateTopics= true, // 启用自动创建
|
||||||
|
FetchMaxBytes = 1024 * 1024 * 50 // 增加拉取大小(50MB)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (enableAuth)
|
if (enableAuth)
|
||||||
@ -105,7 +110,7 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task SubscribeAsync<TKey, TValue>(string[] topics, Func<TKey, TValue, Task<bool>> messageHandler, string? groupId = null) where TKey : notnull where TValue : class
|
public async Task SubscribeAsync<TKey, TValue>(string[] topics, Func<TKey, TValue, Task<bool>> messageHandler, string? groupId = null) where TKey : notnull where TValue : class
|
||||||
{
|
{
|
||||||
var consumerKey = typeof((TKey, TValue));
|
var consumerKey = typeof(KafkaConsumer<TKey, TValue>);
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
||||||
@ -123,15 +128,28 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = consumer.Consume(cts.Token);
|
var result = consumer.Consume(cts.Token);
|
||||||
if (result == null) continue;
|
if (result == null || result.Message==null || result.Message.Value == null)
|
||||||
if (result.Message.Value == null) continue;
|
{
|
||||||
|
_logger.LogWarning($"Kafka消费: {result?.Topic} 分区 {result?.Partition} 值为NULL");
|
||||||
|
consumer.Commit(result); // 手动提交
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (result.IsPartitionEOF)
|
if (result.IsPartitionEOF)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
await Task.Delay(TimeSpan.FromSeconds(1),cts.Token);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } };
|
||||||
|
// 检查 Header 是否符合条件
|
||||||
|
if (!headersFilter.Match(result.Message.Headers))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} Header未匹配", result.Topic, result.Partition);
|
||||||
|
|
||||||
|
//consumer.Commit(result); // 提交偏移量
|
||||||
|
// 跳过消息
|
||||||
|
continue;
|
||||||
|
}
|
||||||
bool sucess= await messageHandler(result.Message.Key, result.Message.Value);
|
bool sucess= await messageHandler(result.Message.Key, result.Message.Value);
|
||||||
if (sucess)
|
if (sucess)
|
||||||
{
|
{
|
||||||
@ -159,7 +177,7 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task SubscribeAsync<TValue>(string[] topics, Func<TValue, Task<bool>> messageHandler, string? groupId) where TValue : class
|
public async Task SubscribeAsync<TValue>(string[] topics, Func<TValue, Task<bool>> messageHandler, string? groupId) where TValue : class
|
||||||
{
|
{
|
||||||
var consumerKey = typeof((Ignore, TValue));
|
var consumerKey = typeof(KafkaConsumer<Ignore, TValue>);
|
||||||
var cts = new CancellationTokenSource();
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
var consumer = _consumerStore.GetOrAdd(consumerKey, _=>
|
var consumer = _consumerStore.GetOrAdd(consumerKey, _=>
|
||||||
@ -177,15 +195,27 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = consumer.Consume(cts.Token);
|
var result = consumer.Consume(cts.Token);
|
||||||
if (result == null) continue;
|
if (result == null || result.Message==null || result.Message.Value == null)
|
||||||
if (result.Message == null) continue;
|
{
|
||||||
|
_logger.LogWarning($"Kafka消费: {result?.Topic} 分区 {result?.Partition} 值为NULL");
|
||||||
|
consumer.Commit(result); // 手动提交
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (result.IsPartitionEOF)
|
if (result.IsPartitionEOF)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
await Task.Delay(100, cts.Token);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } };
|
||||||
|
// 检查 Header 是否符合条件
|
||||||
|
if (!headersFilter.Match(result.Message.Headers))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} Header值未匹配", result.Topic, result.Partition);
|
||||||
|
//consumer.Commit(result); // 提交偏移量
|
||||||
|
// 跳过消息
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sucess = await messageHandler(result.Message.Value);
|
bool sucess = await messageHandler(result.Message.Value);
|
||||||
if (sucess)
|
if (sucess)
|
||||||
consumer.Commit(result); // 手动提交
|
consumer.Commit(result); // 手动提交
|
||||||
@ -199,6 +229,267 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量订阅消息
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">消息Key类型</typeparam>
|
||||||
|
/// <typeparam name="TValue">消息Value类型</typeparam>
|
||||||
|
/// <param name="topic">主题</param>
|
||||||
|
/// <param name="messageBatchHandler">批量消息处理函数</param>
|
||||||
|
/// <param name="groupId">消费组ID</param>
|
||||||
|
/// <param name="batchSize">批次大小</param>
|
||||||
|
/// <param name="batchTimeout">批次超时时间</param>
|
||||||
|
public async Task SubscribeBatchAsync<TKey, TValue>(string topic, Func<IEnumerable<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class
|
||||||
|
{
|
||||||
|
await SubscribeBatchAsync<TKey, TValue>(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量订阅消息
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">消息Key类型</typeparam>
|
||||||
|
/// <typeparam name="TValue">消息Value类型</typeparam>
|
||||||
|
/// <param name="topics">主题列表</param>
|
||||||
|
/// <param name="messageBatchHandler">批量消息处理函数</param>
|
||||||
|
/// <param name="groupId">消费组ID</param>
|
||||||
|
/// <param name="batchSize">批次大小</param>
|
||||||
|
/// <param name="batchTimeout">批次超时时间</param>
|
||||||
|
public async Task SubscribeBatchAsync<TKey, TValue>(string[] topics,Func<IEnumerable<TValue>, Task<bool>> messageBatchHandler, string? groupId = null,int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class
|
||||||
|
{
|
||||||
|
var consumerKey = typeof(KafkaConsumer<TKey, TValue>);
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
||||||
|
(
|
||||||
|
CreateConsumer<TKey, TValue>(groupId),
|
||||||
|
cts
|
||||||
|
)).Consumer as IConsumer<TKey, TValue>;
|
||||||
|
|
||||||
|
consumer!.Subscribe(topics);
|
||||||
|
|
||||||
|
var timeout = batchTimeout ?? TimeSpan.FromSeconds(5); // 默认超时时间调整为5秒
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var messages = new List<(TValue Value, TopicPartitionOffset Offset)>();
|
||||||
|
var startTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
while (!cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 非阻塞快速累积消息
|
||||||
|
while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout)
|
||||||
|
{
|
||||||
|
var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
if (result.IsPartitionEOF)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(1), cts.Token);
|
||||||
|
}
|
||||||
|
else if (result.Message.Value != null)
|
||||||
|
{
|
||||||
|
var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } };
|
||||||
|
// 检查 Header 是否符合条件
|
||||||
|
if (!headersFilter.Match(result.Message.Headers))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} Header未匹配", result.Topic, result.Partition);
|
||||||
|
//consumer.Commit(result); // 提交偏移量
|
||||||
|
// 跳过消息
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
messages.Add((result.Message.Value, result.TopicPartitionOffset));
|
||||||
|
//messages.Add(result.Message.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 无消息时短暂等待
|
||||||
|
await Task.Delay(10, cts.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理批次
|
||||||
|
if (messages.Count > 0)
|
||||||
|
{
|
||||||
|
bool success = await messageBatchHandler(messages.Select(m => m.Value));
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
var offsetsByPartition = new Dictionary<TopicPartition, long>();
|
||||||
|
foreach (var msg in messages)
|
||||||
|
{
|
||||||
|
var tp = msg.Offset.TopicPartition;
|
||||||
|
var offset = msg.Offset.Offset;
|
||||||
|
if (!offsetsByPartition.TryGetValue(tp, out var currentMax) || offset > currentMax)
|
||||||
|
{
|
||||||
|
offsetsByPartition[tp] = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsetsToCommit = offsetsByPartition
|
||||||
|
.Select(kv => new TopicPartitionOffset(kv.Key, new Offset(kv.Value + 1)))
|
||||||
|
.ToList();
|
||||||
|
consumer.Commit(offsetsToCommit);
|
||||||
|
}
|
||||||
|
messages.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
catch (ConsumeException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// 任务取消,正常退出
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "处理批量消息时发生未知错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, cts.Token);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量订阅消息
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TValue">消息Value类型</typeparam>
|
||||||
|
/// <param name="topic">主题列表</param>
|
||||||
|
/// <param name="messageBatchHandler">批量消息处理函数</param>
|
||||||
|
/// <param name="groupId">消费组ID</param>
|
||||||
|
/// <param name="batchSize">批次大小</param>
|
||||||
|
/// <param name="batchTimeout">批次超时时间</param>
|
||||||
|
/// <param name="consumeTimeout">消费等待时间</param>
|
||||||
|
public async Task SubscribeBatchAsync<TValue>(string topic, Func<IEnumerable<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class
|
||||||
|
{
|
||||||
|
await SubscribeBatchAsync<TValue>(new[] { topic }, messageBatchHandler, groupId, batchSize, batchTimeout, consumeTimeout);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量订阅消息
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TValue">消息Value类型</typeparam>
|
||||||
|
/// <param name="topics">主题列表</param>
|
||||||
|
/// <param name="messageBatchHandler">批量消息处理函数</param>
|
||||||
|
/// <param name="groupId">消费组ID</param>
|
||||||
|
/// <param name="batchSize">批次大小</param>
|
||||||
|
/// <param name="batchTimeout">批次超时时间</param>
|
||||||
|
/// <param name="consumeTimeout">消费等待时间</param>
|
||||||
|
public async Task SubscribeBatchAsync<TValue>(string[] topics,Func<IEnumerable<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100,TimeSpan? batchTimeout = null,TimeSpan? consumeTimeout = null)where TValue : class
|
||||||
|
{
|
||||||
|
var consumerKey = typeof(KafkaConsumer<Ignore, TValue>);
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var consumer = _consumerStore.GetOrAdd(consumerKey, _ =>
|
||||||
|
(
|
||||||
|
CreateConsumer<Ignore, TValue>(groupId),
|
||||||
|
cts
|
||||||
|
)).Consumer as IConsumer<Ignore, TValue>;
|
||||||
|
|
||||||
|
consumer!.Subscribe(topics);
|
||||||
|
|
||||||
|
var timeout = batchTimeout ?? TimeSpan.FromSeconds(5); // 默认超时时间调整为5秒
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var messages = new List<(TValue Value, TopicPartitionOffset Offset)>();
|
||||||
|
//var messages = new List<ConsumeResult<TKey, TValue>>();
|
||||||
|
var startTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
while (!cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 非阻塞快速累积消息
|
||||||
|
while (messages.Count < batchSize && (DateTime.UtcNow - startTime) < timeout)
|
||||||
|
{
|
||||||
|
var result = consumer.Consume(TimeSpan.Zero); // 非阻塞调用
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
if (result.IsPartitionEOF)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Kafka消费: {Topic} 分区 {Partition} 已消费完", result.Topic, result.Partition);
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(1), cts.Token);
|
||||||
|
}
|
||||||
|
else if (result.Message.Value != null)
|
||||||
|
{
|
||||||
|
var headersFilter = new HeadersFilter { { "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) } };
|
||||||
|
// 检查 Header 是否符合条件
|
||||||
|
if (!headersFilter.Match(result.Message.Headers))
|
||||||
|
{
|
||||||
|
//consumer.Commit(result); // 提交偏移量
|
||||||
|
// 跳过消息
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
messages.Add((result.Message.Value, result.TopicPartitionOffset));
|
||||||
|
//messages.Add(result.Message.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 无消息时短暂等待
|
||||||
|
await Task.Delay(10, cts.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理批次
|
||||||
|
if (messages.Count > 0)
|
||||||
|
{
|
||||||
|
bool success = await messageBatchHandler(messages.Select(m => m.Value));
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
var offsetsByPartition = new Dictionary<TopicPartition, long>();
|
||||||
|
foreach (var msg in messages)
|
||||||
|
{
|
||||||
|
var tp = msg.Offset.TopicPartition;
|
||||||
|
var offset = msg.Offset.Offset;
|
||||||
|
if (!offsetsByPartition.TryGetValue(tp, out var currentMax) || offset > currentMax)
|
||||||
|
{
|
||||||
|
offsetsByPartition[tp] = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsetsToCommit = offsetsByPartition
|
||||||
|
.Select(kv => new TopicPartitionOffset(kv.Key, new Offset(kv.Value + 1)))
|
||||||
|
.ToList();
|
||||||
|
consumer.Commit(offsetsToCommit);
|
||||||
|
}
|
||||||
|
messages.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
catch (ConsumeException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"消息消费失败: {ex.Error.Reason}");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// 任务取消,正常退出
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "处理批量消息时发生未知错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, cts.Token);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 取消消息订阅
|
/// 取消消息订阅
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using Confluent.Kafka;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -32,6 +33,11 @@ namespace JiShe.CollectBus.Kafka.Consumer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task SubscribeAsync<TValue>(string[] topics, Func<TValue, Task<bool>> messageHandler, string? groupId = null) where TValue : class;
|
Task SubscribeAsync<TValue>(string[] topics, Func<TValue, Task<bool>> messageHandler, string? groupId = null) where TValue : class;
|
||||||
|
|
||||||
|
|
||||||
|
Task SubscribeBatchAsync<TKey, TValue>(string topic, Func<IEnumerable<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null) where TKey : notnull where TValue : class;
|
||||||
|
|
||||||
|
Task SubscribeBatchAsync<TValue>(string[] topics, Func<IEnumerable<TValue>, Task<bool>> messageBatchHandler, string? groupId = null, int batchSize = 100, TimeSpan? batchTimeout = null, TimeSpan? consumeTimeout = null) where TValue : class;
|
||||||
|
|
||||||
void Unsubscribe<TKey, TValue>() where TKey : notnull where TValue : class;
|
void Unsubscribe<TKey, TValue>() where TKey : notnull where TValue : class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/JiShe.CollectBus.KafkaProducer/HeadersFilter.cs
Normal file
30
src/JiShe.CollectBus.KafkaProducer/HeadersFilter.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using Confluent.Kafka;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace JiShe.CollectBus.Kafka
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 消息头过滤器
|
||||||
|
/// </summary>
|
||||||
|
public class HeadersFilter : Dictionary<string, byte[]>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 判断Headers是否匹配
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headers"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Match(Headers headers)
|
||||||
|
{
|
||||||
|
foreach (var kvp in this)
|
||||||
|
{
|
||||||
|
if (!headers.TryGetLastBytes(kvp.Key, out var value) || !value.SequenceEqual(kvp.Value))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
src/JiShe.CollectBus.KafkaProducer/JsonSerializer.cs
Normal file
88
src/JiShe.CollectBus.KafkaProducer/JsonSerializer.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Confluent.Kafka;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
|
||||||
|
namespace JiShe.CollectBus.Kafka
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// JSON 序列化器(支持泛型)
|
||||||
|
/// </summary>
|
||||||
|
public class JsonSerializer<T> : ISerializer<T>, IDeserializer<T>
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions _options = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||||
|
WriteIndented = false,// 设置格式化输出
|
||||||
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,// 允许特殊字符
|
||||||
|
IgnoreReadOnlyFields = true,
|
||||||
|
IgnoreReadOnlyProperties = true,
|
||||||
|
NumberHandling = JsonNumberHandling.AllowReadingFromString, // 允许数字字符串
|
||||||
|
AllowTrailingCommas = true, // 忽略尾随逗号
|
||||||
|
ReadCommentHandling = JsonCommentHandling.Skip, // 忽略注释
|
||||||
|
PropertyNameCaseInsensitive = true, // 属性名称大小写不敏感
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // 属性名称使用驼峰命名规则
|
||||||
|
Converters = { new DateTimeJsonConverter() } // 注册你的自定义转换器,
|
||||||
|
};
|
||||||
|
|
||||||
|
public byte[] Serialize(T data, SerializationContext context)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.SerializeToUtf8Bytes(data, _options);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Kafka序列化失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context)
|
||||||
|
{
|
||||||
|
if (isNull)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<T>(data, _options);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Kafka反序列化失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class DateTimeJsonConverter : JsonConverter<DateTime>
|
||||||
|
{
|
||||||
|
private readonly string _dateFormatString;
|
||||||
|
public DateTimeJsonConverter()
|
||||||
|
{
|
||||||
|
_dateFormatString = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTimeJsonConverter(string dateFormatString)
|
||||||
|
{
|
||||||
|
_dateFormatString = dateFormatString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
return DateTime.Parse(reader.GetString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value.ToString(_dateFormatString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -102,7 +102,7 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
{
|
{
|
||||||
var consumerService = provider.GetRequiredService<IConsumerService>();
|
var consumerService = provider.GetRequiredService<IConsumerService>();
|
||||||
|
|
||||||
await consumerService.SubscribeAsync<string>(attr.Topic, async (message) =>
|
await consumerService.SubscribeAsync<dynamic>(attr.Topic, async (message) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -126,21 +126,21 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
/// <param name="method"></param>
|
/// <param name="method"></param>
|
||||||
/// <param name="subscribe"></param>
|
/// <param name="subscribe"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static async Task<bool> ProcessMessageAsync(string message, MethodInfo method, object subscribe)
|
private static async Task<bool> ProcessMessageAsync(dynamic message, MethodInfo method, object subscribe)
|
||||||
{
|
{
|
||||||
var parameters = method.GetParameters();
|
var parameters = method.GetParameters();
|
||||||
bool isGenericTask = method.ReturnType.IsGenericType
|
bool isGenericTask = method.ReturnType.IsGenericType
|
||||||
&& method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>);
|
&& method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>);
|
||||||
bool existParameters = parameters.Length > 0;
|
bool existParameters = parameters.Length > 0;
|
||||||
dynamic? messageObj= null;
|
//dynamic? messageObj= null;
|
||||||
if (existParameters)
|
//if (existParameters)
|
||||||
{
|
//{
|
||||||
var paramType = parameters[0].ParameterType;
|
// var paramType = parameters[0].ParameterType;
|
||||||
messageObj = paramType == typeof(string) ? message : message.Deserialize(paramType);
|
// messageObj = paramType == typeof(string) ? message : message.Deserialize(paramType);
|
||||||
}
|
//}
|
||||||
if (isGenericTask)
|
if (isGenericTask)
|
||||||
{
|
{
|
||||||
object? result = await (Task<ISubscribeAck>)method.Invoke(subscribe, existParameters? new[] { messageObj }:null)!;
|
object? result = await (Task<ISubscribeAck>)method.Invoke(subscribe, existParameters? new[] { message } :null)!;
|
||||||
if (result is ISubscribeAck ackResult)
|
if (result is ISubscribeAck ackResult)
|
||||||
{
|
{
|
||||||
return ackResult.Ack;
|
return ackResult.Ack;
|
||||||
@ -148,7 +148,7 @@ namespace JiShe.CollectBus.Kafka
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
object? result = method.Invoke(subscribe, existParameters ? new[] { messageObj } : null);
|
object? result = method.Invoke(subscribe, existParameters ? new[] { message } : null);
|
||||||
if (result is ISubscribeAck ackResult)
|
if (result is ISubscribeAck ackResult)
|
||||||
{
|
{
|
||||||
return ackResult.Ack;
|
return ackResult.Ack;
|
||||||
|
|||||||
@ -17,7 +17,8 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
{
|
{
|
||||||
private readonly ILogger<ProducerService> _logger;
|
private readonly ILogger<ProducerService> _logger;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly ConcurrentDictionary<Tuple<Type, Type>, object> _producerCache = new();
|
private readonly ConcurrentDictionary<Type, object> _producerCache = new();
|
||||||
|
private class KafkaProducer<TKey, TValue> where TKey : notnull where TValue : class { }
|
||||||
|
|
||||||
public ProducerService(IConfiguration configuration,ILogger<ProducerService> logger)
|
public ProducerService(IConfiguration configuration,ILogger<ProducerService> logger)
|
||||||
{
|
{
|
||||||
@ -32,14 +33,13 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
/// <typeparam name="TKey"></typeparam>
|
/// <typeparam name="TKey"></typeparam>
|
||||||
/// <typeparam name="TValue"></typeparam>
|
/// <typeparam name="TValue"></typeparam>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private IProducer<TKey, TValue> GetProducer<TKey, TValue>()
|
private IProducer<TKey, TValue> GetProducer<TKey, TValue>(Type typeKey)
|
||||||
{
|
{
|
||||||
var typeKey = Tuple.Create(typeof(TKey), typeof(TValue))!;
|
|
||||||
|
|
||||||
return (IProducer<TKey, TValue>)_producerCache.GetOrAdd(typeKey, _ =>
|
return (IProducer<TKey, TValue>)_producerCache.GetOrAdd(typeKey, _ =>
|
||||||
{
|
{
|
||||||
var config = BuildProducerConfig();
|
var config = BuildProducerConfig();
|
||||||
return new ProducerBuilder<TKey, TValue>(config)
|
return new ProducerBuilder<TKey, TValue>(config)
|
||||||
|
.SetValueSerializer(new JsonSerializer<TValue>()) // Value 使用自定义 JSON 序列化
|
||||||
.SetLogHandler((_, msg) => _logger.Log(ConvertLogLevel(msg.Level), msg.Message))
|
.SetLogHandler((_, msg) => _logger.Log(ConvertLogLevel(msg.Level), msg.Message))
|
||||||
.Build();
|
.Build();
|
||||||
});
|
});
|
||||||
@ -103,8 +103,17 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task ProduceAsync<TKey, TValue>(string topic, TKey key, TValue value)where TKey : notnull where TValue : class
|
public async Task ProduceAsync<TKey, TValue>(string topic, TKey key, TValue value)where TKey : notnull where TValue : class
|
||||||
{
|
{
|
||||||
var producer = GetProducer<TKey, TValue>();
|
var typeKey = typeof(KafkaProducer<TKey, TValue>);
|
||||||
await producer.ProduceAsync(topic, new Message<TKey, TValue> { Key = key, Value = value });
|
var producer = GetProducer<TKey, TValue>(typeKey);
|
||||||
|
var message = new Message<TKey, TValue>
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Value = value,
|
||||||
|
Headers = new Headers{
|
||||||
|
{ "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await producer.ProduceAsync(topic, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -116,8 +125,16 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task ProduceAsync<TValue>(string topic, TValue value) where TValue : class
|
public async Task ProduceAsync<TValue>(string topic, TValue value) where TValue : class
|
||||||
{
|
{
|
||||||
var producer = GetProducer<string, TValue>();
|
var typeKey = typeof(KafkaProducer<string, TValue>);
|
||||||
await producer.ProduceAsync(topic, new Message<string, TValue> { Value = value });
|
var producer = GetProducer<string, TValue>(typeKey);
|
||||||
|
var message = new Message<string, TValue>
|
||||||
|
{
|
||||||
|
Value = value,
|
||||||
|
Headers = new Headers{
|
||||||
|
{ "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await producer.ProduceAsync(topic, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -136,9 +153,13 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
var message = new Message<TKey, TValue>
|
var message = new Message<TKey, TValue>
|
||||||
{
|
{
|
||||||
Key = key,
|
Key = key,
|
||||||
Value = value
|
Value = value,
|
||||||
|
Headers = new Headers{
|
||||||
|
{ "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var producer = GetProducer<TKey, TValue>();
|
var typeKey = typeof(KafkaProducer<TKey, TValue>);
|
||||||
|
var producer = GetProducer<TKey, TValue>(typeKey);
|
||||||
if (partition.HasValue)
|
if (partition.HasValue)
|
||||||
{
|
{
|
||||||
var topicPartition = new TopicPartition(topic, partition.Value);
|
var topicPartition = new TopicPartition(topic, partition.Value);
|
||||||
@ -166,9 +187,13 @@ namespace JiShe.CollectBus.Kafka.Producer
|
|||||||
{
|
{
|
||||||
var message = new Message<string, TValue>
|
var message = new Message<string, TValue>
|
||||||
{
|
{
|
||||||
Value = value
|
Value = value,
|
||||||
|
Headers = new Headers{
|
||||||
|
{ "route-key", Encoding.UTF8.GetBytes(_configuration["ServerTagName"]!) }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var producer = GetProducer<string, TValue>();
|
var typeKey = typeof(KafkaProducer<string, TValue>);
|
||||||
|
var producer = GetProducer<string, TValue>(typeKey);
|
||||||
if (partition.HasValue)
|
if (partition.HasValue)
|
||||||
{
|
{
|
||||||
var topicPartition = new TopicPartition(topic, partition.Value);
|
var topicPartition = new TopicPartition(topic, partition.Value);
|
||||||
|
|||||||
@ -92,7 +92,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
|||||||
var bytes = Build3761SendData.BuildSendCommandBytes(reqParam);
|
var bytes = Build3761SendData.BuildSendCommandBytes(reqParam);
|
||||||
//await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId });
|
//await _producerBus.PublishAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId });
|
||||||
|
|
||||||
await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId }.Serialize());
|
await _producerService.ProduceAsync(ProtocolConst.SubscriberLoginIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId });
|
||||||
//await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId });
|
//await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Login, MessageId = messageReceived.MessageId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ namespace JiShe.CollectBus.Protocol.Contracts.Abstracts
|
|||||||
var bytes = Build3761SendData.BuildSendCommandBytes(reqParam);
|
var bytes = Build3761SendData.BuildSendCommandBytes(reqParam);
|
||||||
//await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId });
|
//await _producerBus.PublishAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId });
|
||||||
|
|
||||||
await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId }.Serialize());
|
await _producerService.ProduceAsync(ProtocolConst.SubscriberHeartbeatIssuedEventName, new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId });
|
||||||
|
|
||||||
//await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId });
|
//await _producerBus.Publish(new IssuedEventMessage { ClientId = messageReceived.ClientId, DeviceNo = messageReceived.DeviceNo, Message = bytes, Type = IssuedEventType.Heartbeat, MessageId = messageReceived.MessageId });
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user