Administrator
2026-08-13 a23f23570935850192099133509e90c33769d26c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.xcong.excoin.modules.station.registry;
 
import com.xcong.excoin.modules.station.model.HeartbeatMsg;
import com.xcong.excoin.modules.station.model.InstanceInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
 
/**
 * JAR 实例注册表 — 心跳更新,超时自动清理。
 */
@Slf4j
@Component
public class InstanceRegistry {
 
    /** 30s 心跳 × 2 = 60s 超时下线 */
    private static final long TIMEOUT_MS = 60_000;
 
    private final ConcurrentHashMap<String, InstanceInfo> registry = new ConcurrentHashMap<>();
 
    /**
     * 心跳更新(不存在则新增)
     */
    public void update(String apiKeyMd5, HeartbeatMsg msg) {
        InstanceInfo info = InstanceInfo.builder()
                .apiKeyMd5(apiKeyMd5)
                .contract(msg.getContract())
                .state(msg.getState())
                .leverage(msg.getLeverage())
                .currentRound(msg.getCurrentRound())
                .cumulativePnl(msg.getCumulativePnl())
                .principal(msg.getPrincipal())
                .hostPort(msg.getHostPort())
                .lastSeen(System.currentTimeMillis())
                .build();
        InstanceInfo old = registry.put(apiKeyMd5, info);
        if (old == null) {
            log.info("[Station] 实例上线, apiKeyMd5={}, contract={}", apiKeyMd5, msg.getContract());
        }
    }
 
    /**
     * 更新状态(用于 ACK)
     */
    public void updateState(String apiKeyMd5, String newState) {
        InstanceInfo info = registry.get(apiKeyMd5);
        if (info != null) {
            info.setState(newState);
            info.setLastSeen(System.currentTimeMillis());
        }
    }
 
    /**
     * 获取单个实例
     */
    public InstanceInfo get(String apiKeyMd5) {
        return registry.get(apiKeyMd5);
    }
 
    /**
     * 所有在线实例
     */
    public Collection<InstanceInfo> list() {
        return registry.values();
    }
 
    /**
     * 每 30s 清理超时实例
     */
    @Scheduled(fixedRate = 30_000)
    public void cleanDead() {
        long now = System.currentTimeMillis();
        registry.entrySet().removeIf(entry -> {
            boolean dead = now - entry.getValue().getLastSeen() > TIMEOUT_MS;
            if (dead) {
                log.info("[Station] 实例离线, apiKeyMd5={}, 最后心跳:{}ms前",
                        entry.getKey(), now - entry.getValue().getLastSeen());
            }
            return dead;
        });
    }
}