全球化 SaaS 基础设施清单:域名、CDN、支付与监控

0阅读12分钟

去年我做了一个面向北美市场的 SaaS 工具,上线第一周就收到了几条用户反馈:页面加载慢、支付失败没提示、注册邮件进了垃圾箱。这些都是基础设施层面的问题,不是功能 bug,但直接影响用户对产品的信任。

独立开发者通常把精力放在产品逻辑上,觉得「先上线再说」。这个思路没错,但如果关键链路不可靠——用户付不了款、收不到邮件、页面加载超过 3 秒——增长流量会被直接浪费。基础设施不需要一开始做得很重,但域名、CDN、支付、邮件、监控这几条链路,在面向海外市场时必须可靠。

这篇文章是我过去两年做全球化 SaaS 踩坑后的总结,覆盖域名与 DNS 配置、CDN 与对象存储选型、多区域部署策略、支付与 Webhook 处理、邮件送达率、监控与日志追踪、数据驻留与合规。每个部分都有具体的翻车经历和修复方案,最后附了一份可执行的出海检查清单。

域名与 DNS:被忽视的第一道门

域名配置看起来简单,但 DNS 解析策略直接影响全球用户的访问速度。我早期犯过一个错误:把主域名、API 域名、管理后台都指向同一个 IP,结果某个区域的 DNS 解析延迟导致整个产品不可用。

后来我学到的教训是:子域名拆分 + 就近解析。主站用 app.example.com,API 用 api.example.com,静态资源用 cdn.example.com 或独立的对象存储域名。DNS 服务商选择上,Cloudflare 和 AWS Route 53 都支持基于地理位置的智能解析(Geo DNS),可以根据用户所在地返回最近的服务器 IP。

// ❌ 错误做法:所有服务指向同一 IP
// DNS 记录
// app.example.com  A  1.2.3.4
// api.example.com  A  1.2.3.4
// cdn.example.com  A  1.2.3.4
 
// ✅ 正确做法:按职责拆分域名,配合 CDN 和智能解析
// app.example.com  CNAME  cname.vercel-dns.com
// api.example.com  CNAME  api-lb-123456789.us-east-1.elb.amazonaws.com
// cdn.example.com  CNAME  cdn.cloudflare.net
// static.example.com  CNAME  r2.cloudflarestorage.com

另一个容易踩的坑是 DNS 缓存。TTL(Time To Live)设置过短会增加 DNS 查询次数,设置过长会导致故障切换慢。我的经验是:正常记录设 300 秒(5 分钟),故障切换时临时调到 60 秒。

CDN 与对象存储:静态资源加速的核心

CDN 的选择取决于目标市场和流量模式。Cloudflare 的全球网络覆盖最广,免费套餐就能满足大部分独立开发者的需求。如果目标用户主要在北美和欧洲,Vercel 自带的 CDN 也够用。但如果用户分布在亚洲、南美、非洲,Cloudflare 的节点覆盖优势就明显了。

对象存储方面,Cloudflare R2 的零出口流量(zero egress)定价是杀手锏。AWS S3 的标准定价是 $0.023/GB/月存储 + $0.09/GB 出口流量,R2 是 $0.015/GB/月存储 + $0 出口流量。对于一个月流出 10TB 数据的产品,R2 能省约 $891/月的出口费用。

// ❌ 错误做法:直接从服务器提供静态资源,没有 CDN 加速
app.get('/images/:id', async (req, res) => {
  const image = await fs.readFile(`./uploads/${req.params.id}`)
  res.set('Content-Type', 'image/jpeg')
  res.send(image)
})
 
// ✅ 正确做法:静态资源上传到对象存储,通过 CDN 分发
// 上传时直接写入 R2/S3,应用只生成签名 URL
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
 
const s3 = new S3Client({
  region: 'auto',
  endpoint: 'https://<account_id>.r2.cloudflarestorage.com',
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
  },
})
 
async function getUploadUrl(key: string) {
  const command = new PutObjectCommand({
    Bucket: 'my-saas-uploads',
    Key: key,
    ContentType: 'image/jpeg',
  })
  return getSignedUrl(s3, command, { expiresIn: 3600 })
}
 
