public Task <List <UserData> > GetAllAsync() { var db = _redis.RedisConn.GetDatabase(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); return(db.HashGetAllAsync(_redisKeyUserByLoginName).ContinueWith(t => { stopwatch.Stop(); string text = $"{nameof(ReadOnlyUserDataRedis)}的redis方法HashGetAllAsync耗时 {stopwatch.GetElapsedSeconds().ToString("f2")} 秒"; NTMinerConsole.UserInfo(text); stopwatch.Restart(); List <UserData> list = new List <UserData>(); foreach (var item in t.Result) { if (item.Value.HasValue) { UserData data = VirtualRoot.JsonSerializer.Deserialize <UserData>(item.Value); if (data != null) { list.Add(data); } } } stopwatch.Stop(); NTMinerConsole.UserInfo($"反序列化和装配UserData列表耗时 {stopwatch.GetElapsedSeconds().ToString("f2")} 秒"); return list; })); }
public Task <List <KeyValuePair <Guid, string> > > GetAllAsync() { var db = _redis.RedisConn.GetDatabase(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); return(db.HashGetAllAsync(_redisKeyMinerIdByClientId).ContinueWith(t => { stopwatch.Stop(); string text = $"{nameof(MinerIdRedis)}的redis方法HashGetAllAsync耗时 {stopwatch.GetElapsedSeconds().ToString("f2")} 秒"; NTMinerConsole.UserInfo(text); stopwatch.Restart(); List <KeyValuePair <Guid, string> > list = new List <KeyValuePair <Guid, string> >(); foreach (var item in t.Result) { if (item.Value.HasValue && Guid.TryParse(item.Name, out Guid clientId)) { string minerId = item.Value; if (!string.IsNullOrEmpty(minerId)) { list.Add(new KeyValuePair <Guid, string>(clientId, minerId)); } } } stopwatch.Stop(); NTMinerConsole.UserInfo($"装配MinerId列表耗时 {stopwatch.GetElapsedSeconds().ToString("f2")} 秒"); return list; })); }
public Task <Dictionary <string, DateTime> > GetAllAsync() { var db = _redis.RedisConn.GetDatabase(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); return(db.HashGetAllAsync(_redisKeyClientActiveOnById).ContinueWith(t => { stopwatch.Stop(); string text = $"{nameof(ClientActiveOnRedis)}的redis方法HashGetAllAsync耗时 {stopwatch.GetElapsedSeconds().ToString("f2")} 秒"; NTMinerConsole.UserInfo(text); stopwatch.Restart(); Dictionary <string, DateTime> dic = new Dictionary <string, DateTime>(); foreach (var item in t.Result) { if (item.Value.HasValue) { // 为了在redis-cli的易读性而存字符串 if (!DateTime.TryParse(item.Value, out DateTime activeOn)) { activeOn = DateTime.MinValue; } dic.Add(item.Name, activeOn); } } stopwatch.Stop(); NTMinerConsole.UserInfo($"装配ClientActiveOn字典耗时 {stopwatch.GetElapsedSeconds().ToString("f2")} 秒"); return dic; })); }
public void AutoStart() { bool isAutoStart = (MinerProfile.IsAutoStart || CommandLineArgs.IsAutoStart); if (isAutoStart && !this.MinerProfile.IsMining) { NTMinerConsole.UserInfo($"{MinerProfile.AutoStartDelaySeconds.ToString()}秒后开始挖矿"); this.MinerProfile.IsMining = true; IMessagePathId pathId = null; pathId = VirtualRoot.BuildViaTimesLimitPath <Per1SecondEvent>("自动开始挖矿倒计时", LogEnum.None, viaTimesLimit: MinerProfile.AutoStartDelaySeconds, location: this.GetType(), PathPriority.Normal, path: message => { if (!NTMinerContext.IsAutoStartCanceled) { MineBtnText = $"倒计时{pathId.ViaTimesLimit.ToString()}"; } if (pathId.ViaTimesLimit == 0) { if (!NTMinerContext.IsAutoStartCanceled) { VirtualRoot.ThisLocalInfo(nameof(StartStopMineButtonViewModel), $"自动开始挖矿", toConsole: true); NTMinerContext.Instance.StartMine(); } } }); } }
public Task <Dictionary <Guid, SpeedData> > GetAllAsync() { var db = _redis.RedisConn.GetDatabase(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); return(db.HashGetAllAsync(_redisKeySpeedDataByClientId).ContinueWith(t => { stopwatch.Stop(); string text = $"{nameof(SpeedDataRedis)}的redis方法HashGetAllAsync耗时 {stopwatch.GetElapsedSeconds().ToString("f2")} 秒"; NTMinerConsole.UserInfo(text); stopwatch.Restart(); Dictionary <Guid, SpeedData> dic = new Dictionary <Guid, SpeedData>(); foreach (var item in t.Result) { if (item.Value.HasValue) { SpeedData data = VirtualRoot.JsonSerializer.Deserialize <SpeedData>(item.Value); if (data != null) { dic.Add(data.ClientId, data); } } } stopwatch.Stop(); NTMinerConsole.UserInfo($"反序列化和装配SpeedData列表耗时 {stopwatch.GetElapsedSeconds().ToString("f2")} 秒"); return dic; })); }
private void ContinueCreateProcess() { Thread.Sleep(1000); if (this != NTMinerContext.Instance.LockedMineContext) { NTMinerConsole.UserWarn("结束开始挖矿"); return; } // 执行文件书写器 this.ExecuteFileWriters(); // 分离命令名和参数 GetCmdNameAndArguments(out string kernelExeFileFullName, out string arguments); // 这是不应该发生的,如果发生很可能是填写命令的时候拼写错误了 if (!File.Exists(kernelExeFileFullName)) { NTMinerConsole.UserError(kernelExeFileFullName + "文件不存在,可能是被杀软删除导致,请退出杀毒软件重试或者QQ群联系小编,解释:大部分挖矿内核会报毒,不是开源矿工的问题也不是杀软的问题,也不是挖矿内核的问题,是挖矿这件事情的问题,可能是挖矿符合了病毒的定义。"); } if (this.KernelProcessType == KernelProcessType.Logfile) { arguments = arguments.Replace(NTKeyword.LogFileParameterName, this.LogFileFullName); } NTMinerConsole.UserOk($"\"{kernelExeFileFullName}\" {arguments}"); NTMinerConsole.UserInfo($"有请内核上场"); if (this != NTMinerContext.Instance.LockedMineContext) { NTMinerConsole.UserWarn("结束开始挖矿"); return; } NTMinerConsole.InitOnece(isForce: true, initHide: !NTMinerContext.IsUiVisible); switch (this.KernelProcessType) { case KernelProcessType.Logfile: CreateLogfileProcess(kernelExeFileFullName, arguments); break; case KernelProcessType.Pip: CreatePipProcess(kernelExeFileFullName, arguments); break; default: throw new InvalidProgramException(); } this.ProcessCreatedOn = DateTime.Now; KernelProcessDaemon(); VirtualRoot.RaiseEvent(new MineStartedEvent(this)); }
public WsServerNodeSet(IWsServerNodeMqSender mqSender) { _mqSender = mqSender; VirtualRoot.AddEventPath <Per20SecondEvent>("周期将不活跃的节点移除", LogEnum.DevConsole, action: message => { DateTime activeOn = DateTime.Now.AddSeconds(-message.Seconds); var toRemoves = _dicByIp.Values.Where(a => a.ActiveOn < activeOn).ToArray(); foreach (var item in toRemoves) { this.RemoveNode(item.Address); } if (toRemoves.Length != 0) { NTMinerConsole.UserInfo($"移除了 {toRemoves.Length.ToString()} 个节点"); } }, this.GetType()); }
public Task <List <SpeedData> > GetAllAsync() { var db = _redis.RedisConn.GetDatabase(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); return(db.HashGetAllAsync(_redisKeySpeedDataByClientId).ContinueWith(t => { stopwatch.Stop(); long seconds = stopwatch.ElapsedMilliseconds / 1000; string text = $"{nameof(SpeedDataRedis)}的redis方法HashGetAllAsync耗时 {seconds.ToString()} 秒"; // 从redis一次性加载几十兆数据没有什么问题,打印些统计信息出来以待将来有问题时容易发现 if (seconds > 5) { NTMinerConsole.UserWarn(text); } else { NTMinerConsole.UserInfo(text); } stopwatch.Start(); List <SpeedData> list = new List <SpeedData>(); foreach (var item in t.Result) { if (item.Value.HasValue) { SpeedData data = VirtualRoot.JsonSerializer.Deserialize <SpeedData>(item.Value); if (data != null) { list.Add(data); } } } stopwatch.Stop(); seconds = stopwatch.ElapsedMilliseconds / 1000; NTMinerConsole.UserInfo($"反序列化和装配耗时 {seconds.ToString()} 秒"); return list; })); }
public void ShowInfo(string message, string header, int autoHideSeconds, bool toConsole = false) { if (toConsole) { NTMinerConsole.UserInfo(message); } UIThread.Execute(() => { var builder = NotificationMessageBuilder.CreateMessage(Manager); builder.Info(header, message ?? string.Empty); if (autoHideSeconds > 0) { builder .Dismiss() .WithDelay(autoHideSeconds) .Queue(); } else { builder .Dismiss().WithButton("知道了", null) .Queue(); } }); }
private void DoSave() { if (string.IsNullOrEmpty(this.Name)) { VirtualRoot.Out.ShowError("作业名称是必须的", autoHideSeconds: 4); } bool isMinerProfileChanged = false; // 表示是否随后打开编辑界面,如果是新建的作业则保存后随即会打开作业编辑界面 bool isShowEdit = false; MineWorkData mineWorkData = new MineWorkData().Update(this); if (MinerStudioRoot.MineWorkVms.TryGetMineWorkVm(this.Id, out MineWorkViewModel vm)) { string sha1 = NTMinerContext.Instance.MinerProfile.GetSha1(); // 如果作业设置变更了则一定变更了 if (this.Sha1 != sha1) { isMinerProfileChanged = true; } else { // 如果作业设置没变更但作业引用的服务器数据库记录状态变更了则变更了 LocalJsonDb localJsonObj = new LocalJsonDb(NTMinerContext.Instance, mineWorkData); ServerJsonDb serverJsonObj = new ServerJsonDb(NTMinerContext.Instance, localJsonObj); var serverJson = VirtualRoot.JsonSerializer.Serialize(serverJsonObj); sha1 = HashUtil.Sha1(serverJson); if (sha1 != this.ServerJsonSha1) { isMinerProfileChanged = true; } } } else { isMinerProfileChanged = true; isShowEdit = true; NTMinerConsole.UserInfo("保存作业。"); } if (RpcRoot.IsOuterNet) { if (!this.Id.IsSelfMineWorkId()) { RpcRoot.OfficialServer.UserMineWorkService.AddOrUpdateMineWorkAsync(new MineWorkData().Update(this), (r, ex) => { if (r.IsSuccess()) { if (isMinerProfileChanged) { NTMinerContext.Instance.ExportWorkJson(mineWorkData, out string localJson, out string serverJson); if (!string.IsNullOrEmpty(localJson) && !string.IsNullOrEmpty(serverJson)) { RpcRoot.OfficialServer.UserMineWorkService.ExportMineWorkAsync(this.Id, localJson, serverJson, (response, e) => { if (response.IsSuccess()) { if (isShowEdit) { VirtualRoot.RaiseEvent(new MineWorkAddedEvent(Guid.Empty, this)); } else { VirtualRoot.RaiseEvent(new MineWorkUpdatedEvent(Guid.Empty, this)); } if (isShowEdit) { this.Edit.Execute(FormType.Edit); } } else { VirtualRoot.Out.ShowError(response.ReadMessage(e), autoHideSeconds: 4); } }); } if (mineWorkData.ServerJsonSha1 != this.ServerJsonSha1) { this.ServerJsonSha1 = mineWorkData.ServerJsonSha1; } } } else { VirtualRoot.Out.ShowError(r.ReadMessage(ex), autoHideSeconds: 4); } });
public ClientDataSet( IMinerDataRedis minerRedis, IClientActiveOnRedis clientActiveOnRedis, ISpeedDataRedis speedDataRedis, IMinerClientMqSender mqSender) : base(isPull: false, getDatas: callback => { var getMinersTask = minerRedis.GetAllAsync(); var getClientActiveOnsTask = clientActiveOnRedis.GetAllAsync(); var getSpeedsTask = speedDataRedis.GetAllAsync(); Task.WhenAll(getMinersTask, getClientActiveOnsTask, getSpeedsTask).ContinueWith(t => { NTMinerConsole.UserInfo($"从redis加载了 {getMinersTask.Result.Count} 条MinerData,和 {getSpeedsTask.Result.Count} 条SpeedData"); Dictionary <Guid, SpeedData> speedDataDic = getSpeedsTask.Result; Dictionary <string, DateTime> clientActiveOnDic = getClientActiveOnsTask.Result; List <ClientData> clientDatas = new List <ClientData>(); DateTime speedOn = DateTime.Now.AddMinutes(-3); foreach (var minerData in getMinersTask.Result) { var clientData = ClientData.Create(minerData); if (clientActiveOnDic.TryGetValue(minerData.Id, out DateTime activeOn)) { clientData.MinerActiveOn = activeOn; } clientDatas.Add(clientData); if (speedDataDic.TryGetValue(minerData.ClientId, out SpeedData speedData) && speedData.SpeedOn > speedOn) { clientData.Update(speedData, out bool _); } } callback?.Invoke(clientDatas); }); }) { _minerRedis = minerRedis; _clientActiveOnRedis = clientActiveOnRedis; _speedDataRedis = speedDataRedis; _mqSender = mqSender; VirtualRoot.BuildEventPath <Per100MinuteEvent>("周期将矿机的MinerActiveOn或NetActiveOn时间戳持久到redis", LogEnum.DevConsole, message => { var minerCients = _dicByObjectId.Values.ToArray(); DateTime time = message.BornOn.AddSeconds(-message.Seconds); int count = 0; foreach (var minerClient in minerCients) { // 如果活跃则更新到redis,否则就不更新了 DateTime activeOn = minerClient.GetActiveOn(); if (activeOn > time) { clientActiveOnRedis.SetAsync(minerClient.Id, activeOn); count++; } } NTMinerConsole.DevWarn($"{count.ToString()} 条活跃矿机的时间戳被持久化"); }, this.GetType()); // 上面的持久化时间戳到redis的目的主要是为了下面那个周期找出不活跃的矿机记录删除掉的逻辑能够在重启WebApiServer服务进程后不中断 VirtualRoot.BuildEventPath <Per2MinuteEvent>("周期找出用不活跃的矿机记录删除掉", LogEnum.DevConsole, message => { var clientDatas = _dicByObjectId.Values.ToArray(); Dictionary <string, List <ClientData> > dicByMACAddress = new Dictionary <string, List <ClientData> >(); DateTime minerClientExpireTime = message.BornOn.AddDays(-7); // 因为SpeedData尺寸较大,时效性较短,可以比CientData更早删除 DateTime minerSpeedExpireTime = message.BornOn.AddMinutes(-3); int count = 0; foreach (var clientData in clientDatas) { DateTime activeOn = clientData.GetActiveOn(); // 如果7天都没有活跃了 if (activeOn <= minerClientExpireTime) { RemoveByObjectId(clientData.Id); count++; } else if (activeOn <= minerSpeedExpireTime) { _speedDataRedis.DeleteByClientIdAsync(clientData.ClientId); } else if (!string.IsNullOrEmpty(clientData.MACAddress)) { if (!dicByMACAddress.TryGetValue(clientData.MACAddress, out List <ClientData> list)) { list = new List <ClientData>(); dicByMACAddress.Add(clientData.MACAddress, list); } list.Add(clientData); } } if (count > 0) { NTMinerConsole.DevWarn($"{count.ToString()} 条不活跃的矿机记录被删除"); } List <string> toRemoveIds = new List <string>(); foreach (var kv in dicByMACAddress) { if (kv.Value.Count > 1) { toRemoveIds.AddRange(kv.Value.Select(a => a.Id)); } } if (toRemoveIds.Count > 0) { count = 0; foreach (var id in toRemoveIds) { RemoveByObjectId(id); count++; } NTMinerConsole.DevWarn($"{count.ToString()} 条MAC地址重复的矿机记录被删除"); } NTMinerConsole.DevDebug($"QueryClients平均耗时 {AverageQueryClientsMilliseconds.ToString()} 毫秒"); }, this.GetType()); // 收到Mq消息之前一定已经初始化完成,因为Mq消费者在ClientSetInitedEvent事件之后才会创建 VirtualRoot.BuildEventPath <SpeedDatasMqEvent>("收到SpeedDatasMq消息后更新ClientData内存", LogEnum.None, path: message => { if (message.AppId == ServerRoot.HostConfig.ThisServerAddress) { return; } if (message.ClientIdIps == null || message.ClientIdIps.Length == 0) { return; } if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(nameof(SpeedDatasMqEvent) + ":" + MqKeyword.SafeIgnoreMessage); return; } speedDataRedis.GetByClientIdsAsync(message.ClientIdIps.Select(a => a.ClientId).ToArray()).ContinueWith(t => { if (t.Result != null && t.Result.Length != 0) { foreach (var item in t.Result) { string minerIp = string.Empty; var clientIdIp = message.ClientIdIps.FirstOrDefault(a => a.ClientId == item.ClientId); if (clientIdIp != null) { minerIp = clientIdIp.MinerIp; } ReportSpeed(item.SpeedDto, minerIp, isFromWsServerNode: true); } } }); }, this.GetType()); VirtualRoot.BuildEventPath <MinerClientWsClosedMqEvent>("收到MinerClientWsClosedMq消息后更新NetActiveOn和IsOnline", LogEnum.None, path: message => { if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(nameof(MinerClientWsClosedMqEvent) + ":" + MqKeyword.SafeIgnoreMessage); return; } if (_dicByClientId.TryGetValue(message.ClientId, out ClientData clientData)) { clientData.NetActiveOn = message.Timestamp; clientData.IsOnline = false; } }, this.GetType()); VirtualRoot.BuildEventPath <MinerClientsWsBreathedMqEvent>("收到MinerClientsWsBreathedMq消息后更新NetActiveOn", LogEnum.None, path: message => { if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(nameof(MinerClientsWsBreathedMqEvent) + ":" + MqKeyword.SafeIgnoreMessage); return; } if (message.ClientIds != null && message.ClientIds.Length != 0) { foreach (var clientId in message.ClientIds) { if (_dicByClientId.TryGetValue(clientId, out ClientData clientData)) { clientData.NetActiveOn = message.Timestamp; clientData.IsOnline = true; } } } }, this.GetType()); VirtualRoot.BuildEventPath <MinerSignSetedMqEvent>("更新内存中的MinerData的MinerSign部分", LogEnum.None, path: message => { if (_dicByObjectId.TryGetValue(message.Data.Id, out ClientData clientData)) { clientData.Update(message.Data); } else { clientData = ClientData.Create(message.Data); Add(clientData); } clientData.NetActiveOn = DateTime.Now; clientData.IsOnline = true; clientData.IsOuterUserEnabled = true; }, this.GetType()); VirtualRoot.BuildCmdPath <QueryClientsForWsMqCommand>(path: message => { QueryClientsResponse response = AppRoot.QueryClientsForWs(message.Query); _mqSender.SendResponseClientsForWs(message.AppId, message.LoginName, message.SessionId, message.MqMessageId, response); }, this.GetType(), LogEnum.None); }
public void Route <TMessage>(TMessage message) where TMessage : IMessage { if (message == null) { throw new ArgumentNullException(nameof(message)); } MessagePath <TMessage>[] messagePaths = PathSetSet.GetMessagePathSet <TMessage>().GetMessagePaths(); if (messagePaths.Length == 0) { Type messageType = typeof(TMessage); MessageTypeAttribute messageTypeAttr = MessageTypeAttribute.GetMessageTypeAttribute(messageType); if (!messageTypeAttr.IsCanNoPath) { NTMinerConsole.DevWarn(() => messageType.FullName + "类型的消息没有对应的处理器"); } } else { foreach (var messagePath in messagePaths) { try { bool canGo = false; if (message is IEvent evt) { canGo = evt.TargetPathId == PathId.Empty || // 事件不是特定路径的事件则放行 messagePath.PathId == PathId.Empty || // 路径不是特定事件的路径则放行 evt.TargetPathId == messagePath.PathId; // 路径是特定事件的路径且路径和事件造型放行 } else if (message is ICmd cmd) { // 路径不是特定命令的路径则放行 if (messagePath.PathId == PathId.Empty) { canGo = true; } else { canGo = messagePath.PathId == cmd.MessageId; } } if (canGo && messagePath.ViaTimesLimit > 0) { // ViaTimesLimite小于0表示是不限定通过的次数的路径,不限定通过的次数的路径不需要消息每通过一次递减一次ViaTimesLimit计数 messagePath.DecreaseViaTimesLimit(onDownToZero: RemovePath); } if (!messagePath.IsEnabled) { continue; } if (canGo) { switch (messagePath.LogType) { case LogEnum.DevConsole: if (DevMode.IsDevMode) { NTMinerConsole.DevDebug(() => $"({typeof(TMessage).Name})->({messagePath.Location.Name}){messagePath.Description}"); } break; case LogEnum.UserConsole: NTMinerConsole.UserInfo($"({typeof(TMessage).Name})->({messagePath.Location.Name}){messagePath.Description}"); break; case LogEnum.Log: Logger.InfoDebugLine($"({typeof(TMessage).Name})->({messagePath.Location.Name}){messagePath.Description}"); break; case LogEnum.None: default: break; } messagePath.Go(message); } } catch (Exception e) { Logger.ErrorDebugLine(e); } } } }
public ClientDataSet(IMinerRedis minerRedis, ISpeedDataRedis speedDataRedis, IMinerClientMqSender mqSender) : base(isPull: false, getDatas: callback => { var getMinersTask = minerRedis.GetAllAsync(); var getSpeedsTask = speedDataRedis.GetAllAsync(); Task.WhenAll(getMinersTask, getSpeedsTask).ContinueWith(t => { NTMinerConsole.UserInfo($"从redis加载了 {getMinersTask.Result.Count} 条MinerData,和 {getSpeedsTask.Result.Count} 条SpeedData"); var speedDatas = getSpeedsTask.Result; List <ClientData> clientDatas = new List <ClientData>(); DateTime speedOn = DateTime.Now.AddMinutes(-3); foreach (var minerData in getMinersTask.Result) { var clientData = ClientData.Create(minerData); // 该属性没有持久化而只在内存中,启动时将该属性值视为当前日期的前一天的零时加上CreatedOn的时间,别处有个周期清理7天不活跃矿机的任务 clientData.MinerActiveOn = DateTime.Today.AddDays(-1) + minerData.CreatedOn.TimeOfDay; clientDatas.Add(clientData); var speedData = speedDatas.FirstOrDefault(a => a.ClientId == minerData.ClientId); if (speedData != null && speedData.SpeedOn > speedOn) { clientData.Update(speedData, out bool _); } } callback?.Invoke(clientDatas); }); }) { _minerRedis = minerRedis; _speedDataRedis = speedDataRedis; _mqSender = mqSender; VirtualRoot.BuildEventPath <Per1MinuteEvent>("周期清理Redis中不活跃的来自挖矿端上报的算力记录", LogEnum.DevConsole, path: message => { DateTime time = message.BornOn.AddSeconds(-130); var toRemoveSpeed = _dicByClientId.Where(a => a.Value.MinerActiveOn != DateTime.MinValue && a.Value.MinerActiveOn <= time).ToArray(); _speedDataRedis.DeleteByClientIdsAsync(toRemoveSpeed.Select(a => a.Key).ToArray()); // 删除一周没有活跃过的客户端 time = message.BornOn.AddDays(-7); var toRemoveClient = _dicByObjectId.Where(a => a.Value.MinerActiveOn <= time).ToArray(); foreach (var kv in toRemoveClient) { base.RemoveByObjectId(kv.Key); } }, this.GetType()); // 收到Mq消息之前一定已经初始化完成,因为Mq消费者在ClientSetInitedEvent事件之后才会创建 VirtualRoot.BuildEventPath <SpeedDataMqMessage>("收到SpeedDataMq消息后更新ClientData内存", LogEnum.None, path: message => { if (message.AppId == ServerRoot.HostConfig.ThisServerAddress) { return; } if (message.ClientId == Guid.Empty) { return; } if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(_safeIgnoreMessage); return; } speedDataRedis.GetByClientIdAsync(message.ClientId).ContinueWith(t => { ReportSpeed(t.Result.SpeedDto, message.MinerIp, isFromWsServerNode: true); }); }, this.GetType()); VirtualRoot.BuildEventPath <MinerClientWsOpenedMqMessage>("收到MinerClientWsOpenedMq消息后更新NetActiveOn和IsOnline", LogEnum.None, path: message => { if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(_safeIgnoreMessage); return; } if (_dicByClientId.TryGetValue(message.ClientId, out ClientData clientData)) { clientData.NetActiveOn = message.Timestamp; clientData.IsOnline = true; } }, this.GetType()); VirtualRoot.BuildEventPath <MinerClientWsClosedMqMessage>("收到MinerClientWsClosedMq消息后更新NetActiveOn和IsOnline", LogEnum.None, path: message => { if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(_safeIgnoreMessage); return; } if (_dicByClientId.TryGetValue(message.ClientId, out ClientData clientData)) { clientData.NetActiveOn = message.Timestamp; clientData.IsOnline = false; } }, this.GetType()); VirtualRoot.BuildEventPath <MinerClientWsBreathedMqMessage>("收到MinerClientWsBreathedMq消息后更新NetActiveOn", LogEnum.None, path: message => { if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(_safeIgnoreMessage); return; } if (_dicByClientId.TryGetValue(message.ClientId, out ClientData clientData)) { clientData.NetActiveOn = message.Timestamp; clientData.IsOnline = true; } }, this.GetType()); VirtualRoot.BuildCmdPath <ChangeMinerSignMqMessage>(path: message => { if (_dicByObjectId.TryGetValue(message.Data.Id, out ClientData clientData)) { clientData.Update(message.Data, out bool isChanged); if (isChanged) { var minerData = MinerData.Create(clientData); _minerRedis.SetAsync(minerData).ContinueWith(t => { _mqSender.SendMinerSignChanged(minerData.Id); }); } } else { // 此时该矿机是第一次在服务端出现 NTMinerConsole.UserWarn("该矿机首次出现于WsServer:" + VirtualRoot.JsonSerializer.Serialize(message.Data)); clientData = ClientData.Create(MinerData.Create(message.Data)); clientData.NetActiveOn = DateTime.Now; clientData.IsOnline = true; clientData.IsOuterUserEnabled = true; Add(clientData); } }, this.GetType(), LogEnum.None); }
public ClientDataSet(IMinerRedis minerRedis, ISpeedDataRedis speedDataRedis, IMinerClientMqSender mqSender) : base(isPull: false, getDatas: callback => { var getMinersTask = minerRedis.GetAllAsync(); var getSpeedsTask = speedDataRedis.GetAllAsync(); Task.WhenAll(getMinersTask, getSpeedsTask).ContinueWith(t => { NTMinerConsole.UserInfo($"从redis加载了 {getMinersTask.Result.Count} 条MinerData,和 {getSpeedsTask.Result.Count} 条SpeedData"); var speedDatas = getSpeedsTask.Result; List <ClientData> clientDatas = new List <ClientData>(); DateTime speedOn = DateTime.Now.AddMinutes(-3); foreach (var minerData in getMinersTask.Result) { var clientData = ClientData.Create(minerData); // 该属性没有持久化而只在内存中,启动时将该属性值视为当前日期的前一天的零时加上CreatedOn // 的时间从而将数据分散开来从而滑动清理,后面有个周期清理7天不活跃矿机的任务。WebApiServer启动后第6天才会进行第一次清理。 clientData.MinerActiveOn = DateTime.Today.AddDays(-1) + minerData.CreatedOn.TimeOfDay; clientDatas.Add(clientData); var speedData = speedDatas.FirstOrDefault(a => a.ClientId == minerData.ClientId); if (speedData != null && speedData.SpeedOn > speedOn) { clientData.Update(speedData, out bool _); } } callback?.Invoke(clientDatas); }); }) { _minerRedis = minerRedis; _speedDataRedis = speedDataRedis; _mqSender = mqSender; VirtualRoot.BuildEventPath <Per1MinuteEvent>("周期清理Redis中不活跃的来自挖矿端上报的算力记录", LogEnum.DevConsole, path: message => { DateTime time = message.BornOn.AddSeconds(-130); var toRemoveSpeed = _dicByClientId.Where(a => a.Value.MinerActiveOn != DateTime.MinValue && a.Value.MinerActiveOn <= time).ToArray(); _speedDataRedis.DeleteByClientIdsAsync(toRemoveSpeed.Select(a => a.Key).ToArray()); // 删除一段时间没有活跃过的客户端 const int nDay = 7; DateTime netActiveOn = message.BornOn.AddSeconds(-30); time = message.BornOn.AddDays(-nDay); var toRemoveClient = _dicByObjectId.Where(a => a.Value.MinerActiveOn <= time && a.Value.NetActiveOn <= netActiveOn).ToArray(); if (toRemoveClient.Length > 0) { NTMinerConsole.UserOk($"{toRemoveClient.Length.ToString()} 台矿机因 {nDay.ToString()} 天没有活跃,删除对应记录"); foreach (var kv in toRemoveClient) { base.RemoveByObjectId(kv.Key); } } }, this.GetType()); // 收到Mq消息之前一定已经初始化完成,因为Mq消费者在ClientSetInitedEvent事件之后才会创建 VirtualRoot.BuildEventPath <SpeedDataMqMessage>("收到SpeedDataMq消息后更新ClientData内存", LogEnum.None, path: message => { if (message.AppId == ServerRoot.HostConfig.ThisServerAddress) { return; } if (message.ClientId == Guid.Empty) { return; } if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(nameof(SpeedDataMqMessage) + ":" + MqKeyword.SafeIgnoreMessage); return; } speedDataRedis.GetByClientIdAsync(message.ClientId).ContinueWith(t => { ReportSpeed(t.Result.SpeedDto, message.MinerIp, isFromWsServerNode: true); }); }, this.GetType()); VirtualRoot.BuildEventPath <MinerClientWsOpenedMqMessage>("收到MinerClientWsOpenedMq消息后更新NetActiveOn和IsOnline", LogEnum.None, path: message => { if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(nameof(MinerClientWsOpenedMqMessage) + ":" + MqKeyword.SafeIgnoreMessage); return; } if (_dicByClientId.TryGetValue(message.ClientId, out ClientData clientData)) { clientData.NetActiveOn = message.Timestamp; clientData.IsOnline = true; } }, this.GetType()); VirtualRoot.BuildEventPath <MinerClientWsClosedMqMessage>("收到MinerClientWsClosedMq消息后更新NetActiveOn和IsOnline", LogEnum.None, path: message => { if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(nameof(MinerClientWsClosedMqMessage) + ":" + MqKeyword.SafeIgnoreMessage); return; } if (_dicByClientId.TryGetValue(message.ClientId, out ClientData clientData)) { clientData.NetActiveOn = message.Timestamp; clientData.IsOnline = false; } }, this.GetType()); VirtualRoot.BuildEventPath <MinerClientWsBreathedMqMessage>("收到MinerClientWsBreathedMq消息后更新NetActiveOn", LogEnum.None, path: message => { if (IsOldMqMessage(message.Timestamp)) { NTMinerConsole.UserOk(nameof(MinerClientWsBreathedMqMessage) + ":" + MqKeyword.SafeIgnoreMessage); return; } if (_dicByClientId.TryGetValue(message.ClientId, out ClientData clientData)) { clientData.NetActiveOn = message.Timestamp; clientData.IsOnline = true; } }, this.GetType()); VirtualRoot.BuildCmdPath <ChangeMinerSignMqMessage>(path: message => { if (_dicByObjectId.TryGetValue(message.Data.Id, out ClientData clientData)) { clientData.Update(message.Data, out bool isChanged); if (isChanged) { var minerData = MinerData.Create(clientData); _minerRedis.SetAsync(minerData).ContinueWith(t => { _mqSender.SendMinerSignChanged(minerData.Id); }); } } else { clientData = ClientData.Create(MinerData.Create(message.Data)); Add(clientData); } clientData.NetActiveOn = DateTime.Now; clientData.IsOnline = true; clientData.IsOuterUserEnabled = true; }, this.GetType(), LogEnum.None); VirtualRoot.BuildCmdPath <QueryClientsForWsMqMessage>(path: message => { QueryClientsResponse response = AppRoot.QueryClientsForWs(message.Query); _mqSender.SendResponseClientsForWs(message.AppId, message.LoginName, message.SessionId, response); }, this.GetType(), LogEnum.None); }