Преглед изворни кода

fix(etl): 死实例行不再按槽位豁免清理,也不再显示为执行机

上一提交把旧模型的「保住被指派实例行」规则原样搬了过来,但那条例外的唯一
理由是指派寄生在 is_runner 上、删行等于删指派。指派独立成表后它只剩害处:
执行机槽位每重启留一行且永不清理,重启最勤的那个槽位无限堆积;监控页的
isRunner 又是按槽位派生的,于是一排早已死掉的实例全部显示成执行机,
把「零台存活执行机」这个本该刺眼的故障重新伪装成一切正常。

- CleanupDeadAsync 去掉槽位豁免,超过保留期一视同仁清理
- 监控页行级 isRunner 加上 alive 前提;「指派槽位无活实例」仍由
  noLiveRunner 告警表达,不靠留尸体来提示

零回归:3213 个测试,7 个失败 = 既有基线原样。

chore: bump version server 1.0.590
Co-authored-by: Cursor <cursoragent@cursor.com>
YY968XX пре 2 дана
родитељ
комит
85acd840f2

+ 5 - 5
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk.Web">
+<Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
     <TargetFrameworks>net10.0</TargetFrameworks>
@@ -10,10 +10,10 @@
     <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
-    <Description>Admin.NET 通用��开�平�</Description>
-    <AssemblyVersion>1.0.589</AssemblyVersion>
-    <FileVersion>1.0.589</FileVersion>
-    <Version>1.0.589</Version>
+    <Description>Admin.NET ͨÓÃȨÏÞ¿ª·¢Æ½Ì¨</Description>
+    <AssemblyVersion>1.0.590</AssemblyVersion>
+    <FileVersion>1.0.590</FileVersion>
+    <Version>1.0.590</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 8 - 11
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/EtlInstanceRegistrar.cs

@@ -36,7 +36,7 @@ public sealed class EtlInstanceRegistrar : BackgroundService
     /// <summary>启动延时:等宿主配置与建表脚本就绪,避免首拍必然失败刷一条 warn。</summary>
     private static readonly TimeSpan StartupDelay = TimeSpan.FromSeconds(5);
 
-    /// <summary>下线实例行的保留期。超期且不在被指派槽位上的行会被清理,防止表随重启无限增长。</summary>
+    /// <summary>下线实例行的保留期。超期即清理,防止表随重启无限增长。</summary>
     private static readonly TimeSpan DeadInstanceRetention = TimeSpan.FromDays(7);
 
     /// <summary>存活判据:连续三拍没心跳即视为下线,与 <see cref="AidopRunnerState.FreshnessWindow"/> 同宽。</summary>
@@ -232,12 +232,14 @@ public sealed class EtlInstanceRegistrar : BackgroundService
     }
 
     /// <summary>
-    /// 清理早已下线的实例行。**被指派槽位上的行即使心跳超期也保留**——
-    /// 运维需要看到「指派槽位上那台机器挂了」,而不是记录凭空消失。
+    /// 清理早已下线的实例行,不分槽位一视同仁。
     ///
-    /// <para>注意这里保留的只是**观测线索**:指派本身存在
-    /// <c>ado_etl_runner_designation</c>,即使这些行被清掉也不会丢指派。
-    /// 旧模型正相反——指派就寄生在实例行上,所以那时必须靠这个过滤条件兜着。</para>
+    /// <para>旧模型必须特意保住被指派实例的行,因为指派就寄生在
+    /// <c>ado_etl_instance.is_runner</c> 上,删行等于删指派。现在指派独立存在
+    /// <c>ado_etl_runner_designation</c>,这条例外不但没用,还有害:执行机槽位
+    /// 每重启一次留一行,恰恰是重启最勤的那个槽位无限堆积,页面上还会摆着一排
+    /// 早已死掉的同槽位实例。运维要看的「指派槽位上没有活实例」由监控页的
+    /// noLiveRunner 告警给出,不需要靠留尸体来表达。</para>
     /// </summary>
     private async Task CleanupDeadAsync(CancellationToken ct)
     {
@@ -245,14 +247,9 @@ public sealed class EtlInstanceRegistrar : BackgroundService
         {
             using var scope = _scopeFactory.CreateScope();
             var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
-            var instances = scope.ServiceProvider.GetRequiredService<IEtlInstanceStore>();
-            var designated = await instances.GetDesignatedSlotAsync(ct);
             var cutoff = DateTime.Now - DeadInstanceRetention;
             var removed = await db.Deleteable<AdoEtlInstance>()
                 .Where(x => x.LastHeartbeatAt < cutoff)
-                // 必须显式放行 slot_code IS NULL:SQL 里 NULL <> 'x' 求值为 NULL 而非真,
-                // 少了这一支,未声明槽位的开发机死行会永远清不掉。
-                .WhereIF(designated != null, x => x.SlotCode == null || x.SlotCode != designated)
                 .ExecuteCommandAsync(ct);
             if (removed > 0)
                 _logger.LogInformation("[EtlInstanceRegistrar] 已清理下线实例 {Count} 行", removed);

+ 18 - 12
server/Plugins/Admin.NET.Plugin.AiDOP/Order/MdpMonitorService.cs

@@ -317,19 +317,25 @@ public class MdpMonitorService : IDynamicApiController, ITransient
             })
             .ToListAsync();
 
-        var rows = instances.Select(x => new
+        var rows = instances.Select(x =>
         {
-            x.InstanceId,
-            x.MachineName,
-            x.ProcessId,
-            x.AppVersion,
-            x.SlotCode,
-            x.StartedAt,
-            x.LastHeartbeatAt,
-            alive = x.LastHeartbeatAt >= heartbeatCutoff,
-            self = x.InstanceId == AidopInstanceIdentity.InstanceId,
-            // 派生值,不再读 ado_etl_instance.is_runner(已降级为遗留列)
-            isRunner = designatedSlot != null && x.SlotCode == designatedSlot
+            var alive = x.LastHeartbeatAt >= heartbeatCutoff;
+            return new
+            {
+                x.InstanceId,
+                x.MachineName,
+                x.ProcessId,
+                x.AppVersion,
+                x.SlotCode,
+                x.StartedAt,
+                x.LastHeartbeatAt,
+                alive,
+                self = x.InstanceId == AidopInstanceIdentity.InstanceId,
+                // 派生值,不再读 ado_etl_instance.is_runner(已降级为遗留列)。
+                // 必须带 alive:被指派槽位上的历史死行若也显示成执行机,就把
+                // 「零台存活执行机」这个本该刺眼的故障重新伪装成一切正常。
+                isRunner = alive && designatedSlot != null && x.SlotCode == designatedSlot
+            };
         }).ToList();
 
         var liveOnSlot = designatedSlot == null