// 前端直接上传到 R2,CDN 自动加速访问
// https://cdn.example.com/uploads/user-123/avatar.jpg

图片压缩也是 CDN 层该做的事。Cloudflare 的 Polish 和 image resizing 功能可以在边缘节点自动转换格式(WebP/AVIF)和压缩,不需要在应用层处理。

多区域部署:什么时候该做,什么时候不该做

AWS 官方文档把多区域灾备策略分为四种:Backup & Restore(备份恢复)、Pilot Light(引导灯)、Warm Standby(温备)、Multi-Site Active/Active(多活)。RTO(恢复时间目标)和 RPO(恢复点目标)从小时级降到秒级,成本也依次递增。

对于独立开发者的早期产品,大部分情况下不需要多活架构。一个区域的计算节点 + 全球 CDN 加速静态资源,就能覆盖大部分场景。等真实用户分布稳定后,再考虑是否需要在其他区域部署计算节点。

graph TB
    User[全球用户] -->|DNS 解析| Route53[Route 53 Geo DNS]
    Route53 -->|北美用户| US[US-East-1 Vercel]
    Route53 -->|欧洲用户| EU[EU-West-1 Vercel]
    Route53 -->|亚太用户| AP[AP-Southeast-1 Vercel]

    US -->|API 请求| APIGateway[API Gateway]
    EU -->|API 请求| APIGateway
    AP -->|API 请求| APIGateway

    APIGateway -->|读写| PrimaryDB[(Primary DB<br/>US-East-1)]
    PrimaryDB -->|异步复制| ReplicaEU[(Replica<br/>EU-West-1)]
    PrimaryDB -->|异步复制| ReplicaAP[(Replica<br/>AP-Southeast-1)]

    US -.->|静态资源| CDN[Cloudflare CDN]
    EU -.->|静态资源| CDN
    AP -.->|静态资源| CDN
    CDN -->|回源| R2[R2 Object Storage]
// ❌ 错误做法:所有区域直连同一个区域的数据库,跨洋延迟高
// 欧洲用户请求 API → US-East-1 API → US-East-1 DB → 响应延迟 150ms+
async function getUser(userId: string) {
  // 直接从主库读取,无论用户在哪个区域
  return db.query('SELECT * FROM users WHERE id = $1', [userId])
}
 
// ✅ 正确做法:读请求路由到就近的只读副本
// 欧洲用户请求 API → EU API → EU Replica DB → 响应延迟 20ms
import { Client } from 'pg'
 
async function getUser(userId: string, region: 'us' | 'eu' | 'ap') {
  const dbConfig = {
    us: { host: 'db-us.example.com', port: 5432 },
    eu: { host: 'db-eu.example.com', port: 5432 },
    ap: { host: 'db-ap.example.com', port: 5432 },
  }
 
  const client = new Client(dbConfig[region])
  await client.connect()
 
  try {
    // 读请求走就近副本
    const result = await client.query(
      'SELECT * FROM users WHERE id = $1',
      [userId]
    )
    return result.rows[0]
  } finally {
    await client.end()
  }
}
 
// 写请求始终路由到主库
async function updateUser(userId: string, data: Partial<User>) {
  const primaryClient = new Client({ host: 'db-us.example.com', port: 5432 })
  await primaryClient.connect()
  try {
    await primaryClient.query(
      'UPDATE users SET name = $1, updated_at = NOW() WHERE id = $2',
      [data.name, userId]
    )
  } finally {
    await primaryClient.end()
  }
}

Fly.io 的多区域部署方案比较独特,它通过 fly-replay 头实现请求路由:如果一个请求到达的区域没有对应的数据,应用可以返回 fly-replay: instance=&lt;target&gt; 让 Fly.io 把请求重定向到正确的区域。这种方式适合数据库分片的场景,但需要在应用层处理路由逻辑。

支付与 Webhook:不能丢的事件

