使用Cloudflare Workers实现边缘A/B测试

本文详解如何使用Cloudflare Workers在边缘节点实现A/B测试,无需修改源服务器代码。

传统A/B测试 vs 边缘A/B测试

方案 实现位置 延迟 准确度
服务器端(PHP/Node) 源服务器 高(增加服务器负载)
客户端(JavaScript) 浏览器 中(闪烁问题) 低(JS被阻止)
边缘(Cloudflare Workers) 边缘节点(全球300+) 极低(< 10ms)

基础A/B测试实现

Worker代码(两个版本)

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  // 1. 读取Cookie(已参与过实验的用户保持相同版本)
  const cookie = request.headers.get('Cookie') || ''
  let variant = getVariantFromCookie(cookie)

  // 2. 新用户随机分配
  if (!variant) {
    variant = Math.random() < 0.5 ? 'A' : 'B'
  }

  // 3. 从源服务器获取页面
  let response = await fetch(request)

  // 4. 修改HTML(注入变体内容)
  let html = await response.text()

  if (variant === 'A') {
    html = html.replace('<h1>欢迎</h1>', '<h1>欢迎(版本A - 绿色按钮)</h1>')
    html = html.replace('class="btn"', 'class="btn btn-green"')
  } else {
    html = html.replace('<h1>欢迎</h1>', '<h1>欢迎(版本B - 红色按钮)</h1>')
    html = html.replace('class="btn"', 'class="btn btn-red"')
  }

  // 5. 设置Cookie(记住用户变体)
  const newResponse = new Response(html, {
    headers: {
      'Content-Type': 'text/html',
      'Set-Cookie': `ab_variant=${variant}; Max-Age=604800; Path=/`,
      'Cache-Control': 'no-cache',
    },
  })

  return newResponse
}

function getVariantFromCookie(cookie) {
  const match = cookie.match(/ab_variant=(A|B)/)
  return match ? match[1] : null
}

高级A/B测试(多变量 + 统计分析)

三个变量测试

async function handleRequest(request) {
  const cookie = request.headers.get('Cookie') || ''
  let variant = getVariantFromCookie(cookie)

  if (!variant) {
    // 随机分配:A(33%) B(33%) C(34%)
    const rand = Math.random()
    if (rand < 0.33) variant = 'A'
    else if (rand < 0.66) variant = 'B'
    else variant = 'C'
  }

  let response = await fetch(request)
  let html = await response.text()

  // 版本A:原价
  if (variant === 'A') {
    // 不修改
  }
  // 版本B:限时折扣
  else if (variant === 'B') {
    html = html.replace('¥199', '¥149 <span class="badge">限时折扣</span>')
  }
  // 版本C:免费试用
  else {
    html = html.replace('立即购买', '免费试用7天')
  }

  return new Response(html, {
    headers: {
      'Content-Type': 'text/html',
      'Set-Cookie': `ab_variant=${variant}; Max-Age=604800; Path=/`,
    },
  })
}

发送数据到Google Analytics

// 在Worker中发送GA4事件
async function sendGAEvent(variant, request) {
  const response = await fetch('https://www.google-analytics.com/mp/collect', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      client_id: getClientId(request),
      events: [{
        name: 'ab_test_view',
        params: {
          variant: variant,
          test_name: 'homepage_button_color',
        },
      }],
    }),
  })
}

function getClientId(request) {
  const ip = request.headers.get('CF-Connecting-IP')
  const ua = request.headers.get('User-Agent')
  // 简单哈希作为client_id(或使用Cookie)
  return btoa(ip + ua).slice(0, 20)
}

边缘KV存储(持久化计数)

使用Cloudflare KV

// 在Cloudflare Dashboard中创建KV namespace
// 绑定到Worker:Variable name = AB_TEST_KV

async function handleRequest(request) {
  const variant = Math.random() < 0.5 ? 'A' : 'B'

  // 记录展示次数
  let impressions = await AB_TEST_KV.get(variant + '_impressions') || 0
  await AB_TEST_KV.put(variant + '_impressions', parseInt(impressions) + 1)

  // 获取页面并修改
  let response = await fetch(request)
  let html = await response.text()

  if (variant === 'A') {
    html = html.replace('注册', '开始免费试用')
  } else {
    html = html.replace('注册', '立即注册(限时优惠)')
  }

  return new Response(html, {
    headers: {
      'Content-Type': 'text/html',
      'Set-Cookie': `ab_variant=${variant}; Max-Age=604800`,
    },
  })
}

读取KV统计(管理API)

// 在另一个Worker或API路由中
app.get('/ab-stats', async (request) => {
  const variantA = await AB_TEST_KV.get('A_impressions') || 0
  const variantB = await AB_TEST_KV.get('B_impressions') || 0

  return new Response(JSON.stringify({
    variant_A: parseInt(variantA),
    variant_B: parseInt(variantB),
  }), {
    headers: { 'Content-Type': 'application/json' },
  })
})

