Skip to content

稳定性设计

1. 这是什么

稳定性设计是指系统面对异常流量、依赖故障、资源不足和局部失效时,仍能保持核心能力可用的一套设计方法。
它关注的是系统在坏情况下如何尽量不失控。

一句话理解:

  • 稳定性设计不是为了“永不出错”
  • 而是为了“出错时别把整套系统一起拖下去”

2. 为什么重要

真实系统一定会遇到故障。
稳定性设计做得越早,系统面对突发问题时就越可控。

最常见的稳定性事故通常都不是单点功能 bug,而是:

  • 流量过高
  • 下游变慢
  • 重试风暴
  • 资源被打满

所以稳定性设计本质上是在做:

  • 风险控制
  • 影响范围控制

3. 先建立直觉:稳定性不是一个开关,而是一组保护动作

很多人会把稳定性理解成:

  • “加个限流”

其实远远不够。
真正的稳定性体系通常由这些动作组合起来:

  • 限流
  • 超时
  • 重试
  • 熔断
  • 降级
  • 隔离

它们解决的问题并不相同。

4. 核心内容

4.1 限流

限流解决的是:

  • 流量超过系统承载能力时,如何控制入口压力

它的本质不是“不让别人用”,而是:

  • 用一部分请求被拒绝,换整个系统不崩

4.2 超时控制

超时控制解决的是:

  • 依赖慢了以后,不能一直傻等

如果没有超时:

  • 线程会被长时间占用
  • 上游请求会继续堆积

4.3 重试策略

重试适合:

  • 临时性失败

但重试必须有边界,因为如果失败是持续性的,再重试只会:

  • 放大压力
  • 雪上加霜

4.4 熔断

熔断解决的是:

  • 当某个依赖明显持续失败时,先临时切断调用,避免继续拖垮系统

它更像电路里的保险丝。

4.5 降级

降级解决的是:

  • 核心功能优先,非核心能力可以暂时退化

例如:

  • 推荐服务挂了,但核心下单还能继续
  • 详情页评论区暂时不展示,但商品主信息仍可访问

4.6 隔离

隔离解决的是:

  • 一个模块出问题,不要把别的模块也拖死

常见隔离方式包括:

  • 独立线程池
  • 独立连接池
  • 独立资源配额

5. 学习重点

这一章最重要的是掌握:

  • 稳定性设计的核心是控制影响范围
  • 限流、超时、重试、熔断、降级、隔离各自解决不同问题
  • 重试不是无脑增强手段
  • 降级和熔断本质上都是系统自我保护

6. 常见问题

6.1 没有限流导致流量高峰把服务打垮

入口没有保护时,系统会直接被高峰流量压穿。

6.2 依赖慢却没有超时控制

下游一慢,上游线程就会被拖住,问题很快扩散。

6.3 失败就无限重试导致雪上加霜

这是非常典型的稳定性反模式。

7. 动手验证

这一节我用一个纯 Java demo,把“限流、有限重试、熔断后降级”这几个关键动作直接跑出来。

7.1 准备一个可运行示例

新建文件 StabilityDesignDemo.java,内容如下:

java
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

public class StabilityDesignDemo {
    static class SimpleRateLimiter {
        private final Semaphore semaphore;

        SimpleRateLimiter(int permits) {
            this.semaphore = new Semaphore(permits);
        }

        boolean tryAcquire() {
            return semaphore.tryAcquire();
        }

        void release() {
            semaphore.release();
        }
    }

    static class RetryExecutor {
        String executeWithRetry(int maxAttempts, Task task) {
            for (int i = 1; i <= maxAttempts; i++) {
                try {
                    return task.run(i);
                } catch (RuntimeException e) {
                    if (i == maxAttempts) {
                        throw e;
                    }
                }
            }
            throw new IllegalStateException("unreachable");
        }
    }

    interface Task {
        String run(int attempt);
    }

    static class CircuitBreaker {
        private final int threshold;
        private int failures;
        private boolean open;

        CircuitBreaker(int threshold) {
            this.threshold = threshold;
        }

        String execute(Task task, String fallback) {
            if (open) {
                return fallback;
            }
            try {
                return task.run(1);
            } catch (RuntimeException e) {
                failures++;
                if (failures >= threshold) {
                    open = true;
                }
                throw e;
            }
        }

        boolean isOpen() {
            return open;
        }
    }

    public static void main(String[] args) {
        SimpleRateLimiter limiter = new SimpleRateLimiter(2);
        boolean first = limiter.tryAcquire();
        boolean second = limiter.tryAcquire();
        boolean third = limiter.tryAcquire();
        System.out.println("rateLimitFirst=" + first);
        System.out.println("rateLimitSecond=" + second);
        System.out.println("rateLimitThird=" + third);
        limiter.release();
        limiter.release();

        RetryExecutor retryExecutor = new RetryExecutor();
        AtomicInteger attempts = new AtomicInteger(0);
        String retryResult = retryExecutor.executeWithRetry(3, attempt -> {
            attempts.incrementAndGet();
            if (attempt < 3) {
                throw new RuntimeException("temporary fail");
            }
            return "retry-success";
        });
        System.out.println("retryAttempts=" + attempts.get());
        System.out.println("retryResult=" + retryResult);

        CircuitBreaker breaker = new CircuitBreaker(2);
        for (int i = 0; i < 2; i++) {
            try {
                breaker.execute(attempt -> {
                    throw new RuntimeException("downstream fail");
                }, "fallback-response");
            } catch (Exception e) {
                System.out.println("breakerFailure=" + e.getMessage());
            }
        }
        String degraded = breaker.execute(attempt -> "should-not-run", "fallback-response");
        System.out.println("breakerOpen=" + breaker.isOpen());
        System.out.println("degradedResult=" + degraded);
    }
}

7.2 编译并运行

bash
javac StabilityDesignDemo.java
java StabilityDesignDemo

7.3 你应该观察到什么

输出应包含这些关键信息:

text
rateLimitFirst=true
rateLimitSecond=true
rateLimitThird=false
retryAttempts=3
retryResult=retry-success
breakerFailure=downstream fail
breakerOpen=true
degradedResult=fallback-response

7.4 每一行在验证什么

  • rateLimitThird=false:说明限流的作用是超额请求直接被挡住
  • retryAttempts=3retryResult=retry-success:说明重试应该有限且面向临时失败
  • breakerFailure=downstream fail:说明熔断前仍会记录真实失败
  • breakerOpen=true:说明连续失败达到阈值后,熔断器进入打开状态
  • degradedResult=fallback-response:说明熔断打开后应优先走降级兜底,而不是继续打下游

8. 练习建议

下面这些练习做完,这一章会更扎实:

  • 设计一个接口限流方案
  • 分析某个依赖服务变慢时的保护措施
  • 总结超时、重试、熔断之间的配合关系
  • 画一张“故障扩散路径图”,看哪些地方应该做隔离

9. 自测问题

  • 限流、熔断、降级分别适合解决什么问题?
  • 为什么失败重试必须有边界?
  • 稳定性设计为什么本质上是在做风险控制?
  • 为什么隔离能阻止问题扩散?

10. 自测核对要点

如果你的回答能覆盖下面这些点,说明这一章基本掌握到位了:

  • 稳定性设计的核心是控制影响范围和资源消耗
  • 限流用于控制入口压力
  • 超时避免线程和资源长期被占住
  • 重试只适合部分临时失败场景,而且必须有限
  • 熔断和降级是系统自我保护的重要手段