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;
|
});
|
}
|
}
|