PHPAB测试怎么实现

wen PHP项目 16

本文目录导读:

PHPAB测试怎么实现

  1. 基本AB测试实现
  2. 基于Cookie的AB测试
  3. 基于数据库的AB测试系统
  4. 完整AB测试框架示例
  5. 最佳实践建议

我来详细解释PHP实现AB测试的几种常见方法:

基本AB测试实现

基于用户ID或Session的随机分配

<?php
class ABTest {
    private $redis;
    private $experiments = [];
    public function __construct($redis = null) {
        $this->redis = $redis ?: new Redis();
    }
    // 注册实验
    public function registerExperiment($name, $variants) {
        $this->experiments[$name] = $variants;
    }
    // 获取用户分组
    public function getVariant($experimentName, $userId) {
        if (!isset($this->experiments[$experimentName])) {
            return null;
        }
        $variants = $this->experiments[$experimentName];
        $cacheKey = "abtest:{$experimentName}:{$userId}";
        // 检查用户是否已有分组
        $variant = $this->redis->get($cacheKey);
        if ($variant !== false) {
            return $variant;
        }
        // 使用用户ID进行一致性哈希分配
        $hash = crc32($userId . $experimentName);
        $index = abs($hash) % count($variants);
        $variant = $variants[$index];
        // 缓存分组结果
        $this->redis->setex($cacheKey, 86400 * 30, $variant);
        return $variant;
    }
}
// 使用示例
$abTest = new ABTest();
$abTest->registerExperiment('button_color', ['red', 'blue', 'green']);
$userId = $_SESSION['user_id'] ?? session_id();
$buttonColor = $abTest->getVariant('button_color', $userId);

基于Cookie的AB测试

<?php
class CookieABTest {
    private $cookiePrefix = 'abtest_';
    private $cookieExpire = 2592000; // 30天
    // 分配变体
    public function assignVariant($experimentName, $variants) {
        $cookieName = $this->cookiePrefix . $experimentName;
        // 检查Cookie中是否有分组
        if (isset($_COOKIE[$cookieName])) {
            return $_COOKIE[$cookieName];
        }
        // 随机分配变体
        $variant = $variants[array_rand($variants)];
        // 设置Cookie
        setcookie($cookieName, $variant, time() + $this->cookieExpire, '/');
        return $variant;
    }
    // 获取变体
    public function getVariant($experimentName) {
        $cookieName = $this->cookiePrefix . $experimentName;
        return $_COOKIE[$cookieName] ?? null;
    }
}
// 使用示例
$cookieTest = new CookieABTest();
$landingPage = $cookieTest->assignVariant('landing_page', [
    'page_a', 'page_b'
]);

基于数据库的AB测试系统

