4. 状态机设计:服务健康状态定义

好,咱们进入正题。服务降级和超时重试,核心就靠一个东西——状态机。说白了,你得让系统知道某个服务现在到底行不行,是活蹦乱跳,还是半死不活,或者已经彻底凉了。

我见过不少团队,上来就写重试逻辑,结果服务都挂了还在疯狂重试,把整个网络都打爆了。嗯,这就是没定义好健康状态。

4.1 三种核心状态

我个人习惯,把服务健康状态分成三档。不多不少,三档刚刚好。多了你管不过来,少了又不够精细。

状态 含义 典型表现
健康 服务正常运行,响应正常 响应时间 < 50ms,无超时
亚健康 服务还能响应,但质量下降 偶发超时,响应时间 > 100ms
不可用 服务完全无响应 连续超时,或收到错误码

你想想看,如果只有「健康」和「不可用」两种状态,那系统就太敏感了。网络稍微抖一下,你就把服务标记成不可用,然后开始降级,这其实没必要。

核心原则:亚健康状态是缓冲带,用来过滤瞬时的网络抖动。别让偶发问题触发大规模降级。

4.2 状态转换逻辑

状态不是乱变的,得有规则。我在项目中遇到过一个问题:某个服务偶尔超时,但很快又恢复了。如果每次超时都直接标记不可用,那整个系统会频繁震荡。

所以,我设计了一套转换逻辑,用计数器和时间窗口来控制。

// 伪代码:状态转换逻辑
enum ServiceState {
    HEALTHY,
    DEGRADED,
    UNAVAILABLE
}

struct ServiceHealth {
    state: ServiceState,
    timeout_count: u32,
    last_check_time: u64,
    degraded_threshold: u32,   // 比如连续3次超时进入亚健康
    unavailable_threshold: u32 // 比如连续5次超时进入不可用
}

fn update_state(health: &mut ServiceHealth, is_timeout: bool) {
    if is_timeout {
        health.timeout_count += 1;
        if health.timeout_count >= health.unavailable_threshold {
            health.state = ServiceState::UNAVAILABLE;
        } else if health.timeout_count >= health.degraded_threshold {
            health.state = ServiceState::DEGRADED;
        }
    } else {
        // 成功响应,逐步恢复
        health.timeout_count = 0;
        if health.state == ServiceState::DEGRADED {
            health.state = ServiceState::HEALTHY;
        }
    }
}

这段逻辑看着简单,但有个坑——恢复条件。我曾经犯过一个错:只要有一次成功响应,就把状态从「不可用」直接拉回「健康」。结果服务其实还在抖动,刚恢复又挂了,反复横跳。

避坑指南:从「不可用」恢复到「健康」,必须经过「亚健康」状态。也就是说,连续成功N次后,先变成亚健康,再连续成功M次,才能回到健康。这叫「慢启动恢复」。

4.3 状态机流程图

光说理论不够直观,我画了一张状态机图。你看一眼就明白了。

SOME/IP 服务健康状态机 服务健康状态机 健康 (HEALTHY) 亚健康 (DEGRADED) 不可用 (UNAVAILABLE) 连续超时 ≥ N次 连续超时 ≥ M次 连续成功 ≥ K次 连续成功 ≥ L次 说明:N、M、K、L 为可配置参数。一般建议 N=3, M=5, K=3, L=5。具体值根据业务容忍度调整。

这张图里,箭头方向就是状态流转的方向。注意看,从「不可用」到「健康」,必须经过「亚健康」。这个设计我吃了不少苦头才悟出来的。

4.4 参数配置建议

参数怎么配?我直接给出一组经验值。你在实际项目中可以微调。

参数 含义 推荐值 说明
N 健康→亚健康的超时次数 3 连续3次超时,说明网络或服务开始不稳定
M 亚健康→不可用的超时次数 5 连续5次超时,基本可以断定服务挂了
K 亚健康→健康的成功次数 3 连续3次成功,说明服务恢复了
L 不可用→亚健康的成功次数 5 连续5次成功,才允许回到亚健康

我的经验:如果服务是核心功能(比如制动控制),建议把阈值设低一点,宁可误判也不让故障扩散。如果是非核心功能(比如娱乐系统),阈值可以设高一些,避免频繁降级影响用户体验。

4.5 代码实现片段

最后,给出一段 C++ 的实现。这段代码我直接用在项目里过,你可以参考。

#include <cstdint>
#include <chrono>

class ServiceHealthStateMachine {
public:
    enum class State { HEALTHY, DEGRADED, UNAVAILABLE };

    ServiceHealthStateMachine(
        uint32_t degraded_threshold = 3,
        uint32_t unavailable_threshold = 5,
        uint32_t recover_to_healthy = 3,
        uint32_t recover_to_degraded = 5
    ) : state_(State::HEALTHY),
        timeout_count_(0),
        success_count_(0),
        degraded_threshold_(degraded_threshold),
        unavailable_threshold_(unavailable_threshold),
        recover_to_healthy_(recover_to_healthy),
        recover_to_degraded_(recover_to_degraded) {}

    void on_timeout() {
        success_count_ = 0;
        timeout_count_++;

        switch (state_) {
            case State::HEALTHY:
                if (timeout_count_ >= degraded_threshold_) {
                    state_ = State::DEGRADED;
                    timeout_count_ = 0;
                }
                break;
            case State::DEGRADED:
                if (timeout_count_ >= unavailable_threshold_) {
                    state_ = State::UNAVAILABLE;
                    timeout_count_ = 0;
                }
                break;
            case State::UNAVAILABLE:
                // 不可用状态下,继续计数,但不改变状态
                break;
        }
    }

    void on_success() {
        timeout_count_ = 0;
        success_count_++;

        switch (state_) {
            case State::DEGRADED:
                if (success_count_ >= recover_to_healthy_) {
                    state_ = State::HEALTHY;
                    success_count_ = 0;
                }
                break;
            case State::UNAVAILABLE:
                if (success_count_ >= recover_to_degraded_) {
                    state_ = State::DEGRADED;
                    success_count_ = 0;
                }
                break;
            case State::HEALTHY:
                // 健康状态下,保持
                break;
        }
    }

    State get_state() const { return state_; }

private:
    State state_;
    uint32_t timeout_count_;
    uint32_t success_count_;
    uint32_t degraded_threshold_;
    uint32_t unavailable_threshold_;
    uint32_t recover_to_healthy_;
    uint32_t recover_to_degraded_;
};

这段代码的核心逻辑就是:超时累加,成功清零。你可能会问,为什么成功要清零超时计数?因为一次成功响应,说明服务至少还能工作,之前的超时可能是网络抖动,不应该累积。

记住:状态机不是越复杂越好。三态模型,配合计数器,已经能覆盖 90% 的场景。别过度设计。