完整的A/B测试Dashboard

使用Cloudflare Workers + HTML

// worker.js - A/B测试Dashboard
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const url = new URL(request.url)

  // API:获取统计数据
  if (url.pathname === '/api/stats') {
    const stats = {
      A: {
        impressions: await AB_TEST_KV.get('A_impressions') || 0,
        conversions: await AB_TEST_KV.get('A_conversions') || 0,
      },
      B: {
        impressions: await AB_TEST_KV.get('B_impressions') || 0,
        conversions: await AB_TEST_KV.get('B_conversions') || 0,
      },
    }
    return new Response(JSON.stringify(stats), {
      headers: { 'Content-Type': 'application/json' },
    })
  }

  // Dashboard页面
  const html = `
    <!DOCTYPE html>
    <html>
    <head>
      <title>A/B Test Dashboard</title>
      <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    </head>
    <body>
      <h1>A/B Test Results</h1>
      <canvas id="chart"></canvas>
      <script>
        fetch('/api/stats')
          .then(r => r.json())
          .then(data => {
            new Chart(document.getElementById('chart'), {
              type: 'bar',
              data: {
                labels: ['Version A', 'Version B'],
                datasets: [{
                  label: 'Conversions',
                  data: [data.A.conversions, data.B.conversions],
                }],
              },
            })
          })
      </script>
    </body>
    </html>
  `

  return new Response(html, {
    headers: { 'Content-Type': 'text/html' },
  })
}

边缘A/B测试的优势

优势 说明
零服务器负载 在Cloudflare边缘节点执行,不消耗源服务器资源
全球低延迟 300+ 边缘节点,用户从最近的节点获取变体
无闪烁 在HTML发送到浏览器之前就完成替换
绕过广告拦截器 修改HTML,不依赖JavaScript
实时调整流量比例 通过KV存储动态调整分配比例

实战:WordPress集成

在WordPress中查看A/B测试结果

// 在WordPress后台添加Dashboard Widget
add_action('wp_dashboard_setup', function() {
  wp_add_dashboard_widget('ab_test_widget', 'A/B Test Results', 'display_ab_stats');
});

function display_ab_stats() {
  // 从Cloudflare API获取统计数据
  $stats = wp_remote_get('https://your-worker.your-subdomain.workers.dev/api/stats', [
    'headers' => [
      'Authorization' => 'Bearer ' . get_option('cloudflare_api_token'),
    ],
  ]);

  $data = json_decode(wp_remote_retrieve_body($stats));

  echo '<table class="widefat">';
  echo '<tr><th>版本</th><th>展示</th><th>转化</th><th>转化率</th></tr>';
  foreach (['A', 'B'] as $v) {
    $impressions = $data->$v->impressions;
    $conversions = $data->$v->conversions;
    $rate = $impressions > 0 ? round($conversions / $impressions * 100, 2) : 0;
    echo "<tr><td>$v</td><td>$impressions</td><td>$conversions</td><td>$rate%</td></tr>";
  }
  echo '</table>';
}

发布Worker到Cloudflare

使用Wrangler CLI

# 安装Wrangler
npm install -g wrangler

# 登录Cloudflare
wrangler login

# 创建Worker项目
wrangler init ab-test-worker

# 编辑 src/index.js(粘贴上面的Worker代码)

# 创建KV namespace
wrangler kv:namespace create "AB_TEST_KV"

# 更新 wrangler.toml
# kv_namespaces = [
#   { binding = "AB_TEST_KV", id = "..." }
# ]

# 发布
wrangler publish

在Cloudflare Dashboard中配置

1. 登录 https://dash.cloudflare.com/
2. Workers & Pages  Create Application
3. 选择 "Create Worker"
4. 粘贴Worker代码
5. 部署
6.  "Triggers" 中设置路由 example.com/*

A/B测试最佳实践

  1. 样本量足够:每个变体至少1000次展示才具有统计意义
  2. 测试周期:至少运行1-2周(包含工作日和周末)
  3. 单变量测试:一次只测试一个元素(按钮颜色 OR 文案,不能同时测试)
  4. 统计显著性:使用卡方检验或在线计算器验证结果(p-value < 0.05)
  5. 避免工作日偏差:不要在周一上线测试、周五就停止

常见问题

问题 原因 解决方案
两个版本都显示 Cookie未正确设置 检查Set-Cookie
统计数据不准确 爬虫也计入展示 在Worker中过滤爬虫UA
转化率异常高 重复计数 使用IP+User-Agent哈希去重
KV写入失败 免费额度限制 升级到Workers Paid Plan

通过Cloudflare Workers实现A/B测试,无需修改服务器代码,延迟极低,是2026年最先进的A/B测试方案。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。