2025-04-09 14:33:20 +08:00
|
|
|
|
using Confluent.Kafka;
|
|
|
|
|
|
using Confluent.Kafka.Admin;
|
2025-04-21 10:17:40 +08:00
|
|
|
|
using Microsoft.Extensions.Configuration;
|
2025-04-09 14:33:20 +08:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Volo.Abp.DependencyInjection;
|
|
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
namespace JiShe.CollectBus.Kafka.AdminClient;
|
|
|
|
|
|
|
|
|
|
|
|
public class AdminClientService : IAdminClientService, IDisposable, ISingletonDependency
|
2025-04-09 14:33:20 +08:00
|
|
|
|
{
|
2025-04-21 10:17:40 +08:00
|
|
|
|
private readonly ILogger<AdminClientService> _logger;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes a new instance of the <see cref="AdminClientService" /> class.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="configuration"></param>
|
|
|
|
|
|
/// <param name="logger"></param>
|
|
|
|
|
|
public AdminClientService(IConfiguration configuration, ILogger<AdminClientService> logger)
|
2025-04-09 14:33:20 +08:00
|
|
|
|
{
|
2025-04-21 10:17:40 +08:00
|
|
|
|
_logger = logger;
|
|
|
|
|
|
Instance = GetInstance(configuration);
|
|
|
|
|
|
}
|
2025-04-09 14:33:20 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets or sets the instance.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <value>
|
|
|
|
|
|
/// The instance.
|
|
|
|
|
|
/// </value>
|
|
|
|
|
|
public IAdminClient Instance { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 创建Kafka主题
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="topic"></param>
|
|
|
|
|
|
/// <param name="numPartitions"></param>
|
|
|
|
|
|
/// <param name="replicationFactor"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task CreateTopicAsync(string topic, int numPartitions, short replicationFactor)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
2025-04-09 14:33:20 +08:00
|
|
|
|
{
|
2025-04-21 10:17:40 +08:00
|
|
|
|
if (await CheckTopicAsync(topic)) return;
|
2025-04-09 14:33:20 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
|
|
|
|
|
|
await Instance.CreateTopicsAsync(new[]
|
2025-04-09 14:33:20 +08:00
|
|
|
|
{
|
2025-04-21 10:17:40 +08:00
|
|
|
|
new TopicSpecification
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = topic,
|
|
|
|
|
|
NumPartitions = numPartitions,
|
|
|
|
|
|
ReplicationFactor = replicationFactor
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-04-09 14:33:20 +08:00
|
|
|
|
}
|
2025-04-21 10:17:40 +08:00
|
|
|
|
catch (CreateTopicsException e)
|
2025-04-09 14:33:20 +08:00
|
|
|
|
{
|
2025-04-21 10:17:40 +08:00
|
|
|
|
if (e.Results[0].Error.Code != ErrorCode.TopicAlreadyExists) throw;
|
2025-04-09 14:33:20 +08:00
|
|
|
|
}
|
2025-04-21 10:17:40 +08:00
|
|
|
|
}
|
2025-04-09 14:33:20 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 删除Kafka主题
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="topic"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task DeleteTopicAsync(string topic)
|
|
|
|
|
|
{
|
|
|
|
|
|
await Instance.DeleteTopicsAsync(new[] { topic });
|
|
|
|
|
|
}
|
2025-04-14 16:41:41 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取Kafka主题列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task<List<string>> ListTopicsAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(10));
|
|
|
|
|
|
return new List<string>(metadata.Topics.Select(t => t.Topic));
|
|
|
|
|
|
}
|
2025-04-14 09:29:12 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 判断Kafka主题是否存在
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="topic"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task<bool> TopicExistsAsync(string topic)
|
|
|
|
|
|
{
|
|
|
|
|
|
var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(10));
|
|
|
|
|
|
return metadata.Topics.Any(t => t.Topic == topic);
|
|
|
|
|
|
}
|
2025-04-14 09:29:12 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检测分区是否存在
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="topic"></param>
|
|
|
|
|
|
/// <param name="partitions"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public Dictionary<int, bool> CheckPartitionsExists(string topic, int[] partitions)
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = new Dictionary<int, bool>();
|
|
|
|
|
|
var metadata = Instance.GetMetadata(topic, TimeSpan.FromSeconds(10));
|
|
|
|
|
|
if (metadata.Topics.Count == 0)
|
|
|
|
|
|
return partitions.ToDictionary(p => p, p => false);
|
|
|
|
|
|
var existingPartitions = metadata.Topics[0].Partitions.Select(p => p.PartitionId).ToHashSet();
|
|
|
|
|
|
return partitions.ToDictionary(p => p, p => existingPartitions.Contains(p));
|
|
|
|
|
|
}
|
2025-04-14 09:29:12 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检测分区是否存在
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="topic"></param>
|
|
|
|
|
|
/// <param name="targetPartition"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public bool CheckPartitionsExist(string topic, int targetPartition)
|
|
|
|
|
|
{
|
|
|
|
|
|
var metadata = Instance.GetMetadata(topic, TimeSpan.FromSeconds(10));
|
|
|
|
|
|
if (metadata.Topics.Count == 0)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
var partitions = metadata.Topics[0].Partitions;
|
|
|
|
|
|
return partitions.Any(p => p.PartitionId == targetPartition);
|
|
|
|
|
|
}
|
2025-04-09 14:33:20 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取主题的分区数量
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="topic"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public int GetTopicPartitionsNum(string topic)
|
|
|
|
|
|
{
|
|
|
|
|
|
var metadata = Instance.GetMetadata(topic, TimeSpan.FromSeconds(10));
|
|
|
|
|
|
if (metadata.Topics.Count == 0)
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
return metadata.Topics[0].Partitions.Count;
|
|
|
|
|
|
}
|
2025-04-12 15:11:18 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
Instance?.Dispose();
|
|
|
|
|
|
}
|
2025-04-12 15:11:18 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the instance.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="configuration">The configuration.</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public IAdminClient GetInstance(IConfiguration configuration)
|
|
|
|
|
|
{
|
|
|
|
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(configuration["Kafka:EnableAuthorization"]);
|
|
|
|
|
|
var enableAuthorization = bool.Parse(configuration["Kafka:EnableAuthorization"]!);
|
|
|
|
|
|
var adminClientConfig = new AdminClientConfig
|
2025-04-12 15:11:18 +08:00
|
|
|
|
{
|
2025-04-21 10:17:40 +08:00
|
|
|
|
BootstrapServers = configuration["Kafka:BootstrapServers"]
|
|
|
|
|
|
};
|
|
|
|
|
|
if (enableAuthorization)
|
2025-04-14 19:10:27 +08:00
|
|
|
|
{
|
2025-04-21 10:17:40 +08:00
|
|
|
|
adminClientConfig.SecurityProtocol = SecurityProtocol.SaslPlaintext;
|
|
|
|
|
|
adminClientConfig.SaslMechanism = SaslMechanism.Plain;
|
|
|
|
|
|
adminClientConfig.SaslUsername = configuration["Kafka:SaslUserName"];
|
|
|
|
|
|
adminClientConfig.SaslPassword = configuration["Kafka:SaslPassword"];
|
2025-04-14 19:10:27 +08:00
|
|
|
|
}
|
2025-04-21 10:17:40 +08:00
|
|
|
|
return new AdminClientBuilder(adminClientConfig).Build();
|
|
|
|
|
|
}
|
2025-04-14 19:10:27 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Checks the topic asynchronous.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="topic">The topic.</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task<bool> CheckTopicAsync(string topic)
|
|
|
|
|
|
{
|
|
|
|
|
|
var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(5));
|
|
|
|
|
|
return await Task.FromResult(metadata.Topics.Exists(a => a.Topic == topic));
|
|
|
|
|
|
}
|
2025-04-14 19:10:27 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 判断Kafka主题是否存在
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="topic">主题名称</param>
|
|
|
|
|
|
/// <param name="numPartitions">副本数量,不能高于Brokers数量</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public async Task<bool> CheckTopicAsync(string topic, int numPartitions)
|
|
|
|
|
|
{
|
|
|
|
|
|
var metadata = Instance.GetMetadata(TimeSpan.FromSeconds(5));
|
|
|
|
|
|
if (numPartitions > metadata.Brokers.Count)
|
|
|
|
|
|
throw new Exception($"{nameof(CheckTopicAsync)} 主题检查时,副本数量大于了节点数量。");
|
2025-04-16 09:54:21 +08:00
|
|
|
|
|
2025-04-21 10:17:40 +08:00
|
|
|
|
return await Task.FromResult(metadata.Topics.Exists(a => a.Topic == topic));
|
2025-04-09 14:33:20 +08:00
|
|
|
|
}
|
2025-04-21 10:17:40 +08:00
|
|
|
|
}
|