云韧性不是一句“高可用”的口号,而是系统在真实约束下继续工作的能力:硬件会坏,网络会抖,区域会遇到容量压力,依赖服务会变慢。Microsoft Azure 关于韧性演进的讨论,核心并不只是“云平台越来越可靠”,更重要的是云上应用也要被设计成能适应、恢复、降级并持续提供服务的系统。
韧性不是单点能力,而是一组工程习惯
很多团队第一次谈 resiliency,会把注意力放在 SLA、可用区、备份这些名词上。它们都重要,但单独拿出来都不够。
真正的云韧性通常由几层能力叠在一起:
- 基础设施层:实例、磁盘、网络、机房、区域都可能失效,平台需要隔离故障并提供恢复路径。
- 架构层:应用要避免把所有流量、状态、队列和数据库写死在一个故障域里。
- 运行层:监控、告警、自动扩缩容、健康探针、发布回滚要能在压力下工作。
- 业务层:不是所有功能都必须同等可用,核心交易链路和非核心报表链路应该有不同的降级策略。
Azure 这类云平台的演进方向,是让底层能力更标准化:区域、可用区、负载均衡、托管数据库副本、备份恢复、服务健康事件、自动化运维。但平台只提供材料,应用是否能“弹回来”,还取决于你怎么使用这些材料。
从“避免故障”转向“接受故障并恢复”
传统机房时代,很多设计目标是尽量避免故障:买更好的机器、加更多冗余、把变更窗口压到深夜。云环境里,这种思路会变得不够用,因为分布式系统的故障形态更细碎:一个可用区延迟升高、某个下游 API 超时、连接池耗尽、DNS 缓存过期、队列积压。
更实际的目标是:
- 缩小故障影响范围,而不是假设故障不会发生。
- 让服务能检测异常,而不是等用户报障。
- 让恢复流程自动化,而不是依赖临时手工操作。
- 让系统有降级路径,而不是非核心依赖失败就拖垮主链路。
可以把韧性设计拆成三个问题:
- 这个组件坏掉时,影响半径多大?
- 系统多久能发现它坏了?
- 恢复动作是否经过演练?
这三个问题比“我们用了几个云产品”更能暴露真实风险。
可以这样实践:用 Azure CLI 建一个带健康探针的负载入口
下面示例不是原文中的具体配置,而是一个可改造的最小实践:用 Azure CLI 创建资源组、虚拟网络、公网 IP、负载均衡器和 HTTP 健康探针。你可以把后端池接到 VM、VM Scale Set 或其他合适的计算资源上。
运行前需要安装并登录 Azure CLI,并把变量改成你的命名和区域。
az login
RESOURCE_GROUP="rg-resiliency-demo"
LOCATION="eastus"
VNET="vnet-resiliency-demo"
SUBNET="snet-app"
PUBLIC_IP="pip-resiliency-demo"
LB="lb-resiliency-demo"
BACKEND_POOL="bep-app"
PROBE="probe-http"
RULE="rule-http"
az group create \
--name "$RESOURCE_GROUP" \
--location "$LOCATION"
az network vnet create \
--resource-group "$RESOURCE_GROUP" \
--name "$VNET" \
--address-prefix 10.20.0.0/16 \
--subnet-name "$SUBNET" \
--subnet-prefix 10.20.1.0/24
az network public-ip create \
--resource-group "$RESOURCE_GROUP" \
--name "$PUBLIC_IP" \
--sku Standard \
--allocation-method Static
az network lb create \
--resource-group "$RESOURCE_GROUP" \
--name "$LB" \
--sku Standard \
--public-ip-address "$PUBLIC_IP" \
--frontend-ip-name fe-public \
--backend-pool-name "$BACKEND_POOL"
az network lb probe create \
--resource-group "$RESOURCE_GROUP" \
--lb-name "$LB" \
--name "$PROBE" \
--protocol Http \
--port 8080 \
--path /healthz \
--interval 5 \
--threshold 2
az network lb rule create \
--resource-group "$RESOURCE_GROUP" \
--lb-name "$LB" \
--name "$RULE" \
--protocol Tcp \
--frontend-port 80 \
--backend-port 8080 \
--frontend-ip-name fe-public \
--backend-pool-name "$BACKEND_POOL" \
--probe-name "$PROBE"
这个例子里最值得关注的不是负载均衡器本身,而是 /healthz 的含义。健康检查不应该只返回“进程还活着”,更应该覆盖关键依赖的最低可用性,例如配置是否加载、数据库连接是否可建、队列客户端是否可用。但也不要把健康检查写得太重,否则下游短暂抖动会导致实例被频繁摘除,反而放大故障。
一个简单的 Node.js 健康端点可以这样写:
import http from "node:http";
const server = http.createServer(async (req, res) => {
if (req.url === "/healthz") {
// 可以在这里加入轻量级依赖检查,例如连接池状态或本地配置状态。
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ status: "ok" }));
return;
}
res.writeHead(200, { "content-type": "text/plain" });
res.end("hello from resilient app\n");
});
server.listen(8080, "0.0.0.0", () => {
console.log("server listening on :8080");
});
本地试运行:
node server.mjs
curl -i http://127.0.0.1:8080/healthz
恢复能力要被测试,不要只写在架构图里
韧性设计最容易变成文档幻觉:图上有两个区域,实际数据库连接串只有一个;图上写了自动恢复,实际告警没人值守;图上说支持降级,实际前端没有处理部分接口失败。
可以从小规模演练开始:
- 停掉一个应用实例,确认负载入口能摘除异常节点。
- 人为让一个下游接口返回 500,确认主流程不会无限等待。
- 降低数据库连接池上限,观察排队、超时和告警是否符合预期。
- 在非生产环境模拟区域级配置切换,记录人工步骤和耗时。
对于 Azure 上的系统,还可以把这些检查固化进发布流程。下面是一个简化的 GitHub Actions 示例,用来在部署后探测健康端点。把 APP_HEALTH_URL 配成你的真实地址。
name: post-deploy-health-check
on:
workflow_dispatch:
push:
branches: ["main"]
jobs:
health-check:
runs-on: ubuntu-latest
steps:
- name: Check application health
env:
APP_HEALTH_URL: ${{ secrets.APP_HEALTH_URL }}
run: |
test -n "$APP_HEALTH_URL"
for i in $(seq 1 12); do
code=$(curl -sS -o /tmp/health.txt -w "%{http_code}" "$APP_HEALTH_URL")
if [ "$code" = "200" ]; then
cat /tmp/health.txt
exit 0
fi
echo "health check failed with HTTP $code, retry $i/12"
sleep 10
done
echo "application did not become healthy in time"
cat /tmp/health.txt || true
exit 1
这不是完整的韧性体系,但它能防止一种常见事故:部署已经结束,入口已经切流,应用却没有真正准备好服务请求。
采用建议:把韧性预算花在关键路径上
云韧性不是把所有组件都堆到最高规格。多区域、主动主动、跨区域数据库复制、复杂流量调度都会增加成本和运维复杂度。更成熟的做法是按业务影响分层:
- 核心写入链路:明确 RTO、RPO、幂等策略、重试边界和人工兜底流程。
- 用户读取链路:优先考虑缓存、只读副本、部分数据降级展示。
- 异步任务链路:关注队列积压、死信队列、重放能力和重复消费。
- 内部管理功能:可以接受更低可用性,但要避免影响主链路。
落地时可以用一个短清单做自查:
- 每个服务是否有明确的健康端点?
- 重试是否带退避和最大次数,而不是无限重试?
- 超时是否小于调用方的整体请求预算?
- 状态是否跨故障域保护?
- 恢复流程是否在最近一个季度演练过?
- 告警是否指向可执行动作,而不是只告诉你“出事了”?
Azure 的韧性演进提醒我们:云平台会持续提高底座能力,但应用团队不能把恢复责任完全外包。真正可靠的系统,通常不是从不失败,而是在失败发生时能限制影响、快速判断、自动恢复,并把业务留在可接受的状态里。