2025-04-10 14:12:14 +08:00
|
|
|
|
using JiShe.CollectBus.FreeRedisProvider;
|
|
|
|
|
|
using System;
|
2025-04-09 23:11:36 +08:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2025-04-10 14:12:14 +08:00
|
|
|
|
using Volo.Abp.DependencyInjection;
|
2025-04-09 23:11:36 +08:00
|
|
|
|
|
|
|
|
|
|
namespace JiShe.CollectBus.Common.Helpers
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设备组负载控制
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class DeviceGroupBalanceControl
|
2025-04-10 14:12:14 +08:00
|
|
|
|
{
|
2025-04-09 23:11:36 +08:00
|
|
|
|
/// <summary>
|
2025-04-10 14:12:14 +08:00
|
|
|
|
/// 分组集合
|
2025-04-09 23:11:36 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static List<string>[] _cachedGroups;
|
2025-04-10 14:12:14 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设备分组关系映射
|
|
|
|
|
|
/// </summary>
|
2025-04-09 23:11:36 +08:00
|
|
|
|
private static Dictionary<string, int> _balancedMapping;
|
|
|
|
|
|
|
2025-04-10 14:12:14 +08:00
|
|
|
|
|
2025-04-09 23:11:36 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 初始化缓存并强制均衡
|
|
|
|
|
|
/// </summary>
|
2025-04-10 14:12:14 +08:00
|
|
|
|
public static void InitializeCache(List<string> deviceList,int groupCount = 50)
|
2025-04-09 23:11:36 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 步骤1: 生成均衡映射表
|
2025-04-10 14:12:14 +08:00
|
|
|
|
_balancedMapping = CreateBalancedMapping(deviceList, groupCount);
|
2025-04-09 23:11:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 步骤2: 根据映射表填充分组
|
2025-04-10 14:12:14 +08:00
|
|
|
|
_cachedGroups = new List<string>[groupCount];
|
|
|
|
|
|
for (int i = 0; i < groupCount; i++)
|
2025-04-09 23:11:36 +08:00
|
|
|
|
{
|
2025-04-10 14:12:14 +08:00
|
|
|
|
_cachedGroups[i] = new List<string>(capacity: deviceList.Count / groupCount + 1);
|
2025-04-09 23:11:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var deviceId in deviceList)
|
|
|
|
|
|
{
|
|
|
|
|
|
int groupId = _balancedMapping[deviceId];
|
|
|
|
|
|
_cachedGroups[groupId].Add(deviceId);
|
2025-04-10 14:12:14 +08:00
|
|
|
|
}
|
2025-04-09 23:11:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-04-10 14:12:14 +08:00
|
|
|
|
/// 通过 deviceId 获取所在的分组集合
|
2025-04-09 23:11:36 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static List<string> GetGroup(string deviceId)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_balancedMapping == null || _cachedGroups == null)
|
|
|
|
|
|
throw new InvalidOperationException("缓存未初始化");
|
|
|
|
|
|
|
|
|
|
|
|
int groupId = _balancedMapping[deviceId];
|
|
|
|
|
|
return _cachedGroups[groupId];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-10 14:12:14 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 通过 deviceId 获取分组Id
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static int GetDeviceGroupId(string deviceId)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_balancedMapping == null || _cachedGroups == null)
|
|
|
|
|
|
throw new InvalidOperationException("缓存未初始化");
|
|
|
|
|
|
|
|
|
|
|
|
return _balancedMapping[deviceId];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-09 23:11:36 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 创建均衡映射表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="deviceList">数据集合</param>
|
|
|
|
|
|
/// <param name="groupCount">分组数量</param>
|
|
|
|
|
|
/// <param name="maxDeviation">允许的最大偏差百分比</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static Dictionary<string, int> CreateBalancedMapping(List<string> deviceList, int groupCount, int maxDeviation = 5)
|
|
|
|
|
|
{
|
|
|
|
|
|
var mapping = new Dictionary<string, int>();
|
|
|
|
|
|
int targetPerGroup = deviceList.Count / groupCount;
|
|
|
|
|
|
int maxAllowed = (int)(targetPerGroup * (1 + maxDeviation / 100.0));
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化分组计数器
|
|
|
|
|
|
int[] groupCounters = new int[groupCount];
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var deviceId in deviceList)
|
|
|
|
|
|
{
|
|
|
|
|
|
int preferredGroup = GetGroupId(deviceId, groupCount);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果首选分组未满,直接分配
|
|
|
|
|
|
if (groupCounters[preferredGroup] < maxAllowed)
|
|
|
|
|
|
{
|
|
|
|
|
|
mapping[deviceId] = preferredGroup;
|
|
|
|
|
|
groupCounters[preferredGroup]++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 寻找当前最空闲的分组
|
|
|
|
|
|
int fallbackGroup = Array.IndexOf(groupCounters, groupCounters.Min());
|
|
|
|
|
|
mapping[deviceId] = fallbackGroup;
|
|
|
|
|
|
groupCounters[fallbackGroup]++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return mapping;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 分析分组分布
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="deviceList"></param>
|
|
|
|
|
|
/// <param name="groupCount"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static Dictionary<int, int> AnalyzeDistribution(List<string> deviceList, int groupCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
Dictionary<int, int> distribution = new Dictionary<int, int>();
|
|
|
|
|
|
foreach (var deviceId in deviceList)
|
|
|
|
|
|
{
|
|
|
|
|
|
int groupId = GetGroupId(deviceId, groupCount);
|
|
|
|
|
|
distribution[groupId] = distribution.TryGetValue(groupId, out var count) ? count + 1 : 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
return distribution;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取设备ID对应的分组ID
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="deviceId"></param>
|
|
|
|
|
|
/// <param name="groupCount"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static int GetGroupId(string deviceId, int groupCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
int hash = Fnv1aHash(deviceId);
|
|
|
|
|
|
// 双重取模确保分布均匀
|
|
|
|
|
|
return (hash % groupCount + groupCount) % groupCount;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// FNV-1a哈希算法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static int Fnv1aHash(string input)
|
|
|
|
|
|
{
|
|
|
|
|
|
const uint fnvPrime = 16777619;
|
|
|
|
|
|
const uint fnvOffsetBasis = 2166136261;
|
|
|
|
|
|
|
|
|
|
|
|
uint hash = fnvOffsetBasis;
|
|
|
|
|
|
foreach (char c in input)
|
|
|
|
|
|
{
|
|
|
|
|
|
hash ^= (byte)c;
|
|
|
|
|
|
hash *= fnvPrime;
|
|
|
|
|
|
}
|
|
|
|
|
|
return (int)hash;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 打印分组统计数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static void PrintDistributionStats()
|
|
|
|
|
|
{
|
|
|
|
|
|
var stats = _cachedGroups
|
|
|
|
|
|
.Select((group, idx) => new { GroupId = idx, Count = group.Count })
|
|
|
|
|
|
.OrderBy(x => x.GroupId);
|
|
|
|
|
|
|
|
|
|
|
|
Console.WriteLine("分组数据量统计:");
|
|
|
|
|
|
foreach (var stat in stats)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine($"Group {stat.GroupId}: {stat.Count} 条数据");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|