集中修复 T8/T9/T11/T15 已实现代码的审查问题: - M2 令牌桶 Lua 改用 Redis TIME(消除多节点时钟偏斜) - M3 after 跨租户标记复查授权 + 无授权告警兜底 - m1 before 审计 MsgSeq/MsgRandom null 防御 - m2 HealthService 加 forceDown 开关,实测 DOWN 真返 503 - m3 countByStatus 排除 done(WHERE status IN (0,1,3)) - m5 配额查询异常不缓存(下次重试) - 附带 app.yml console charset GBK→UTF-8 M1 限流审计/事务收窄暂缓。 Co-Authored-By: Claude <noreply@anthropic.com>
111 lines
3.7 KiB
Java
111 lines
3.7 KiB
Java
package com.imutil.common;
|
||
|
||
import com.imutil.mapper.DistQueueMapper;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.noear.solon.annotation.Component;
|
||
import org.noear.solon.annotation.Inject;
|
||
import redis.clients.jedis.Jedis;
|
||
import redis.clients.jedis.JedisPool;
|
||
|
||
import java.time.OffsetDateTime;
|
||
import java.util.LinkedHashMap;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
|
||
/**
|
||
* 健康检查服务(对齐设计:监控告警 - 中间件连通、dist_queue 堆积)
|
||
* <p>
|
||
* 聚合 PG / Redis 连通性与分发队列状态分布,输出整体 status:
|
||
* - UP:PG + Redis 均连通
|
||
* - DEGRADED:PG 连通、Redis 异常(限流 fail-open、缓存降级,核心链路仍可用)
|
||
* - DOWN:PG 异常(消息落库不可用,核心受损)
|
||
* <p>
|
||
* 队列 pending/processing/dead 计数供运维判断是否需干预:
|
||
* - pending 持续上涨 → worker 处理不过去,考虑扩容
|
||
* - processing 长期不归零且无 done 增长 → 工作线程可能卡死
|
||
* - dead 积累 → 需排查死信或重发
|
||
*
|
||
* @author imutil
|
||
*/
|
||
@Slf4j
|
||
@Component
|
||
public class HealthService {
|
||
|
||
@Inject
|
||
private DistQueueMapper distQueueMapper;
|
||
|
||
@Inject
|
||
private JedisPool jedisPool;
|
||
|
||
/** 运维测试用:强制返回 DOWN,验证 LB/k8s 探针摘流(默认关) */
|
||
@Inject("${imutil.health.forceDown:false}")
|
||
private boolean forceDown;
|
||
|
||
/**
|
||
* 采集健康指标
|
||
*
|
||
* @return 含 status / pg / redis / distQueue / timestamp
|
||
*/
|
||
public Map<String, Object> health() {
|
||
// 运维测试开关:强制 DOWN,验证探针据 503 摘流
|
||
if (forceDown) {
|
||
Map<String, Object> r = new LinkedHashMap<>();
|
||
r.put("status", "DOWN");
|
||
r.put("pg", "DOWN");
|
||
r.put("redis", "UP");
|
||
r.put("distQueue", new LinkedHashMap<>());
|
||
r.put("forced", true);
|
||
r.put("timestamp", OffsetDateTime.now().toString());
|
||
return r;
|
||
}
|
||
Map<String, Object> distQueue = new LinkedHashMap<>();
|
||
boolean pgUp;
|
||
try {
|
||
List<Map<String, Object>> rows = distQueueMapper.countByStatus();
|
||
// 四态计数初始化为 0(无记录的状态不出现在 GROUP BY 结果中)
|
||
long[] cnt = new long[4];
|
||
for (Map<String, Object> row : rows) {
|
||
int st = ((Number) row.get("status")).intValue();
|
||
long c = ((Number) row.get("cnt")).longValue();
|
||
if (st >= 0 && st < 4) {
|
||
cnt[st] = c;
|
||
}
|
||
}
|
||
distQueue.put("pending", cnt[0]);
|
||
distQueue.put("processing", cnt[1]);
|
||
distQueue.put("dead", cnt[3]);
|
||
// done(status=2) 不统计:无索引且无限增长,健康检查只关心堆积/死信
|
||
pgUp = true;
|
||
} catch (Exception e) {
|
||
log.warn("健康检查 PG 查询失败 : {}", e.getMessage());
|
||
distQueue.put("error", e.getMessage());
|
||
pgUp = false;
|
||
}
|
||
|
||
boolean redisUp;
|
||
try (Jedis j = jedisPool.getResource()) {
|
||
redisUp = "PONG".equals(j.ping());
|
||
} catch (Exception e) {
|
||
log.warn("健康检查 Redis ping 失败 : {}", e.getMessage());
|
||
redisUp = false;
|
||
}
|
||
|
||
String status;
|
||
if (pgUp && redisUp) {
|
||
status = "UP";
|
||
} else if (pgUp) {
|
||
status = "DEGRADED";
|
||
} else {
|
||
status = "DOWN";
|
||
}
|
||
|
||
Map<String, Object> r = new LinkedHashMap<>();
|
||
r.put("status", status);
|
||
r.put("pg", pgUp ? "UP" : "DOWN");
|
||
r.put("redis", redisUp ? "UP" : "DOWN");
|
||
r.put("distQueue", distQueue);
|
||
r.put("timestamp", OffsetDateTime.now().toString());
|
||
return r;
|
||
}
|
||
}
|