Stripe 的 Webhook 机制有严格的超时要求:必须在 20 秒内返回 2xx 响应,否则 Stripe 会认为投递失败并重试。重试策略是指数退避,最长重试 72 小时。如果 Webhook 处理器有 bug 返回了 5xx,会触发「重试风暴」——Stripe 会持续重试,把你的服务器打垮。

我遇到过一次事故:支付成功后的邮件发送逻辑超时,导致 Webhook 返回 500,Stripe 开始重试。每次重试又触发邮件发送,又超时,又 500。三天内积累了上万次重试,服务器资源被耗尽。

// ❌ 错误做法:在 Webhook 处理器中同步执行耗时操作
app.post('/webhook/stripe', async (req, res) => {
  const event = req.body
 
  switch (event.type) {
    case 'checkout.session.completed':
      // 同步发送邮件 + 更新数据库 + 调用第三方 API
      await sendWelcomeEmail(event.data.object.customer_email) // 可能超时
      await updateSubscription(event.data.object) // 可能失败
      await notifySlack(event) // 可能超时
      break
  }
 
  res.json({ received: true })
})
 
// ✅ 正确做法:快速响应,耗时操作异步处理
import { Queue } from 'bullmq'
 
const webhookQueue = new Queue('stripe-webhooks', {
  connection: { host: 'redis.example.com', port: 6379 },
})
 
app.post('/webhook/stripe', async (req, res) => {
  // 1. 验证签名
  const sig = req.headers['stripe-signature']
  let event
  try {
    event = stripe.webhooks.constructEvent(
      req.rawBody,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    )
  } catch (err) {
    console.error('Webhook signature verification failed')
    return res.status(400).json({ error: 'Invalid signature' })
  }
 
  // 2. 幂等性检查:检查是否已处理过这个事件
  const alreadyProcessed = await db.query(
    'SELECT 1 FROM processed_events WHERE event_id = $1',
    [event.id]
  )
  if (alreadyProcessed.rows.length > 0) {
    return res.json({ received: true })
  }
 
  // 3. 入队异步处理,快速返回 200
  await webhookQueue.add('process-event', {
    eventId: event.id,
    eventType: event.type,
    eventData: event.data.object,
  })
 
  res.json({ received: true })
})
 
// 4. 异步处理器:消费队列,处理实际业务逻辑
webhookQueue.process('process-event', async (job) => {
  const { eventId, eventType, eventData } = job.data
 
  try {
    switch (eventType) {
      case 'checkout.session.completed':
        await sendWelcomeEmail(eventData.customer_email)
        await updateSubscription(eventData)
        await notifySlack(eventData)
        break
      // ...
    }
 
    // 标记事件已处理
    await db.query(
      'INSERT INTO processed_events (event_id, processed_at) VALUES ($1, NOW())',
      [eventId]
    )
  } catch (err) {
    console.error(`Failed to process event ${eventId}:`, err)
    // 抛出异常,让 BullMQ 自动重试这个 job
    throw err
  }
})

关键点是幂等性异步处理。每个 Webhook 事件用 event.id 做幂等键,即使 Stripe 重试也不会重复处理。耗时操作(邮件、数据库更新、第三方调用)全部入队异步执行,Webhook 处理器只负责验签、幂等检查、入队、返回 200。

邮件送达率:事务邮件不能进垃圾箱

邮件送达率取决于 DNS 配置和发件人信誉。SPF(Sender Policy Framework)、DKIM(DomainKeys Identified Mail)、DMARC(Domain-based Message Authentication, Reporting & Conformance)这三项必须配齐,否则 Gmail、Outlook 会直接把邮件扔进垃圾箱。

事务邮件(注册验证、密码重置、账单通知)的优先级高于营销邮件。我早期用 SendGrid 的共享 IP 发事务邮件,送达率只有 70% 左右。后来切换到专用 IP + 独立子域名(mail.example.com),送达率提升到 98%。

# ❌ 错误做法:只配了 SPF,没配 DKIM 和 DMARC
# example.com DNS 记录
example.com.    TXT    "v=spf1 include:sendgrid.net ~all"
 
