Azure 可用性区域的设计,不能用一个数字概括整个工作负载。真正需要回答的问题不是“要用几个区域”,而是“每个组件需要跨几个区域,才能承受一个区域失效”。
两区和三区并不是全局二选一。更稳妥的做法是逐个组件分析故障域、恢复目标、数据一致性和服务能力,再决定采用两区、三区,还是直接使用 Azure 服务自身提供的区域冗余。
把工作负载拆成故障边界
一个典型应用通常至少包含入口层、计算层、缓存、消息系统、数据库和存储。它们对区域失效的敏感度并不相同:
- 无状态 Web 或 API 实例通常可以分布到两个区域,并通过负载均衡器分散流量。
- 需要持续写入的数据库,除了区域数量,还要考虑复制延迟、故障转移方式和数据丢失目标。
- 缓存可以接受重建时,区域冗余的要求可能低于主数据库。
- 消息系统如果依赖严格的顺序、分区或事务语义,增加区域并不一定自动得到更好的结果。
- 入口和网络组件必须与后端的容灾策略匹配,否则应用仍可能存在单一区域故障点。
这意味着一个工作负载可以采用混合策略:计算层使用两区部署,关键数据服务使用三区或服务托管的区域冗余,非关键缓存则采用更简单的重建方案。
先看服务托管的冗余能力
Azure 中有些服务可以由平台管理区域级复制、故障转移或冗余。只要服务提供的语义满足业务要求,优先使用这种能力通常比自行拼装多个实例更容易运维。
评估时应确认几个问题:
- 服务是否在目标区域提供可用性区域支持。
- 所选 SKU、部署模式和 API 版本是否支持两区或三区。
- 区域失效时,故障转移是自动完成,还是需要人工或应用侧介入。
- 复制是同步还是异步,是否存在可接受的数据丢失窗口。
- 备份、监控、扩容和升级是否覆盖所有副本。
不要把资源模板中的 zones: ['1', '2', '3'] 当成通用的高可用开关。区域编号、服务能力和 SKU 都可能因区域而异,部署前必须检查目标区域的支持情况。
两区与三区如何做决定
两区设计适合大多数希望承受单一区域失效、同时控制成本和复杂度的无状态组件。它能提供两个独立故障域,但在一个区域失效后,剩余区域需要承担全部流量,因此必须预留容量和验证扩缩容速度。
三区设计适合确实需要第三个独立故障域的组件,例如业务要求在一个区域失效后仍保留更大的容量余量,或者服务本身通过三区复制提供更明确的数据可用性语义。三区通常也意味着更高的实例、网络、存储和运维成本。
可以按下面的顺序判断:
- 先定义组件必须承受的故障:单实例、可用性区域、区域,还是更大范围的灾难。
- 再确认 RTO、RPO 和故障转移期间的容量要求。
- 检查 Azure 服务与 SKU 是否支持目标区域模式。
- 计算一个区域丢失后剩余容量是否足够。
- 用故障注入或演练验证 DNS、负载均衡、连接重试、数据复制和告警链路。
如果只是因为“三区看起来更可靠”而增加第三个区域,却没有验证复制语义和业务恢复流程,复杂度可能增加,实际恢复能力却没有同步提升。
一个可改造的 Azure 部署示例
下面的 Bicep 示例展示如何把区域数量作为组件级参数,用于一个虚拟机规模集。它假设已经存在目标子网,适合作为无状态计算层的起点。部署前请根据目标区域和 SKU 检查区域支持,并为生产环境补充负载均衡、托管身份、诊断设置和安全配置。
将内容保存为 main.bicep,把 subnetId 替换为现有子网资源 ID:
param vmssName string = 'api-vmss'
param location string = resourceGroup().location
param subnetId string
param adminUsername string = 'azureuser'
@secure()
param adminPassword string
param zones array = [
'1'
'2'
]
resource vmss 'Microsoft.Compute/virtualMachineScaleSets@2023-09-01' = {
name: vmssName
location: location
zones: zones
sku: {
name: 'Standard_D2s_v5'
tier: 'Standard'
capacity: 2
}
properties: {
upgradePolicy: {
mode: 'Automatic'
}
virtualMachineProfile: {
storageProfile: {
imageReference: {
publisher: 'Canonical'
offer: '0001-com-ubuntu-server-jammy'
sku: '22_04-lts-gen2'
version: 'latest'
}
osDisk: {
createOption: 'FromImage'
caching: 'ReadWrite'
managedDisk: {
storageAccountType: 'Premium_LRS'
}
}
}
osProfile: {
computerNamePrefix: 'api'
adminUsername: adminUsername
adminPassword: adminPassword
}
networkProfile: {
networkInterfaceConfigurations: [
{
name: 'primary-nic'
properties: {
primary: true
ipConfigurations: [
{
name: 'primary-ip'
properties: {
subnet: {
id: subnetId
}
}
}
]
}
}
]
}
}
}
}
可以先执行预览,再部署:
az deployment group what-if \
--resource-group rg-app-prod \
--template-file main.bicep \
--parameters subnetId='/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Network/virtualNetworks/<vnet>/subnets/<subnet>' \
adminPassword='<strong-password>' \
zones='["1","2"]'
az deployment group create \
--resource-group rg-app-prod \
--template-file main.bicep \
--parameters subnetId='/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Network/virtualNetworks/<vnet>/subnets/<subnet>' \
adminPassword='<strong-password>' \
zones='["1","2"]'
若这个组件确实需要三区,可以把参数改为 zones='["1","2","3"]',但这只是部署层面的变化。应用仍需要确认负载均衡策略、实例容量、连接重试和故障后的流量分配都能正常工作。生产环境也不应在命令行直接传递密码,示例中的密码参数应改为 Key Vault、CI/CD secret 或其他安全注入方式。
在选择区域之前,可以用 Azure CLI 查看目标区域的可用 SKU 和区域信息:
az vm list-skus \
--location eastus \
--resource-type virtualMachines \
--query 'sort_by([].{name:name, zones:locationInfo[0].zones}, &name)' \
--output table
这个查询只能帮助发现计算资源能力,不能替代具体 Azure 服务的功能文档和配额检查。数据库、存储、网络入口等组件需要分别验证。
落地时保留一张组件决策表
设计评审可以为每个组件记录以下内容:
| 组件 | 失效目标 | 区域模式 | 复制语义 | 单区失效后的容量 | 故障转移方式 |
|---|---|---|---|---|---|
| API 计算层 | 丢失一个区域 | 两区 | 无状态 | 由剩余实例承载 | 负载均衡器 |
| 主数据库 | 丢失一个区域 | 由服务能力决定 | 需确认同步或异步 | 需满足写入与读取目标 | 自动或人工 |
| 缓存 | 可重建 | 两区或单区重建 | 非关键 | 允许降级 | 应用重建 |
| 消息系统 | 区域失效与积压 | 按分区和顺序要求决定 | 需验证顺序与重复投递 | 需保留处理能力 | 服务或应用处理 |
这张表能把“用了几个区域”的讨论转化为可验证的工程决策,也能暴露隐藏的单点:单区域密钥服务、单区域监控、固定区域的连接字符串,或者只在一个区域运行的运维入口。
采用建议
把区域冗余视为组件级设计参数,而不是工作负载的统一标签。优先使用 Azure 服务托管的区域冗余;对无状态计算层,两区通常是控制成本和复杂度的合理起点;只有在容量、数据可用性或服务语义确实需要时,才引入三区。
上线前至少完成以下检查:
- 每个关键组件都写明了单区域失效后的行为。
- 所有选定 SKU 和目标区域都确认支持对应区域模式。
- 剩余区域有足够容量,且扩容配额已经验证。
- RTO、RPO、复制延迟和故障转移责任边界明确。
- 演练覆盖入口、应用、数据、依赖服务和监控告警,而不只是计算实例。
好的区域设计不是把数字做大,而是让每个组件拥有与业务风险相匹配的故障域。