187 lines
7.4 KiB
C#
Raw Normal View History

2025-06-06 14:15:31 +08:00
using JiShe.CollectBus.Common;
2025-04-29 23:55:53 +08:00
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
2025-04-30 12:36:54 +08:00
using System.Diagnostics;
using System.Threading.Channels;
using Volo.Abp.DependencyInjection;
2025-04-29 23:55:53 +08:00
namespace JiShe.CollectBus.DataChannels
{
/// <summary>
/// 数据通道管理服务
/// </summary>
2025-04-30 12:36:54 +08:00
public class DataChannelManageService : IDataChannelManageService, ITransientDependency
2025-04-29 23:55:53 +08:00
{
private readonly ILogger<DataChannelManageService> _logger;
2025-06-06 14:15:31 +08:00
private readonly IoTDBSessionPoolProvider _dbProvider;
2025-04-29 23:55:53 +08:00
private readonly IProducerService _producerService;
private readonly KafkaOptionConfig _kafkaOptions;
2025-06-06 14:15:31 +08:00
private readonly ServerApplicationOptions _applicationOptions;
2025-04-29 23:55:53 +08:00
public DataChannelManageService(
ILogger<DataChannelManageService> logger,
2025-06-06 14:15:31 +08:00
IoTDBSessionPoolProvider dbProvider,
2025-04-29 23:55:53 +08:00
IProducerService producerService,
IOptions<KafkaOptionConfig> kafkaOptions,
2025-06-06 14:15:31 +08:00
IOptions<ServerApplicationOptions> applicationOptions)
2025-04-29 23:55:53 +08:00
{
_logger = logger;
2025-05-18 16:04:23 +08:00
_dbProvider = dbProvider;
2025-04-29 23:55:53 +08:00
_producerService = producerService;
_kafkaOptions = kafkaOptions.Value;
2025-06-06 14:15:31 +08:00
_applicationOptions = applicationOptions.Value;
2025-04-29 23:55:53 +08:00
}
2025-04-30 12:36:54 +08:00
/// <summary>
/// 定时任务数据通道写入
/// </summary>
/// <returns></returns>
public async Task ScheduledMeterTaskWriterAsync(ChannelWriter<ValueTuple<string, List<MeterReadingTelemetryPacketInfo>>> _telemetryPacketInfoWriter, ValueTuple<string, List<MeterReadingTelemetryPacketInfo>> dataItems)
2025-04-30 12:36:54 +08:00
{
await _telemetryPacketInfoWriter.WriteAsync(dataItems);
}
/// <summary>
/// 定时任务数据入库和Kafka推送通道
/// </summary>
public async Task ScheduledMeterTaskReadingAsync(
ChannelReader<ValueTuple<string, List<MeterReadingTelemetryPacketInfo>>> telemetryPacketInfoReader)
2025-04-30 12:36:54 +08:00
{
const int BatchSize = 50000;
2025-05-12 11:20:50 +08:00
const int EmptyWaitMilliseconds = 50;
2025-05-18 16:04:23 +08:00
var timeout = TimeSpan.FromMilliseconds(50);
var timer = Stopwatch.StartNew();
long timeoutMilliseconds = 0;
var metadata = await _dbProvider.GetMetadata<MeterReadingTelemetryPacketInfo>();
2025-05-18 16:04:23 +08:00
var timeoutStopwatch = Stopwatch.StartNew();
try
2025-04-30 12:36:54 +08:00
{
while (true)
2025-04-30 12:36:54 +08:00
{
var batch = new List<ValueTuple<string, List<MeterReadingTelemetryPacketInfo>>>();
var canRead = telemetryPacketInfoReader.Count;
if (canRead <= 0)
{
if (timeoutMilliseconds > 0)
{
_logger.LogError($"{nameof(ScheduledMeterTaskReadingAsync)} 任务数据通道处理数据耗时{timeoutMilliseconds}毫秒");
}
timeoutMilliseconds = 0;
2025-05-12 11:20:50 +08:00
//无消息时短等待50毫秒
await Task.Delay(EmptyWaitMilliseconds);
continue;
}
timer.Restart();
2025-05-18 16:04:23 +08:00
timeoutStopwatch.Restart();
try
2025-04-30 12:36:54 +08:00
{
// 异步批量读取数据
2025-05-18 16:04:23 +08:00
while (batch != null && batch.Count < BatchSize && timeoutStopwatch.Elapsed <= timeout)
{
try
{
if (telemetryPacketInfoReader.TryRead(out var dataItem))
{
batch.Add(dataItem);
}
}
catch (Exception)
{
throw;
}
}
2025-04-30 12:36:54 +08:00
}
catch (Exception)
{
throw;
}
2025-05-18 16:04:23 +08:00
2025-04-30 12:36:54 +08:00
if (batch.Count == 0)
{
await Task.Delay(EmptyWaitMilliseconds);
continue;
}
2025-04-30 12:36:54 +08:00
// 按TopicName分组处理
var topicGroups = new Dictionary<string, List<MeterReadingTelemetryPacketInfo>>();
foreach (var (topicName, records) in batch)
{
if (!topicGroups.TryGetValue(topicName, out var list))
2025-04-30 12:36:54 +08:00
{
list = new List<MeterReadingTelemetryPacketInfo>();
topicGroups[topicName] = list;
2025-04-30 12:36:54 +08:00
}
list.AddRange(records);
}
// 处理每个分组
foreach (var (topicName, records) in topicGroups)
{
try
{
// 批量写入数据库
2025-05-18 16:04:23 +08:00
await _dbProvider.GetSessionPool(true).BatchInsertAsync(metadata, records);
// 限流推送Kafka
await DeviceGroupBalanceControl.ProcessWithThrottleAsync(
items: records,
deviceIdSelector: data => data.DeviceId,
processor: async (data, groupIndex) =>
await KafkaProducerIssuedMessageAction(topicName, data, groupIndex)
);
}
catch (Exception ex)
{
_logger.LogError(ex, "数据通道处理主题 {TopicName} 数据时发生异常", topicName);
}
}
batch.Clear();
timer.Stop();
2025-05-18 16:04:23 +08:00
timeoutStopwatch.Stop();
timeoutMilliseconds = timeoutMilliseconds + timer.ElapsedMilliseconds;
2025-04-30 12:36:54 +08:00
}
}
catch (Exception ex)
{
_logger.LogCritical(ex, "定时任务处理发生致命错误");
throw;
2025-04-30 12:36:54 +08:00
}
}
/// <summary>
/// Kafka推送消息(增加重试机制和参数校验)
2025-04-30 12:36:54 +08:00
/// </summary>
protected async Task KafkaProducerIssuedMessageAction<T>(
string topicName,
T taskRecord,
int partition) where T : class
2025-04-30 12:36:54 +08:00
{
if (string.IsNullOrWhiteSpace(topicName) || taskRecord == null)
{
throw new Exception($"{nameof(KafkaProducerIssuedMessageAction)} 推送消息失败,参数异常,-101");
}
const int maxRetries = 3;//重试次数
for (int retry = 0; retry < maxRetries; retry++)
{
try
{
await _producerService.ProduceAsync(topicName, taskRecord, partition);
return;
}
catch (Exception ex)
{
2025-05-06 15:21:31 +08:00
_logger.LogWarning(ex, "Kafka推送{topicName}主题分区{partition}重试中({Retry}/{MaxRetries})", topicName, partition, retry + 1, maxRetries);
if (retry == maxRetries - 1) throw;
await Task.Delay(1000 * (retry + 1));
}
}
2025-06-06 14:15:31 +08:00
}
2025-04-29 23:55:53 +08:00
}
}