<?php
// 1. 数据表结构
/*
CREATE TABLE ab_experiments (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) UNIQUE,
    status ENUM('draft', 'running', 'paused', 'completed'),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE ab_variants (
    id INT PRIMARY KEY AUTO_INCREMENT,
    experiment_id INT,
    name VARCHAR(50),
    traffic_ratio DECIMAL(5,2), -- 流量比例
    FOREIGN KEY (experiment_id) REFERENCES ab_experiments(id)
);
CREATE TABLE ab_assignments (
    id INT PRIMARY KEY AUTO_INCREMENT,
    experiment_id INT,
    user_id VARCHAR(100),
    variant_id INT,
    assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY (experiment_id, user_id)
);
CREATE TABLE ab_events (
    id INT PRIMARY KEY AUTO_INCREMENT,
    assignment_id INT,
    event_type VARCHAR(50), -- 'view', 'click', 'conversion'
    event_data JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
*/
class DatabaseABTest {
    private $db;
    private $experimentCache = [];
    public function __construct(PDO $db) {
        $this->db = $db;
    }
    // 获取用户变体
    public function getVariant($experimentName, $userId) {
        // 获取实验信息
        $experiment = $this->getExperiment($experimentName);
        if (!$experiment || $experiment['status'] !== 'running') {
            return null;
        }
        // 检查是否已有分配
        $existingAssignment = $this->getExistingAssignment($experiment['id'], $userId);
        if ($existingAssignment) {
            return $existingAssignment;
        }
        // 分配新变体
        return $this->assignVariant($experiment, $userId);
    }
    // 记录事件
    public function trackEvent($experimentName, $userId, $eventType, $eventData = []) {
        $experiment = $this->getExperiment($experimentName);
        $assignment = $this->getExistingAssignment($experiment['id'], $userId);
        if ($assignment) {
            $stmt = $this->db->prepare("
                INSERT INTO ab_events (assignment_id, event_type, event_data)
                VALUES (?, ?, ?)
            ");
            $stmt->execute([
                $assignment['id'],
                $eventType,
                json_encode($eventData)
            ]);
        }
    }
    private function getExperiment($name) {
        if (!isset($this->experimentCache[$name])) {
            $stmt = $this->db->prepare("SELECT * FROM ab_experiments WHERE name = ?");
            $stmt->execute([$name]);
            $experiment = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($experiment) {
                // 获取变体
                $stmt = $this->db->prepare("SELECT * FROM ab_variants WHERE experiment_id = ?");
                $stmt->execute([$experiment['id']]);
                $experiment['variants'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
                $this->experimentCache[$name] = $experiment;
            }
        }
        return $this->experimentCache[$name] ?? null;
    }
    private function getExistingAssignment($experimentId, $userId) {
        $stmt = $this->db->prepare("
            SELECT a.*, v.name as variant_name
            FROM ab_assignments a
            JOIN ab_variants v ON a.variant_id = v.id
            WHERE a.experiment_id = ? AND a.user_id = ?
        ");
        $stmt->execute([$experimentId, $userId]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }
    private function assignVariant($experiment, $userId) {
        // 根据流量比例分配
        $rand = mt_rand(1, 100);
        $cumulative = 0;
        foreach ($experiment['variants'] as $variant) {
            $cumulative += $variant['traffic_ratio'];
            if ($rand <= $cumulative) {
                // 记录分配
                $stmt = $this->db->prepare("
                    INSERT INTO ab_assignments (experiment_id, user_id, variant_id)
                    VALUES (?, ?, ?)
                ");
                $stmt->execute([$experiment['id'], $userId, $variant['id']]);
                return [
                    'id' => $this->db->lastInsertId(),
                    'variant_name' => $variant['name']
                ];
            }
        }
        return null;
    }
}

完整AB测试框架示例

<?php
// ABTestManager.php - 完整管理类
class ABTestManager {
    private $experiments = [];
    private $results = [];
    private $logger;
    public function __construct(Logger $logger = null) {
        $this->logger = $logger ?: new NullLogger();
    }
    // 创建实验
    public function createExperiment($name, $config) {
        $this->experiments[$name] = [
            'name' => $name,
            'variants' => $config['variants'] ?? ['control', 'variant'],
            'metrics' => $config['metrics'] ?? ['conversion'],
            'sample_size' => $config['sample_size'] ?? 1000,
            'traffic_percentage' => $config['traffic_percentage'] ?? 100,
            'created_at' => time()
        ];
        $this->logger->info("Created experiment: {$name}");
        return $this;
    }
    // 运行实验
    public function runExperiment($name, $context) {
        if (!isset($this->experiments[$name])) {
            throw new Exception("Experiment {$name} not found");
        }
        $experiment = $this->experiments[$name];
        $variant = $this->assignVariant($experiment, $context);
        // 记录实验参与
        $this->recordParticipation($name, $variant, $context);
        return $variant;
    }
    // 跟踪指标
    public function trackMetric($experimentName, $metric, $value, $context) {
        $key = "{$experimentName}:{$metric}";
        if (!isset($this->results[$key])) {
            $this->results[$key] = [];
        }
        $variant = $this->getUserVariant($experimentName, $context);
        if ($variant) {
            $this->results[$key][$variant][] = $value;
        }
    }
    // 分析结果
    public function analyzeResults($experimentName) {
        $experiment = $this->experiments[$experimentName];
        $analysis = [];
        foreach ($experiment['metrics'] as $metric) {
            $key = "{$experimentName}:{$metric}";
            $data = $this->results[$key] ?? [];
            foreach ($experiment['variants'] as $variant) {
                $values = $data[$variant] ?? [];
                $analysis[$variant][$metric] = [
                    'count' => count($values),
                    'mean' => count($values) > 0 ? array_sum($values) / count($values) : 0,
                    'sum' => array_sum($values),
                    'values' => $values
                ];
            }
            // 计算统计显著性
            $analysis['significance'] = $this->calculateSignificance($analysis);
        }
        return $analysis;
    }
    private function assignVariant($experiment, $context) {
        $userId = $context['user_id'] ?? session_id();
        $hash = crc32($userId . $experiment['name']);
        // 检查流量百分比
        if (($hash % 100) >= $experiment['traffic_percentage']) {
            return null; // 不在实验范围内
        }
        $index = abs($hash) % count($experiment['variants']);
        return $experiment['variants'][$index];
    }
    private function recordParticipation($experimentName, $variant, $context) {
        $this->logger->info("User {$context['user_id']} in experiment {$experimentName}, variant: {$variant}");
    }
    private function getUserVariant($experimentName, $context) {
        // 从缓存或数据库获取用户分组
        $userId = $context['user_id'] ?? session_id();
        $hash = crc32($userId . $experimentName);
        $experiment = $this->experiments[$experimentName];
        if (($hash % 100) >= $experiment['traffic_percentage']) {
            return null;
        }
        $index = abs($hash) % count($experiment['variants']);
        return $experiment['variants'][$index];
    }
    private function calculateSignificance($analysis) {
        // 简单的卡方检验实现
        // 实际应用中可以使用更复杂的统计方法
        return [
            'method' => 'chi_square',
            'p_value' => 0.05, // 示例值
            'significant' => true
        ];
    }
}
// 使用示例
$abManager = new ABTestManager();
// 创建实验
$abManager->createExperiment('pricing_page_v2', [
    'variants' => ['current', 'new_design'],
    'metrics' => ['click_rate', 'conversion_rate', 'revenue'],
    'sample_size' => 5000,
    'traffic_percentage' => 50
]);
// 分配用户并跟踪
$context = [
    'user_id' => 'user_123',
    'page' => '/pricing',
    'timestamp' => time()
];
$variant = $abManager->runExperiment('pricing_page_v2', $context);
// 跟踪用户行为
$abManager->trackMetric('pricing_page_v2', 'click_rate', 1, $context);
$abManager->trackMetric('pricing_page_v2', 'conversion_rate', 0, $context);
// 分析结果
$results = $abManager->analyzeResults('pricing_page_v2');

最佳实践建议

注意事项:

  1. 样本量计算:确保足够的样本量来获得统计显著性
  2. 随机化:使用一致的哈希算法确保用户分组稳定
  3. 数据隔离:不同实验应该独立运行,避免相互影响
  4. 监控和告警:实时监控实验指标,发现异常及时停止
  5. 长期影响:考虑实验可能带来的长期影响

性能优化:

// 使用缓存减少数据库查询
class OptimizedABTest {
    private $cache;
    private $ttl = 3600;
    public function getVariant($experiment, $userId) {
        $cacheKey = "abtest:{$experiment}:{$userId}";
        // 尝试从缓存获取
        if ($variant = $this->cache->get($cacheKey)) {
            return $variant;
        }
        // 计算并缓存
        $variant = $this->calculateVariant($experiment, $userId);
        $this->cache->set($cacheKey, $variant, $this->ttl);
        return $variant;
    }
}

这就是PHP实现AB测试的完整方案,根据你的具体需求选择合适的方法,对于简单场景可以使用Cookie或Session,对于复杂场景建议使用数据库+缓存的方式。

抱歉,评论功能暂时关闭!