# ✅ 正确做法:SPF + DKIM + DMARC 三件套
# SPF 记录
example.com.    TXT    "v=spf1 include:sendgrid.net ~all"
mail.example.com. TXT  "v=spf1 include:sendgrid.net ~all"
 
# DKIM 记录(SendGrid 提供)
s1._domainkey.example.com.  CNAME  s1.domainkey.u1234567.wl.sendgrid.net
s2._domainkey.example.com.  CNAME  s2.domainkey.u1234567.wl.sendgrid.net
 
# DMARC 记录(先从 none 开始监控,逐步过渡到 quarantine/reject)
_dmarc.example.com.  TXT  "v=DMARC1; p=none; rua=mailto:[email protected]; pct=100"

DMARC 的 p 策略建议分阶段实施:先设 p=none 收集报告,分析 30 天的数据后改成 p=quarantine(可疑邮件进垃圾箱),再 30 天后改成 p=reject(直接拒绝伪造邮件)。

监控与日志:错误必须能被及时发现

监控分三层:基础设施监控(CPU、内存、磁盘)、应用监控(请求延迟、错误率、吞吐量)、业务监控(支付成功率、邮件送达率、用户注册转化率)。独立开发者不需要一开始就部署完整的可观测性平台,但关键指标必须有告警。

我用的是组合方案:Vercel Analytics 看页面性能,Sentry 看应用错误,Better Stack(原 Better Uptime)看可用性,Stripe Dashboard 看支付指标。日志用 Axiom 或 Logtail,按用户 ID 和请求 ID 索引,方便追踪单个用户的完整链路。

// ❌ 错误做法:日志没有结构化,没有请求 ID,难以追踪
console.log('User login failed')
console.log('Payment webhook received')
console.log('Database query slow')
 
// ✅ 正确做法:结构化日志 + 请求 ID 串联
import pino from 'pino'
 
const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  formatters: {
    level: (label) => ({ level: label }),
  },
})
 
// 中间件:为每个请求生成唯一 ID
app.use((req, res, next) => {
  const requestId = req.headers['x-request-id'] || crypto.randomUUID()
  req.requestId = requestId
  res.setHeader('x-request-id', requestId)
  next()
})
 
// 业务逻辑:携带上下文的结构化日志
app.post('/api/checkout', async (req, res) => {
  const { userId, amount } = req.body
  const requestId = req.requestId
 
  logger.info({
    event: 'checkout_started',
    requestId,
    userId,
    amount,
    currency: 'USD',
  })
 
  try {
    const session = await stripe.checkout.sessions.create({
      // ...
    })
 
    logger.info({
      event: 'checkout_session_created',
      requestId,
      userId,
      sessionId: session.id,
    })
 
    res.json({ sessionId: session.id })
  } catch (err) {
    logger.error({
      event: 'checkout_failed',
      requestId,
      userId,
      error: err.message,
      stack: err.stack,
    })
 
    res.status(500).json({ error: 'Checkout failed' })
  }
})

日志的关键是可追踪性。每个请求有唯一 ID,所有相关日志都带上这个 ID 和用户 ID,就能在日志系统中快速过滤出单个用户的完整操作链路。结构化日志(JSON 格式)也方便后续用日志分析工具查询和告警。

数据驻留与合规:GDPR 不是可选项

如果产品面向欧盟用户,GDPR(General Data Protection Regulation)是必须遵守的法律。GDPR 要求个人数据的存储和处理必须在欧盟境内,或者存储在被认为有「充分保护水平」的国家。违反 GDPR 的罚款最高可达全球年营业额的 4% 或 2000 万欧元。

数据驻留(Data Residency)的技术实现有两种方案:一是单区域部署在欧盟(比如 AWS EU-West-1 爱尔兰),所有用户数据都存储在那里;二是多区域部署,欧盟用户的数据路由到欧盟区域的数据库,其他区域的用户路由到各自的区域。

// ❌ 错误做法:所有用户数据都存储在同一个区域,违反 GDPR 数据驻留要求
// 假设数据库部署在 US-East-1,但服务欧盟用户
const dbConfig = {
  host: 'db-us-east-1.example.com', // 所有欧盟用户数据都在美国
  port: 5432,
  database: 'my_saas',
}
 
