feat: add Kafka BackgroundService
This commit is contained in:
parent
610afac511
commit
985a7bdf11
108
GeoPulse Pipeline/KafkaConsumer.cs
Normal file
108
GeoPulse Pipeline/KafkaConsumer.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Confluent.Kafka;
|
||||||
|
using Npgsql;
|
||||||
|
|
||||||
|
namespace GeoPulse_Pipeline;
|
||||||
|
|
||||||
|
public class KafkaConsumer : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly ILogger<KafkaConsumer> _logger;
|
||||||
|
private readonly string _connectionString;
|
||||||
|
private readonly string _kafkaHost;
|
||||||
|
|
||||||
|
private const int BatchSize = 1000;
|
||||||
|
private readonly TimeSpan MaxWaitTime = TimeSpan.FromSeconds(2);
|
||||||
|
|
||||||
|
public KafkaConsumer(ILogger<KafkaConsumer> logger, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_connectionString = configuration.GetConnectionString("DefaultConnection")
|
||||||
|
?? throw new ArgumentNullException("資料庫連線字串未設定");
|
||||||
|
_kafkaHost = configuration["KafkaHost"];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var consumerConfig = new ConsumerConfig
|
||||||
|
{
|
||||||
|
BootstrapServers = _kafkaHost,
|
||||||
|
GroupId = "telemetry-db-writer-group",
|
||||||
|
AutoOffsetReset = AutoOffsetReset.Earliest,
|
||||||
|
EnableAutoCommit = false
|
||||||
|
};
|
||||||
|
|
||||||
|
using var consumer = new ConsumerBuilder<Ignore, string>(consumerConfig).Build();
|
||||||
|
consumer.Subscribe("telemetry-events");
|
||||||
|
|
||||||
|
var buffer = new List<ConsumeResult<Ignore, string>>();
|
||||||
|
var lastBatchTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var consumeResult = consumer.Consume(TimeSpan.FromMilliseconds(500));
|
||||||
|
|
||||||
|
if (consumeResult != null)
|
||||||
|
{
|
||||||
|
buffer.Add(consumeResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isBatchFull = buffer.Count >= BatchSize;
|
||||||
|
bool isTimeUp = buffer.Count > 0 && (DateTime.UtcNow - lastBatchTime) >= MaxWaitTime;
|
||||||
|
|
||||||
|
if (isBatchFull || isTimeUp)
|
||||||
|
{
|
||||||
|
await BulkInsertToDatabaseAsync(buffer, stoppingToken);
|
||||||
|
|
||||||
|
var latestOffset = buffer.Last();
|
||||||
|
consumer.Commit(latestOffset);
|
||||||
|
|
||||||
|
_logger.LogInformation($"成功批次寫入 {buffer.Count} 筆資料至 PostgreSQL。");
|
||||||
|
|
||||||
|
buffer.Clear();
|
||||||
|
lastBatchTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"發生錯誤: {ex.Message} \n {ex.StackTrace}");
|
||||||
|
|
||||||
|
buffer.Clear();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
consumer.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task BulkInsertToDatabaseAsync(List<ConsumeResult<Ignore, string>> buffer,
|
||||||
|
CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
var jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
|
await using var dataSource = NpgsqlDataSource.Create(_connectionString);
|
||||||
|
await using var batch = dataSource.CreateBatch();
|
||||||
|
|
||||||
|
foreach (var msg in buffer)
|
||||||
|
{
|
||||||
|
var data = JsonSerializer.Deserialize<TelemetryRequest>(msg.Message.Value, jsonOptions);
|
||||||
|
if (data == null) continue;
|
||||||
|
|
||||||
|
var cmd = batch.CreateBatchCommand();
|
||||||
|
cmd.CommandText =
|
||||||
|
@"INSERT INTO telemetry_history (id, device_id, geom, timestamp) VALUES (gen_random_uuid(), $1, ST_SetSRID(ST_MakePoint($2, $3), 4326), $4)";
|
||||||
|
|
||||||
|
cmd.Parameters.AddWithValue(data.DeviceId ?? string.Empty);
|
||||||
|
cmd.Parameters.AddWithValue(data.Lng);
|
||||||
|
cmd.Parameters.AddWithValue(data.Lat);
|
||||||
|
cmd.Parameters.AddWithValue(data.Timestamp);
|
||||||
|
|
||||||
|
batch.BatchCommands.Add(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
await batch.ExecuteNonQueryAsync(stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user