EventHub在订阅之前,支持注册topic和注销topic
石头 authored at 2025-12-20 17:05:02
4.35 KiB
X
using System.Collections.Concurrent;
using System.Net;
using NewLife.Log;

namespace NewLife.Net;

/// <summary>DNS解析器</summary>
public interface IDnsResolver
{
    /// <summary>解析域名</summary>
    /// <param name="host"></param>
    /// <returns></returns>
    IPAddress[]? Resolve(String host);
}

/// <summary>DNS解析器,带有缓存,解析失败时使用旧数据</summary>
public class DnsResolver : IDnsResolver
{
    /// <summary>静态实例</summary>
    public static DnsResolver Instance { get; set; } = new();

    /// <summary>缓存超时时间</summary>
    public TimeSpan Expire { set; get; } = TimeSpan.FromMinutes(5);

    private readonly ConcurrentDictionary<String, DnsItem> _cache = new();
    private readonly ConcurrentDictionary<String, Byte> _refreshing = new(); // 刷新去重

    /// <summary>解析域名</summary>
    /// <param name="host"></param>
    /// <returns></returns>
    public IPAddress[]? Resolve(String host)
    {
        if (host.IsNullOrEmpty()) return null;

        if (_cache.TryGetValue(host, out var item))
        {
            // 超时数据,异步更新,不影响当前请求
            if (item.UpdateTime.Add(Expire) <= DateTime.Now)
            {
                if (_refreshing.TryAdd(host, 0)) _ = ResolveCoreAsync(host, item, false);
            }
        }
        else
        {
            // 首次解析同步等待(保持现有同步接口语义)
            item = ResolveCoreAsync(host, null, true).ConfigureAwait(false).GetAwaiter().GetResult();
        }

        return item?.Addresses;
    }

    /// <summary>异步解析/刷新核心</summary>
    /// <param name="host"></param>
    /// <param name="item"></param>
    /// <param name="throwError"></param>
    /// <returns></returns>
    async Task<DnsItem?> ResolveCoreAsync(String host, DnsItem? item, Boolean throwError)
    {
        using var span = DefaultTracer.Instance?.NewSpan($"dns:{host}");
        try
        {
            // 执行DNS解析
#if NET6_0_OR_GREATER
            using var source = new CancellationTokenSource(5000);
            var addrs = await Dns.GetHostAddressesAsync(host, source.Token).ConfigureAwait(false);
#else
            var task = Dns.GetHostAddressesAsync(host);
            if (!task.Wait(5000)) throw new TaskCanceledException();
            var addrs = task.ConfigureAwait(false).GetAwaiter().GetResult();
#endif
            span?.AppendTag($"addrs={addrs.Join(",")}");
            if (addrs != null && addrs.Length > 0)
            {
                span?.Value = addrs.Length;

                // 更新缓存数据
                if (item == null)
                {
                    _cache[host] = item = new DnsItem
                    {
                        Host = host,
                        Addresses = addrs,
                        CreateTime = DateTime.Now,
                        UpdateTime = DateTime.Now
                    };
                }
                else
                {
                    item.Addresses = addrs;
                    item.UpdateTime = DateTime.Now;

                    span?.AppendTag($"CreateTime={item.CreateTime.ToFullString()}");
                }
            }
        }
        catch (Exception ex)
        {
            if (item != null) return item; // 保留旧值

            span?.SetError(ex, null);

            if (throwError) throw;
        }
        finally
        {
            // 结束刷新标记
            _refreshing.TryRemove(host, out _);
        }

        return item;
    }

    /// <summary>设置缓存</summary>
    /// <remarks>一般用于单元测试,或者局部篡改DNS解析。受Expire影响,过期后仍然刷新</remarks>
    /// <param name="host">域名</param>
    /// <param name="addrs">IP地址集合</param>
    /// <param name="expire">过期时间。默认0秒,使用Exire</param>
    public void Set(String host, IPAddress[] addrs, Int32 expire = 0)
    {
        var item = new DnsItem
        {
            Host = host,
            Addresses = addrs,
            CreateTime = DateTime.Now,
            UpdateTime = DateTime.Now.AddSeconds(expire)
        };
        _cache[host] = item;
    }

    class DnsItem
    {
        public String Host { get; set; } = null!;

        public IPAddress[]? Addresses { get; set; }

        public DateTime CreateTime { get; set; }

        public DateTime UpdateTime { get; set; }
    }
}