// ✅ 正确做法:根据用户所在地路由到对应区域的数据库
import { Client } from 'pg'
 
const dbConfigs = {
  eu: {
    host: 'db-eu-west-1.example.com', // 欧盟用户数据存储在欧盟
    port: 5432,
    database: 'my_saas_eu',
  },
  us: {
    host: 'db-us-east-1.example.com',
    port: 5432,
    database: 'my_saas_us',
  },
}
 
async function getDbClient(userRegion: 'eu' | 'us') {
  return new Client(dbConfigs[userRegion])
}
 
// 用户注册时,根据其所在地选择数据库
app.post('/api/register', async (req, res) => {
  const { email, country } = req.body
 
  // 根据 IP 或用户声明确定区域
  const region = isEUCountry(country) ? 'eu' : 'us'
 
  const client = await getDbClient(region)
  await client.connect()
 
  try {
    await client.query(
      'INSERT INTO users (email, country, region) VALUES ($1, $2, $3)',
      [email, country, region]
    )
    res.json({ success: true })
  } finally {
    await client.end()
  }
})

除了数据驻留,GDPR 还要求提供数据导出(Data Portability)和数据删除(Right to be Forgotten)功能。产品需要有 API 或界面让用户导出自己的数据,以及申请删除账号和所有相关数据。

基础设施方案对比

维度VercelFly.ioAWS (独立部署)Railway
部署模型Serverless / Edge FunctionsDocker 容器(全球边缘)全面自主(EC2/ECS/Lambda)容器(单区域)
多区域支持需 Edge Functions原生多区域(fly-replay)完全自主控制有限(单区域为主)
数据库外部(PlanetScale/Neon/Supabase)托管 Postgres + 卷完全自主(RDS/Aurora)托管 Postgres/MySQL
CDN内置(全球边缘)需自建或配合 Cloudflare需配置 CloudFront需配合 Cloudflare
适用阶段早期快速验证中期需要多区域控制大规模自定义快速原型
月成本估算$0-20(Hobby/Pro)$0-30(按用量)$50-500+(复杂计费)$5-50(按用量)
CDN / 对象存储方案Cloudflare CDN + R2Vercel CDN + BlobAWS CloudFront + S3
全球节点覆盖300+ 城市依赖 Vercel 边缘网络450+ 边缘节点
存储定价$0.015/GB/月$0.025/GB/月(Blob)$0.023/GB/月(S3 Standard)
出口流量$0(零出口费)包含在 Vercel 套餐$0.09/GB(首 10TB)
图片优化Polish + Image Resizing内置 next/imageLambda@Edge 或 CloudFront Functions
中国访问企业版有中国节点无优化需中国境内 CDN 备案
10TB/月出口成本$0包含在套餐~$891
多区域灾备策略RTORPO月成本适用场景
Backup & Restore小时级小时级$非关键业务,数据可丢失
Pilot Light十分钟级分钟级$$核心服务需快速恢复
Warm Standby十分钟级秒级$$$高可用要求,快速扩容
Multi-Site Active/Active秒级秒级或零$$$$零停机要求,全球用户
合规框架适用范围核心要求违规处罚
GDPR欧盟用户数据数据驻留、用户权利、DPO、DPIA全球营业额 4% 或 €2000 万
SOC 2B2B SaaS(美国客户)安全、可用性、保密性控制客户合同违约
CCPA加州居民数据数据披露、删除权、退出权$2,500-7,500/次违规
HIPAA医疗健康数据数据加密、访问控制、审计日志$100-50,000/次违规

出海基础设施检查清单

以下清单按上线阶段分组,每项都可执行。

