判题服务巡检,不能只看进程还活着
判题服务的 HTTP 接口能返回成功,并不表示它仍能完成一次判题。队列可能已经堆积,worker 可能卡在编译器或存储上,沙箱可能无法创建,磁盘和文件描述符也可能接近限制。若巡检只检查进程端口,故障会以“服务看起来正常,用户一直超时”的方式出现。
健康检查要服务于运维决策:此刻实例是否还能接新流量,是否需要减少低优先级任务,问题在本机还是共享依赖,是否真的值得重启。把这些问题混成一个布尔值,编排器很难做出正确动作。
分开定义存活、就绪和深度状态
存活检查回答的是进程有没有卡死。它应该轻量、快速,不依赖队列、数据库或外部网络。若存活检查失败,才适合让编排器重启实例。把所有外部依赖故障都塞进存活检查,会导致实例在共享服务故障时反复重启,却无法解决根因。
就绪检查回答的是实例现在能否接收新任务。它可以检查必要的本地初始化是否完成、worker 池是否可用、关键配置是否加载,但不要在每次请求里做昂贵工作。就绪失败时,流量可以转到其他实例或进入等待队列,而不是直接判定进程死亡。
深度巡检则用于告警、观测和降级决策。它会关心队列等待、worker 消费速度、沙箱创建成功率、依赖存储延迟和资源余量。深度巡检的结果不一定要立即把实例摘除;例如共享存储短暂变慢时,保留核心判题、暂停低优先级解释生成,可能比全量下线更合适。
探针本身不能制造新的压力
最差的做法是每次巡检都提交一段真实编译任务,或创建完整沙箱来证明服务可用。正常情况下它会浪费资源,故障时更会与用户任务竞争 CPU、磁盘和 worker。深度探测若需要验证关键路径,应使用无敏感内容的最小样本、严格的频率上限和独立的资源配额。
日志也要克制。探针记录任务标识、阶段、耗时、错误类别和资源状态已经足够;不要把用户代码、标准输入输出或完整编译命令复制进健康日志。判题平台常处理用户提交内容,巡检系统不应成为另一条数据泄露路径。
检查必须有超时。一个卡住的存储读取不能让整个巡检协程永久等待,也不能占满健康检查连接池。失败后用明确状态上报,等待下一次采样或由告警系统处理。
指标要看趋势和关联关系
单次队列增长或一次沙箱失败,可能只是短暂抖动。更有价值的是一段窗口内的等待时间、积压变化、成功率与资源占用是否同步恶化。worker 存活但处理速度接近零,往往说明它卡在外部依赖、长任务或资源争用上;只统计 worker 数量看不出这一点。
可把队列长度、最早任务等待、活跃 worker、任务完成率、沙箱创建耗时、编译超时、磁盘和文件描述符余量放在同一观察面板。指标之间的时间关系会帮助定位:队列先涨而资源稳定,可能是 worker 数不足;沙箱失败与磁盘余量下降同时出现,则更应检查宿主环境。
阈值应来自服务能够承受的等待时间和恢复速度。不要直接复制其他系统的数值。对于批处理任务,较长排队或许可接受;对交互式提交,用户等待更敏感。阈值触发后,也应有抑制和聚合规则,避免一个共享故障造成几百条重复告警。
每种信号都要能落到具体动作
队列持续积压时,可以限流、扩容 worker 或暂停可延迟任务;沙箱创建失败时检查镜像、宿主资源和配额;依赖存储超时时进入受控重试或降级;实例完全无响应时才交给编排器重启。动作要有责任人、恢复条件和退出策略,不能只写“告警后人工处理”。
验证不应等到真实事故。可在测试环境分别模拟队列阻塞、worker 卡住、沙箱无法创建、依赖超时和磁盘接近限制,确认每种场景给出的信号不同,降级不会影响不该受影响的任务,恢复后实例能重新进入就绪状态。
巡检不是多加几个接口,而是让服务在异常中仍然可判断、可处理。把存活、就绪和深度状态分开,控制探针成本,再把信号连接到明确动作,判题服务才不会在表面健康时悄悄失去交付能力。