概述
当你的业务从"服务一个城市"扩展到"服务全国"甚至"服务全球"时,单机房架构会遇到两个硬约束:距离带来的延迟和单点故障的风险。多地域多活架构是解决这两个问题的工程方案。
但多活架构是 SRE 领域最复杂的主题之一——它不是简单的"在两个机房部署服务",而是涉及数据一致性、流量调度、故障切换、运维复杂度等一系列深层工程挑战。做对了,系统可用性从 99.9% 提升到 99.99%;做错了,多活架构本身就会成为最大的故障源。
从多活架构模式、数据一致性挑战、流量切换策略、容灾 RTO/RPO 设计、跨地域监控到故障切换演练,详细梳理多活架构的可靠性设计。
关于多活架构的深入讨论,可参考 Google SRE Book - Disaster Preparedness 和 AWS - Multi-Region Active-Active Architecture。
一、为什么需要多活架构
单机房架构的局限
单机房架构:
┌── App Server ×N
用户 ──→ DNS/CDN ──→ Load Balancer ─────┼── App Server ×N
└── App Server ×N
│
┌─────────┴─────────┐
│ Database (主从) │
└───────────────────┘
问题:
1. 如果机房断电/网络中断 → 全站不可用
2. 跨地域用户延迟高(北京到广州 ~30ms RTT)
3. 容量受限于单个机房
多活架构的驱动力
| 驱动力 | 说明 | 优先级 |
|---|---|---|
| 容灾 | 机房级故障时业务不中断 | 高 |
| 低延迟 | 就近服务用户,降低访问延迟 | 高 |
| 容量扩展 | 突破单机房容量上限 | 中 |
| 合规要求 | 数据必须在特定地域存储 | 视行业 |
| 容灾演练要求 | 监管要求具备跨机房容灾能力 | 视行业 |
容灾的关键指标
在设计多活架构之前,必须先明确两个容灾指标:
| 指标 | 全称 | 含义 | 设计影响 |
|---|---|---|---|
| RTO | Recovery Time Objective | 故障后允许的最大恢复时间 | RTO 越短 → 需要越快的故障切换 → 倾向自动切换 |
| RPO | Recovery Point Objective | 故障后允许的最大数据丢失量 | RPO=0 → 需要同步复制 → 牺牲性能 |
RTO 和 RPO 的关系:
时间线:
正常运行 │←─────── 故障发生 ───────→ 恢复
│ │
│←── RPO ──→│ │
│ 数据丢失范围 │
│ │
│←────── RTO ──────────────→│
服务不可用时间
不同 RTO/RPO 目标对应不同的架构选择:
| RTO 目标 | RPO 目标 | 架构选择 | 成本 |
|---|---|---|---|
| < 1 分钟 | 0 | 同城双活(同步复制) | 高 |
| < 5 分钟 | < 1 分钟 | 同城主备(半同步复制) | 中 |
| < 30 分钟 | < 5 分钟 | 异地主备(异步复制) | 低 |
| < 4 小时 | < 1 小时 | 异地冷备(定期备份) | 最低 |
二、多活架构模式
模式一:主从架构(Active-Standby)
用户 ──→ DNS ──→ 主机房(Active)
├── App Server
├── DB (Primary)
└── ↓ 异步复制
备机房(Standby)
├── App Server(待命)
└── DB (Standby)
工作原理:正常情况下只有主机房对外服务,备机房通过异步复制保持数据同步。主机房故障时,切换到备机房。
| 优势 | 局限 |
|---|---|
| 架构相对简单 | 备机房闲置,资源利用率低 |
| 数据一致性容易保证 | 异步复制有延迟,切换可能丢数据(RPO > 0) |
| 无写冲突 | 切换时间较长(RTO 数分钟到数十分钟) |
| 成本较低 | 备机房平时不承载流量,难以验证可用性 |
适用场景:RTO < 30 分钟,RPO 允许少量丢失的中等关键业务。
模式二:同城双活(Active-Active)
用户 ──→ DNS ──→ 负载均衡(按权重分流)
│
┌──────────┴──────────┐
↓ ↓
机房 A(Active) 机房 B(Active)
├── App Server ├── App Server
├── DB (Primary) ├── DB (Replica)
└── 同步复制 ←─────────┘
工作原理:两个机房同时对外服务,数据库通过同步复制保持一致。任一机房故障,另一个机房继续服务。
| 优势 | 局限 |
|---|---|
| 资源利用率高 | 同步复制有性能开销 |
| RTO 接近 0 | 机房距离受限(通常 < 100km) |
| RPO = 0 | 需要低延迟专线连接 |
| 平时双机房承载流量 | 架构复杂度高 |
适用场景:RTO < 1 分钟,RPO = 0 的核心业务(如金融交易)。
模式三:异地多活(Geo-distributed Active-Active)
用户 ──→ 全局 DNS(按地域路由)
│
┌─────────┼─────────┐
↓ ↓ ↓
北京区域 上海区域 海外区域
├── App ├── App ├── App
├── DB ├── DB ├── DB
└──┬───────┴──┬──────┘
│ 异步复制 │
└──────────┘
工作原理:多个地域同时对外服务,每个地域有完整的数据库副本。数据通过异步复制在各地域间同步。
| 优势 | 局限 |
|---|---|
| 就近服务,低延迟 | 数据一致性挑战大(CAP 定理) |
| 任一地域故障不影响其他 | 写冲突需要解决 |
| 容量可水平扩展 | 运维复杂度极高 |
| 全球覆盖 | 成本最高 |
适用场景:全球性业务(如跨境电商、全球 SaaS)。
模式选择决策树
需要多机房容灾?
│
├─ 是 → RTO 和 RPO 要求?
│ │
│ ├─ RTO < 1min, RPO = 0
│ │ → 同城双活(同步复制)
│ │ → 需要:低延迟专线、同步复制数据库
│ │
│ ├─ RTO < 30min, RPO < 1min
│ │ → 异地主备(异步复制)
│ │ → 需要:异步复制、自动故障切换
│ │
│ └─ RTO < 4h, RPO < 1h
│ → 异地冷备(定期备份)
│ → 需要:备份系统、手动恢复流程
│
└─ 需要全球低延迟?
→ 异地多活(单元化架构)
→ 需要:数据同步方案、写冲突解决、全局路由
三、数据一致性挑战
CAP 定理的约束
多活架构中最根本的挑战来自 CAP 定理:
在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)最多只能同时满足两个。
在网络分区(Partition)不可避免的情况下,多活架构必须在 C 和 A 之间做选择:
| 选择 | 含义 | 架构模式 | 适用场景 |
|---|---|---|---|
| CP | 保证一致性,牺牲可用性 | 同步复制 | 金融交易 |
| AP | 保证可用性,牺牲一致性 | 异步复制 | 社交媒体、内容分发 |
数据复制方案对比
| 复制方案 | 一致性 | 性能 | RPO | 适用场景 |
|---|---|---|---|---|
| 同步复制 | 强一致 | 低(等待远端确认) | 0 | 金融、支付 |
| 半同步复制 | 准一致 | 中(至少一个远端确认) | ≈0 | 核心业务 |
| 异步复制 | 最终一致 | 高(不等远端) | >0 | 一般业务 |
| 不复制 | N/A | 最高 | 全丢 | 可重建的数据 |
数据冲突解决
在多活架构中,多个地域可能同时修改同一数据,产生冲突:
场景:用户在北京和上海同时修改个人资料
14:00:00 北京:用户改名 "张三" → 写入北京 DB
14:00:01 上海:用户改名 "李四" → 写入上海 DB
14:00:05 异步复制:北京 → 上海("张三" 覆盖 "李四")
14:00:06 异步复制:上海 → 北京("李四" 覆盖 "张三")
→ 数据冲突!最终状态不确定
冲突解决策略:
| 策略 | 原理 | 优势 | 局限 |
|---|---|---|---|
| 最后写入胜(LWW) | 用时间戳,最新的覆盖 | 简单 | 时钟不同步导致问题 |
| 向量时钟 | 记录因果关系,检测冲突 | 精确检测冲突 | 实现复杂 |
| 应用层解决 | 应用感知业务语义,自定义合并 | 语义正确 | 每个业务需定制 |
| 单元化 | 数据按用户归属地域,避免跨地域写 | 从根本上避免冲突 | 路由需准确 |
单元化架构
单元化是多活架构中解决数据冲突的最优雅方案——从源头避免跨地域写冲突。
单元化架构:
用户 ──→ 路由层(按用户 ID 路由到归属单元)
│
┌─────────┼─────────┐
↓ ↓ ↓
Unit-北京 Unit-上海 Unit-深圳
├── App ├── App ├── App
├── DB ├── DB ├── DB
└── 数据按用户分片
用户 A 的数据只在 Unit-北京读写
用户 B 的数据只在 Unit-上海读写
→ 无写冲突
单元化路由规则:
# 单元路由配置
unit_routing:
rule: "hash(user_id) % unit_count"
units:
- name: "unit-bj"
region: "beijing"
user_range: "0-33%"
- name: "unit-sh"
region: "shanghai"
user_range: "33-66%"
- name: "unit-sz"
region: "shenzhen"
user_range: "66-100%"
# 用户首次访问时确定归属单元
# 之后所有请求路由到该单元
单元化的挑战:
- 跨单元查询:用户 A 关注了用户 B,但 B 在另一个单元——需要跨单元查询或数据同步
- 单元扩缩容:增加或减少单元时,需要重新分配数据
- 故障切换:某单元故障时,其用户需要迁移到其他单元
四、流量切换策略
全局流量管理
多活架构需要一个全局流量管理层,负责将用户路由到正确的地域:
用户请求
│
↓
全局 DNS / GSLB(Global Server Load Balancing)
│
├── 按地域就近路由
├── 按健康状态路由
├── 按容量权重路由
└── 故障时自动切换
流量切换场景
| 场景 | 触发条件 | 切换方式 | RTO |
|---|---|---|---|
| 计划内切换 | 维护窗口、架构迁移 | 手动切换,逐步引流 | 无 |
| 单机房故障 | 机房网络中断/断电 | 自动切换到备用机房 | < 5min |
| 地域故障 | 整个地域不可用 | 自动切换到其他地域 | < 10min |
| 降级切换 | 单机房性能劣化 | 部分流量切换 | < 5min |
DNS 切换
# DNS 故障切换配置
dns_failover:
primary:
record: "api.example.com"
target: "1.2.3.4" # 主机房 IP
health_check: "https://api.example.com/healthz"
check_interval: 10s
failover:
target: "5.6.7.8" # 备机房 IP
trigger: "primary health check failed 3 consecutive times"
ttl: 30s # DNS TTL 设短,加速切换
# 注意:DNS 切换有 TTL 缓存延迟
# 客户端可能缓存了旧 IP,最多需要 TTL 时间才生效
DNS 切换的局限:
- DNS TTL 缓存导致切换不即时
- 部分客户端不遵守 TTL
- 无法精确控制流量比例
应用层流量切换
# 应用层流量路由(更精确)
app_level_routing:
gateway:
type: "API Gateway / Service Mesh"
routing_rules:
normal:
- region: "beijing"
weight: 50%
- region: "shanghai"
weight: 50%
failover:
trigger: "beijing region health check failed"
action:
- region: "beijing"
weight: 0% # 立即摘除
- region: "shanghai"
weight: 100% # 全量切换
speed: "instant" # 应用层切换无 DNS 缓存问题
canary_failover:
trigger: "beijing region degraded (latency > 1s)"
action:
- region: "beijing"
weight: 25% # 降级而非完全摘除
- region: "shanghai"
weight: 75%
流量切换的注意事项
traffic_switching_best_practices:
before_switch:
- "验证目标机房健康状态"
- "确认数据同步延迟在可接受范围"
- "通知相关团队"
- "准备回滚方案"
during_switch:
- "逐步切换,而非一次性全量"
- "切换过程中持续监控"
- "如出现异常,立即回滚"
after_switch:
- "验证服务正常"
- "观察至少 15 分钟"
- "更新 DNS 记录"
- "通知所有团队切换完成"
switch_sequence:
1: "降低源机房流量权重(100% → 90%)"
2: "观察目标机房是否能承受 10% 流量"
3: "继续降低源机房权重(90% → 50%)"
4: "观察目标机房在 50% 流量下的表现"
5: "完成切换(50% → 0%)"
6: "源机房完全摘除"
五、容灾 RTO/RPO 设计
RTO 设计
RTO(Recovery Time Objective)取决于故障检测时间和切换时间:
RTO = 故障检测时间 + 决策时间 + 切换时间 + 验证时间
示例:
故障检测:30 秒(健康检查 3 次失败 × 10 秒间隔)
决策时间:30 秒(自动决策脚本)
切换时间:60 秒(DNS 更新 + 流量切换)
验证时间:60 秒(健康检查确认)
→ RTO ≈ 3 分钟
降低 RTO 的策略:
| 策略 | 措施 | RTO 改善 |
|---|---|---|
| 自动检测 | 健康检查 + 自动告警 | 检测时间从分钟级到秒级 |
| 自动切换 | 无需人工干预的自动故障切换 | 决策时间从分钟级到秒级 |
| 预热备用 | 备用机房保持运行状态 | 切换时间从分钟级到秒级 |
| 自动验证 | 自动化健康验证脚本 | 验证时间从分钟级到秒级 |
RPO 设计
RPO(Recovery Point Objective)取决于数据复制延迟:
RPO = 数据复制延迟
同步复制:RPO = 0(数据实时同步)
异步复制:RPO = 复制延迟时间(通常 1-60 秒)
降低 RPO 的策略:
| 策略 | 措施 | RPO 改善 |
|---|---|---|
| 同步复制 | 数据写入需要至少 2 个机房确认 | RPO = 0 |
| 半同步复制 | 至少 1 个备库确认 | RPO ≈ 0 |
| 缩短复制间隔 | 更频繁的复制 | RPO 从分钟到秒 |
| 监控复制延迟 | 复制延迟超标时告警 | 不降低 RPO 但能及时告警 |
RTO/RPO 的监控
# RTO/RPO 监控指标
disaster_recovery_monitoring:
rto_related:
- metric: "health_check_failure_count"
alert: "> 3 consecutive failures"
action: "trigger automatic failover"
- metric: "failover_execution_time"
target: "< 60s"
- metric: "service_recovery_time"
target: "< 5min (RTO target)"
rpo_related:
- metric: "replication_lag_seconds"
alert: "> 10s"
action: "warning"
critical: "> 60s"
- metric: "replication_lag_bytes"
alert: "> 10MB"
- metric: "data_loss_on_failover"
target: "0 (RPO target)"
# Prometheus 监控复制延迟
# MySQL 主从复制延迟
mysql_slave_status_seconds_behind_master > 10
# PostgreSQL 复制延迟
pg_replication_lag > 10
# Redis 复制延迟
redis_replication_offset_diff > 1024000
六、跨地域监控
监控架构
多活架构需要全局视角的监控系统:
监控架构:
北京机房 上海机房
├── Prometheus ├── Prometheus
├── Node Exporter ├── Node Exporter
├── App Metrics ├── App Metrics
└── ↓ remote_write └── ↓ remote_write
│ │
└────────┬───────────────┘
↓
中央 Prometheus / Thanos
│
┌───────┼───────┐
↓ ↓ ↓
Grafana AlertManager 长期存储
关键监控维度
multi_region_monitoring:
# 1. 各地域服务健康
per_region:
- "各地域服务可用性"
- "各地域延迟(P50/P95/P99)"
- "各地域错误率"
- "各地域流量分布"
# 2. 跨地域数据同步
cross_region:
- "数据库复制延迟"
- "消息队列跨地域同步延迟"
- "缓存同步延迟"
- "复制通道健康状态"
# 3. 全局视图
global:
- "全局可用性(所有地域的加权平均)"
- "全局 SLO 达成状态"
- "地域间流量分布"
- "DNS 解析分布"
# 4. 容灾就绪度
dr_readiness:
- "备用机房健康状态"
- "数据同步延迟是否在 RPO 范围内"
- "故障切换脚本是否可用"
- "最近一次切换演练的时间"
全局监控仪表盘
# 全局监控仪表盘设计
global_dashboard:
row_1_global_overview:
- panel: "全局服务可用性"
query: "avg by (region) (slo:availability:rate30d)"
- panel: "全局流量分布"
query: "sum by (region) (rate(http_requests_total[5m]))"
- panel: "全局错误率"
query: "sum by (region) (rate(http_requests_total{status=~'5..'}[5m]))"
row_2_replication_health:
- panel: "数据库复制延迟"
query: "max by (region) (mysql_slave_status_seconds_behind_master)"
threshold: "10s (warning), 60s (critical)"
- panel: "消息队列同步延迟"
query: "max by (region) (mq_replication_lag_seconds)"
row_3_dr_readiness:
- panel: "各机房健康状态"
type: "status_map"
query: "up by (region, instance)"
- panel: "RPO 状态"
type: "gauge"
query: "max(replication_lag_seconds)"
threshold: "green < 10s, yellow 10-60s, red > 60s"
- panel: "最近演练时间"
type: "stat"
query: "time() - last_drill_timestamp"
七、故障切换演练
为什么需要演练
多活架构最大的风险不是"切换不了",而是**“以为能切换但实际上切换不了”**。不经过真实演练的容灾方案只是一厢情愿。
真实案例:
某公司投入数百万建设异地容灾,三年没有演练。
真实故障时尝试切换,发现:
- DNS 切换脚本过期不可用
- 备机房的数据库版本与主机房不一致
- SSL 证书已过期
- 备机房的应用配置不完整
→ 容灾形同虚设
演练类型
| 类型 | 方式 | 风险 | 频率 |
|---|---|---|---|
| 桌面推演 | 讨论式模拟故障场景 | 无 | 每季度 |
| 仿真切换 | 在非生产环境模拟切换 | 无 | 每月 |
| 生产切流 | 生产环境部分流量切换 | 低 | 每月 |
| 全量切换 | 生产环境完全切换到备用机房 | 中 | 每半年 |
| 混沌注入 | 模拟机房级故障,验证自动切换 | 中高 | 每年 |
全量切换演练流程
# 多活故障切换演练
## 演练目标
验证从北京机房切换到上海机房的完整流程,确认 RTO < 5min,RPO < 10s。
## 前置条件
- [ ] 上海机房健康状态正常
- [ ] 数据同步延迟 < 5s
- [ ] 切换脚本经过仿真验证
- [ ] 回滚方案已准备
- [ ] 相关团队已通知
- [ ] 选择低峰时段(凌晨 2:00-4:00)
## 演练流程
### Phase 1: 预检查(T-30min)
1. 验证上海机房服务健康
```bash
curl -s https://api-sh.example.com/healthz
- 确认数据同步延迟
# MySQL 复制延迟 mysql -h sh-db -e "SHOW SLAVE STATUS\G" | grep Seconds_Behind_Master # 预期:< 5s - 确认上海机房容量能承载全量流量
Phase 2: 流量切换(T+0)
- 将 DNS 权重从 北京100%/上海0% 调整为 北京90%/上海10%
./update_dns_weights.sh --bj 90 --sh 10 - 观察 2 分钟,确认上海机房正常处理流量
- 继续调整:北京50%/上海50%
- 观察 5 分钟
- 完成切换:北京0%/上海100%
Phase 3: 验证(T+10min)
- 验证服务可用性
curl -s https://api.example.com/healthz # 预期:200 OK - 验证 SLO 指标
# 查询 Prometheus curl -s "http://prometheus:9090/api/v1/query?query=slo:availability:rate5m" # 预期:> 99.9% - 验证用户体验(黑盒监控)
Phase 4: 切回(T+30min)
- 按相反顺序将流量切回北京
- 确认北京机房正常服务
Phase 5: 演练复盘(T+1day)
- 记录演练时间线
- 评估 RTO 和 RPO 是否达标
- 发现的问题记录为改进项
- 更新故障切换文档
### 自动化故障切换
```yaml
# 自动故障切换配置
auto_failover:
enabled: true
trigger:
condition: "primary region health check failed 3 consecutive times"
health_check:
url: "https://api-bj.example.com/healthz"
interval: 10s
timeout: 5s
failure_threshold: 3
pre_failover_checks:
- "standby region health: OK"
- "replication lag < 10s"
- "standby capacity > 100% of current traffic"
failover_steps:
1:
action: "update DNS weights"
from: "bj:100,sh:0"
to: "bj:0,sh:100"
timeout: 30s
2:
action: "promote standby DB to primary"
command: "./promote_standby_db.sh --region sh"
timeout: 60s
3:
action: "verify service health"
command: "./verify_service.sh --region sh"
timeout: 60s
4:
action: "notify teams"
command: "./notify.sh --event auto_failover --to all"
rollback:
condition: "verification failed or service not healthy after 5 min"
action: "revert DNS weights to original"
八、多活架构的常见陷阱
陷阱一:“伪双活”
伪双活:
两个机房都部署了应用,但数据库只有一个机房。
机房 A 故障 → 机房 B 的应用无法访问数据库 → 全站不可用。
这不是双活,只是"双部署"。
真正的双活:
两个机房都有完整的应用 + 数据库副本。
任一机房故障 → 另一个机房独立继续服务。
陷阱二:忽视依赖服务
场景:
应用做了双活,但依赖的第三方服务(如短信网关、支付通道)只有单点。
机房故障 → 应用切换到备机房 → 但备机房无法访问第三方服务 → 仍然不可用。
教训:
双活的范围必须覆盖所有关键依赖,包括第三方服务。
第三方服务也需要有备用方案(如多个短信供应商)。
陷阱三:DNS 缓存导致切换无效
场景:
DNS TTL 设为 1 小时。
机房故障时切换 DNS,但客户端缓存了旧 IP。
1 小时内部分用户仍然访问故障机房。
教训:
故障切换场景的 DNS TTL 应设为 30-60 秒。
或使用应用层路由(API Gateway/Service Mesh)绕过 DNS 缓存。
陷阱四:数据不一致导致切换后数据错乱
场景:
异步复制延迟 30 秒。
主机房故障时强制切换,丢失最后 30 秒数据。
用户发现刚提交的订单消失了。
教训:
明确 RPO 目标,并在切换前检查复制延迟。
如果复制延迟超过 RPO,应该等待而非立即切换。
对于 RPO=0 的场景,必须使用同步复制。
陷阱五:从不演练
场景:
建设了多活容灾,但从不演练。
真实故障时发现切换脚本过期、配置不一致、证书过期。
教训:
没有演练过的容灾方案等于没有容灾。
至少每半年做一次全量切换演练。
每月做一次部分流量切换验证。
九、多活架构的成本分析
multi_region_cost_analysis:
infrastructure:
dual_active:
compute: "2x(两个机房都需要完整计算资源)"
storage: "2x + 复制带宽"
network: "专线/互联带宽"
active_standby:
compute: "1.5x(备机房可缩减规模)"
storage: "2x"
network: "复制带宽"
operational:
engineering: "需要多活架构经验的工程师"
monitoring: "跨地域监控系统"
testing: "定期演练成本"
hidden_costs:
- "数据复制延迟导致的业务复杂性"
- "跨地域调试和排障的时间成本"
- "数据合规审计的额外工作"
- "团队需要掌握多活运维技能的培训成本"
roi_analysis:
benefit: "避免机房级故障导致的业务损失"
question: "机房级故障的年概率 × 故障损失 vs 多活架构年成本"
typical: "核心业务值得做多活;非核心业务用主备即可"
总结
多活架构是 SRE 体系中最复杂但价值最高的架构模式之一。核心要点:
- 明确 RTO/RPO 目标:一切多活设计从 RTO/RPO 目标出发,不要为了多活而多活
- 选择合适的模式:主从、双活、多活各有适用场景,不是越复杂越好
- 数据一致性是核心挑战:CAP 定理不可绕过,需要在一致性和可用性之间做明确选择
- 单元化是解决写冲突的优雅方案:按用户归属地域路由,从源头避免跨地域写
- 流量切换要逐步进行:不要一次性全量切换,逐步切流并持续验证
- 跨地域监控是全局视角:需要看到所有地域的健康状态和数据同步状态
- 必须定期演练:没有演练过的容灾等于没有容灾
最后提醒:多活架构不是银弹。 它在提升可用性的同时,也带来了巨大的架构复杂度和运维成本。在决定做多活之前,先问自己:单机房 + 快速恢复是否已经足够?只有当单机房的 RTO/RPO 确实无法满足业务需求时,多活架构才值得投入。
记住架构设计的基本原则:用最简单的架构满足可靠性要求。复杂度本身是可靠性的敌人。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- Google SRE Book - Disaster Preparedness — Google SRE 团队,参考了Google SRE Book - Disaster Preparedness相关内容
- AWS - Multi-Region Active-Active Architecture — AWS,参考了AWS - Multi-Region Active-Active Architecture相关内容