阶段一:上线前(MVP)

  • 域名与 DNS:主域名、API 域名、静态资源域名已配置;DNS 启用 Geo 解析;关键记录 TTL 设为 300 秒
  • SSL 证书:所有域名启用 HTTPS;证书自动续期(Let's Encrypt 或平台自带)
  • CDN 加速:静态资源通过 CDN 分发;图片启用自动压缩和格式转换(WebP/AVIF)
  • 对象存储:用户上传文件存储在 R2/S3,不占用应用服务器磁盘;生成预签名 URL 直传
  • 支付集成:Stripe/Paddle 已接入;沙箱环境测试通过订阅、取消、退款流程;Webhook 签名验证已启用
  • 邮件配置:SPF、DKIM、DMARC 三件套已配置;事务邮件使用独立子域名;送达率监控已启用
  • 错误追踪:Sentry 或类似工具已接入;未捕获异常自动上报;Source Map 已上传
  • 基础监控:可用性监控已配置(Better Stack / UptimeRobot);API 错误率告警阈值已设定

阶段二:增长期(有真实用户后)

  • 结构化日志:所有日志使用 JSON 格式;每个请求携带 requestId 和用户 ID;日志保留 30 天
  • Webhook 可靠性:Webhook 处理器异步化;幂等性检查已实现;死信队列(DLQ)已配置
  • 数据库备份:每日自动备份;跨区域备份已启用;恢复流程已测试
  • 速率限制:API 接口启用速率限制(如 100 次/分钟/IP);支付接口更严格(如 10 次/分钟)
  • 安全加固:CSP(Content Security Policy)头已配置;CORS 白名单已设定;敏感环境变量加密存储

阶段三:合规与扩展(面向欧盟或 B2B)

  • GDPR 合规:欧盟用户数据存储在欧盟区域;数据导出 API 已实现;账号删除功能已实现
  • 隐私政策:隐私政策页面已上线;Cookie 同意横幅已配置(面向欧盟用户)
  • SOC 2 准备:访问控制审计日志已启用;员工权限最小化;供应商安全评估已完成
  • 灾备方案:根据业务需求选择 Pilot Light / Warm Standby / Active-Active;故障切换演练已执行

验收方式

基础设施配置完成后,用以下方式验收:

  1. 访问速度:用海外代理或 WebPageTest 测试全球多个地区的页面加载时间,目标 TTFB < 500ms
  2. 支付流程:用 Stripe 测试卡完成订阅、取消、退款全流程;检查 Webhook 是否正确触发和处理
  3. 邮件送达:用 Gmail、Outlook、Yahoo 邮箱测试注册、登录、账单邮件是否到达收件箱
  4. 监控告警:手动触发一个错误(比如访问不存在的页面),检查 Sentry 和告警是否在 5 分钟内触发
  5. 日志追踪:执行一个完整操作(注册 → 登录 → 创建资源),在日志系统中按用户 ID 查询,确认所有相关日志都能被检索到

总结

全球化 SaaS 的基础设施不需要一开始就做到极致。域名拆分、CDN 加速、支付可靠性、邮件送达率、基础监控——这五项是 MVP 阶段必须可靠的。多区域部署、数据驻留、SOC 2 合规可以等到有真实用户后再优化。

关键原则是:支付和邮件比页面动效更重要。用户不能付款或收不到登录邮件,增长流量会被直接浪费。先把关键链路做可靠,再追求极致的性能和合规。

参考资料

  1. AWS. Disaster Recovery of Workloads on AWS. AWS Whitepaper
  2. AWS Architecture Blog. Disaster Recovery (DR) Architecture on AWS, Part IV: Multi-site Active-Active. AWS Blog
  3. Stripe. Receive Stripe events in your webhook endpoint. Stripe Docs
  4. Stripe Dev Blog. Building resilient webhook handlers in AWS: Implementing DLQs for Stripe Events. Stripe Dev
  5. Cloudflare. Egress-Free Object Storage — Cloudflare R2. Cloudflare
  6. Railway Blog. The Best PaaS for Multi-Region Deployments in 2026. Railway
  7. Fly.io. Multi-region databases and fly-replay. Fly.io Docs
  8. Alation. Data Residency for SaaS: How to Build Compliant Architecture. Alation Blog
  9. Sprinto. SOC 2 vs GDPR Explained (2026): Map Once, Comply Twice. Sprinto Blog
  10. Middleware.io. 10 Observability Best Practices Every DevOps Should Implement. Middleware Blog

评论 0

0 / 1000