v9.10.2019.0101  全面巩固批量Insert/Update/Upsert,支持数据备份、恢复和同步,支持实体列表保存到文件以及加载
大石头 authored at 2019-01-01 13:38:33
3.27 KiB
X
namespace NewLife.Remoting;

/// <summary>加权轮询负载均衡器</summary>
/// <remarks>
/// 加权轮询策略:
/// <list type="bullet">
/// <item><description>按权重分配请求到多个节点,权重越高分配越多</description></item>
/// <item><description>自动屏蔽不可用节点,屏蔽时间过后恢复</description></item>
/// <item><description>支持动态调整权重</description></item>
/// </list>
/// 适用场景:多服务器负载分担,需要按服务器性能分配流量
/// </remarks>
public class WeightedRoundRobinLoadBalancer : LoadBalancerBase
{
    #region 属性
    /// <summary>负载均衡模式</summary>
    public override LoadBalanceMode Mode => LoadBalanceMode.RoundRobin;

    /// <summary>调度索引,当前使用该索引处的服务</summary>
    private volatile Int32 _serverIndex;
    #endregion

    #region 方法
    /// <summary>获取一个服务用于处理请求</summary>
    /// <param name="services">服务列表</param>
    /// <returns>选中的服务节点</returns>
    public override ServiceEndpoint GetService(IList<ServiceEndpoint> services)
    {
        if (services == null || services.Count == 0)
            throw new InvalidOperationException("No available service nodes!");

        EnsureAvailable(services);

        // 加权轮询选择节点
        ServiceEndpoint? svc = null;
        for (var i = 0; i < services.Count; i++)
        {
            svc = services[_serverIndex % services.Count];

            // 权重足够,又没有被屏蔽,就是它了
            // Weight <= 0 表示无限制
            // Index < Weight 表示本轮还有配额
            var hasQuota = svc.Weight <= 0 || svc.Index < svc.Weight || services.Count == 1;
            var isAvailable = svc.IsAvailable();

            if (hasQuota && isAvailable) break;

            // 这个节点用完了配额或被屏蔽,切换下一个
            svc.Index = 0;
            svc = null;
            _serverIndex++;
        }

        // 如果都没有可用节点,默认选第一个
        if (svc == null && services.Count > 0) svc = services[0];
        if (svc == null) throw new InvalidOperationException("No available service nodes!");

        svc.Times++;

        // 计算下一次节点
        svc.Index++;
        if (svc.Index >= svc.Weight && svc.Weight > 0)
        {
            svc.Index = 0;
            _serverIndex++;
        }

        // 防止索引溢出
        if (_serverIndex >= services.Count) _serverIndex = 0;

        return svc;
    }

    /// <summary>归还服务,报告请求结果</summary>
    /// <param name="services">服务列表</param>
    /// <param name="service">归还的服务</param>
    /// <param name="error">异常信息,null表示成功</param>
    public override void PutService(IList<ServiceEndpoint> services, ServiceEndpoint service, Exception? error)
    {
        base.PutService(services, service, error);

        var ex = error;
        while (ex is AggregateException age) ex = age.InnerException;

        // 网络异常时,跳过当前节点继续轮询
        if (ex is HttpRequestException or TaskCanceledException)
        {
            _serverIndex++;
            Log?.Debug("服务节点[{0}]网络异常,跳过该节点", service.Name);
        }
    }
    #endregion
}