本文目录导读:

我将为您提供一个完整的PHP功能灰度发布解决方案,包含多种实现方式和完整的示例代码。
基础灰度发布类
<?php
/**
* 灰度发布管理器
*/
class GrayReleaseManager
{
private $redis;
private $config;
/**
* @param Redis $redis Redis连接
* @param array $config 配置数组
*/
public function __construct($redis = null, array $config = [])
{
$this->redis = $redis ?: new Redis();
$this->config = array_merge([
'default_percent' => 0, // 默认灰度比例 0-100
'storage_prefix' => 'gray:', // 存储前缀
'whitelist' => [], // 白名单用户
'blacklist' => [], // 黑名单用户
'cache_time' => 3600 // 缓存时间
], $config);
}
/**
* 获取功能灰度状态
* @param string $feature 功能名称
* @param string $userId 用户ID
* @return bool 是否启用灰度
*/
public function isEnabled($feature, $userId = '')
{
// 检查是否强制开启
if ($this->isForceEnabled($feature)) {
return true;
}
// 检查是否强制关闭
if ($this->isForceDisabled($feature)) {
return false;
}
// 检查黑白名单
if ($userId && $this->checkUserList($userId)) {
return true;
}
// 获取灰度比例
$percent = $this->getFeaturePercent($feature);
if ($percent <= 0) {
return false;
}
if ($percent >= 100) {
return true;
}
// 根据用户ID计算哈希值
$hash = $this->getUserHash($feature, $userId);
return $hash <= $percent;
}
/**
* 获取功能灰度比例
* @param string $feature 功能名称
* @return int 灰度比例 0-100
*/
public function getFeaturePercent($feature)
{
$key = $this->config['storage_prefix'] . 'percent:' . $feature;
// 尝试从缓存获取
$cached = $this->getCache($key);
if ($cached !== false) {
return (int)$cached;
}
// 从配置获取
$percent = isset($this->config['features'][$feature])
? $this->config['features'][$feature]
: $this->config['default_percent'];
// 保存到缓存
$this->setCache($key, $percent);
return $percent;
}
/**
* 设置功能灰度比例
* @param string $feature 功能名称
* @param int $percent 灰度比例 0-100
*/
public function setFeaturePercent($feature, $percent)
{
$percent = max(0, min(100, (int)$percent));
$key = $this->config['storage_prefix'] . 'percent:' . $feature;
if ($this->redis) {
$this->redis->set($key, $percent, $this->config['cache_time']);
}
// 更新配置
$this->config['features'][$feature] = $percent;
}
/**
* 获取用户灰度状态
* @param string $feature 功能名称
* @param string $userId 用户ID
* @return array 包含是否启用和相关信息
*/
public function getUserGrayStatus($feature, $userId)
{
$enabled = $this->isEnabled($feature, $userId);
return [
'enabled' => $enabled,
'feature' => $feature,
'user_id' => $userId,
'percent' => $this->getFeaturePercent($feature),
'timestamp' => time()
];
}
/**
* 获取用户灰度哈希值
* @param string $feature 功能名称
* @param string $userId 用户ID
* @return int 哈希值 0-100
*/
private function getUserHash($feature, $userId)
{
if (empty($userId)) {
// 未登录用户使用IP
$userId = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
}
// 组合特征和用户ID生成哈希值
$combined = $feature . ':' . $userId;
$hash = crc32($combined);
// 转换为0-100的范围
return ($hash % 100) + 1;
}
/**
* 检查用户是否在白名单或黑名单
* @param string $userId 用户ID
* @return bool 是否通过检查
*/
private function checkUserList($userId)
{
$whiteList = $this->config['whitelist'] ?? [];
$blackList = $this->config['blacklist'] ?? [];
if (in_array($userId, $blackList)) {
return false;
}
return in_array($userId, $whiteList);
}
/**
* 检查功能是否强制开启
* @param string $feature 功能名称
* @return bool
*/
private function isForceEnabled($feature)
{
$key = $this->config['storage_prefix'] . 'force_enabled:' . $feature;
return $this->getCache($key) === '1';
}
/**
* 检查功能是否强制关闭
* @param string $feature 功能名称
* @return bool
*/
private function isForceDisabled($feature)
{
$key = $this->config['storage_prefix'] . 'force_disabled:' . $feature;
return $this->getCache($key) === '1';
}
/**
* 获取缓存数据
* @param string $key 缓存键
* @return mixed 缓存值
*/
private function getCache($key)
{
if ($this->redis) {
return $this->redis->get($key);
}
// 文件缓存实现
$cacheFile = sys_get_temp_dir() . '/' . md5($key) . '.cache';
if (file_exists($cacheFile)) {
$data = unserialize(file_get_contents($cacheFile));
if ($data['expire'] > time()) {
return $data['value'];
}
}
return false;
}
/**
* 设置缓存数据
* @param string $key 缓存键
* @param mixed $value 缓存值
*/
private function setCache($key, $value)
{
if ($this->redis) {
$this->redis->setex($key, $this->config['cache_time'], $value);
return;
}
// 文件缓存实现
$cacheFile = sys_get_temp_dir() . '/' . md5($key) . '.cache';
$data = [
'value' => $value,
'expire' => time() + $this->config['cache_time']
];
file_put_contents($cacheFile, serialize($data));
}
}
高级灰度发布类(支持更多功能)
<?php
/**
* 高级灰度发布管理器
* 支持按用户、IP、地区、设备等多维度灰度
*/
class AdvancedGrayRelease
{
private $storage;
private $logger;
private $config;
/**
* @param StorageInterface $storage 存储接口
* @param LoggerInterface $logger 日志接口
* @param array $config 配置
*/
public function __construct($storage, $logger, array $config = [])
{
$this->storage = $storage;
$this->logger = $logger;
$this->config = array_merge([
'rules' => [], // 灰度规则
'strategy' => 'hash', // 默认策略:hash-哈希,session-会话,ip-IP
'enable_log' => true // 是否启用日志
], $config);
}
/**
* 执行灰度判断
* @param string $feature 功能名称
* @param array $context 上下文信息
* @return bool 是否启用
*/
public function evaluate($feature, array $context = [])
{
// 获取功能配置
$featureConfig = $this->getFeatureConfig($feature);
// 检查是否完全开启或关闭
if ($featureConfig['status'] === 'full') {
return true;
}
if ($featureConfig['status'] === 'off') {
return false;
}
// 获取用户信息
$userId = $context['user_id'] ?? $this->getClientIp();
// 检查规则匹配
foreach ($featureConfig['rules'] as $rule) {
if ($this->matchRule($rule, $context)) {
return true;
}
}
// 根据策略进行灰度
$percent = $featureConfig['percent'] ?? 0;
switch ($featureConfig['strategy'] ?? $this->config['strategy']) {
case 'session':
// 基于会话的灰度
return $this->sessionBased($feature, $percent, $userId);
case 'ip':
// 基于IP的灰度
return $this->ipBased($feature, $percent, $this->getClientIp());
case 'ab_test':
// A/B测试灰度
return $this->abTest($feature, $percent, $userId);
default:
// 默认使用哈希灰度
return $this->hashBased($feature, $percent, $userId);
}
}
/**
* 哈希灰度策略
*/
private function hashBased($feature, $percent, $userId)
{
$seed = $this->getSeed($feature, $userId);
$hash = $seed % 100;
return $hash < $percent;
}
/**
* 基于会话的灰度
*/
private function sessionBased($feature, $percent, $userId)
{
$sessionKey = 'gray_session_' . md5($feature);
if (!isset($_SESSION[$sessionKey])) {
$rand = random_int(1, 100);
$_SESSION[$sessionKey] = $rand <= $percent;
}
return $_SESSION[$sessionKey];
}
/**
* 基于IP的灰度
*/
private function ipBased($feature, $percent, $ip)
{
$ipHash = crc32($ip . ':' . $feature) % 100;
return $ipHash < $percent;
}
/**
* A/B测试
*/
private function abTest($feature, $percent, $userId)
{
$testKey = 'test_' . $feature . '_' . $userId;
// 检查是否已有分组
$group = $this->storage->get($testKey);
if ($group === null) {
$group = random_int(1, 100) <= $percent ? 'A' : 'B';
$this->storage->set($testKey, $group, 3600 * 24 * 30);
}
return $group === 'A';
}
/**
* 获取灰度种子
*/
private function getSeed($feature, $userId)
{
$combined = $feature . '_' . $userId . '_' . date('Y-m-d');
return crc32($combined);
}
/**
* 匹配规则
*/
private function matchRule($rule, $context)
{
$type = $rule['type'] ?? 'user_id';
$values = $rule['values'] ?? [];
$match = $rule['match'] ?? 'in';
switch ($type) {
case 'user_id':
$actual = $context['user_id'] ?? '';
break;
case 'ip':
$actual = $this->getClientIp();
break;
case 'region':
$actual = $context['region'] ?? '';
break;
case 'device':
$actual = $this->detectDevice();
break;
default:
$actual = null;
}
if ($actual === null) {
return false;
}
switch ($match) {
case 'in':
return in_array($actual, $values);
case 'not_in':
return !in_array($actual, $values);
case 'regex':
foreach ($values as $pattern) {
if (preg_match($pattern, $actual)) {
return true;
}
}
return false;
default:
return false;
}
}
/**
* 获取功能配置
*/
private function getFeatureConfig($feature)
{
return $this->config['rules'][$feature] ?? [
'status' => 'off',
'percent' => 0,
'strategy' => 'hash',
'rules' => []
];
}
/**
* 获取客户端IP
*/
private function getClientIp()
{
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if (!empty($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR'];
}
return 'unknown';
}
/**
* 检测设备类型
*/
private function detectDevice()
{
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (preg_match('/mobile/i', $userAgent)) {
return 'mobile';
}
if (preg_match('/tablet/i', $userAgent)) {
return 'tablet';
}
return 'desktop';
}
}
灰度发布控制器
<?php
/**
* 灰度发布控制器
* 用于管理灰度功能和用户
*/
class GrayReleaseController
{
private $manager;
private $adminToken;
/**
* 构造函数
*/
public function __construct()
{
$this->manager = new GrayReleaseManager(new Redis());
$this->adminToken = 'your_admin_token'; // 管理令牌
}
/**
* 处理请求
*/
public function handleRequest()
{
$action = $_GET['action'] ?? '';
$token = $_GET['token'] ?? '';
// 验证管理权限
if (!$this->validateToken($token)) {
$this->response(['error' => 'Unauthorized'], 401);
}
switch ($action) {
case 'set_percent':
$this->setPercent();
break;
case 'get_status':
$this->getStatus();
break;
case 'add_whitelist':
$this->addWhitelist();
break;
case 'remove_whitelist':
$this->removeWhitelist();
break;
case 'force_enable':
$this->forceEnable();
break;
case 'force_disable':
$this->forceDisable();
break;
default:
$this->response(['error' => 'Invalid action'], 400);
}
}
/**
* 设置灰度比例
*/
private function setPercent()
{
$feature = $_POST['feature'] ?? '';
$percent = isset($_POST['percent']) ? (int)$_POST['percent'] : 0;
if (empty($feature)) {
$this->response(['error' => 'Feature required'], 400);
}
$this->manager->setFeaturePercent($feature, $percent);
$this->response(['success' => true, 'feature' => $feature, 'percent' => $percent]);
}
/**
* 获取灰度状态
*/
private function getStatus()
{
$feature = $_GET['feature'] ?? '';
if (empty($feature)) {
$this->response(['error' => 'Feature required'], 400);
}
$status = [
'feature' => $feature,
'percent' => $this->manager->getFeaturePercent($feature),
'enabled_count' => $this->getEnabledUsers($feature),
'total_users' => $this->getTotalUsers()
];
$this->response($status);
}
/**
* 添加白名单用户
*/
private function addWhitelist()
{
$feature = $_POST['feature'] ?? '';
$userId = $_POST['user_id'] ?? '';
if (empty($feature) || empty($userId)) {
$this->response(['error' => 'Feature and user_id required'], 400);
}
// 添加白名单
$key = 'gray:whitelist:' . $feature;
$this->manager->addToSet($key, $userId);
$this->response(['success' => true, 'message' => 'User added to whitelist']);
}
/**
* 强制开启功能
*/
private function forceEnable()
{
$feature = $_POST['feature'] ?? '';
if (empty($feature)) {
$this->response(['error' => 'Feature required'], 400);
}
$this->manager->setFeaturePercent($feature, 100);
$this->response(['success' => true, 'message' => 'Feature force enabled']);
}
/**
* 强制关闭功能
*/
private function forceDisable()
{
$feature = $_POST['feature'] ?? '';
if (empty($feature)) {
$this->response(['error' => 'Feature required'], 400);
}
$this->manager->setFeaturePercent($feature, 0);
$this->response(['success' => true, 'message' => 'Feature force disabled']);
}
/**
* 验证管理员令牌
*/
private function validateToken($token)
{
return hash_equals($this->adminToken, $token);
}
/**
* 获取启用用户数
*/
private function getEnabledUsers($feature)
{
// 实际应用中这里应该从数据库或缓存获取
return 0;
}
/**
* 获取总用户数
*/
private function getTotalUsers()
{
// 实际应用中这里应该从数据库获取
return 0;
}
/**
* 响应处理
*/
private function response($data, $status = 200)
{
http_response_code($status);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
}
// 使用示例
// $controller = new GrayReleaseController();
// $controller->handleRequest();
实用功能示例
<?php
/**
* 灰度发布实用功能示例
*/
class GrayReleaseExample
{
private $grayManager;
public function __construct()
{
$config = [
'default_percent' => 20,
'whitelist' => ['admin', 'tester'],
'blacklist' => [],
'features' => [
'new_ui' => 30,
'new_payment' => 50,
'recommend_system' => 10
]
];
$this->grayManager = new GrayReleaseManager(new Redis(), $config);
}
/**
* 示例1:简单的功能灰度
*/
public function example1()
{
$userId = $_SESSION['user_id'] ?? '';
// 检查新UI功能是否启用
if ($this->grayManager->isEnabled('new_ui', $userId)) {
return $this->renderNewUI();
} else {
return $this->renderOldUI();
}
}
/**
* 示例2:不同功能使用不同灰度策略
*/
public function example2()
{
$feature = 'new_payment';
$userId = $_SESSION['user_id'] ?? '';
$status = $this->grayManager->getUserGrayStatus($feature, $userId);
if ($status['enabled']) {
// 使用新版支付
return $this->processPaymentV2();
} else {
// 使用旧版支付
return $this->processPaymentV1();
}
}
/**
* 示例3:实时监控灰度效果
*/
public function example3()
{
$feature = 'new_ui';
$userId = $_SESSION['user_id'] ?? '';
// 记录用户流量
$this->recordTraffic($feature, $userId);
$enabled = $this->grayManager->isEnabled($feature, $userId);
// 记录用户行为
if ($enabled) {
$this->recordMetric('gray_' . $feature . '_enabled');
} else {
$this->recordMetric('gray_' . $feature . '_disabled');
}
return $enabled ? 'new_ui' : 'old_ui';
}
/**
* 示例4:灰度降级
*/
public function example4()
{
$userId = $_SESSION['user_id'] ?? '';
$feature = 'recommend_system';
try {
// 尝试使用推荐系统
if ($this->grayManager->isEnabled($feature, $userId)) {
return $this->getRecommendationsV2();
} else {
return $this->getRecommendationsV1();
}
} catch (Exception $e) {
// 发生错误时降级到旧版本
$this->grayManager->setFeaturePercent($feature, 0);
return $this->getRecommendationsV1();
}
}
/**
* 示例5:A/B测试
*/
public function example5()
{
$userId = $_SESSION['user_id'] ?? '';
$feature = 'ab_test_homepage';
$advancedGray = new AdvancedGrayRelease(
new RedisStorageAdapter(new Redis()),
new SimpleLogger(),
[
'rules' => [
'ab_test_homepage' => [
'status' => 'partial',
'percent' => 50,
'strategy' => 'ab_test'
]
]
]
);
$useNewHomepage = $advancedGray->evaluate($feature, ['user_id' => $userId]);
return $useNewHomepage ? 'new_homepage' : 'old_homepage';
}
// 辅助方法
private function renderNewUI() { return 'new ui'; }
private function renderOldUI() { return 'old ui'; }
private function processPaymentV1() { return 'payment v1'; }
private function processPaymentV2() { return 'payment v2'; }
private function getRecommendationsV1() { return 'rec v1'; }
private function getRecommendationsV2() { return 'rec v2'; }
private function recordTraffic($feature, $userId) { /* 实现 */ }
private function recordMetric($name) { /* 实现 */ }
}
/**
* Redis存储适配器(示例)
*/
class RedisStorageAdapter
{
private $redis;
public function __construct($redis)
{
$this->redis = $redis;
}
public function get($key)
{
return $this->redis->get($key);
}
public function set($key, $value, $ttl = null)
{
if ($ttl) {
return $this->redis->setex($key, $ttl, $value);
}
return $this->redis->set($key, $value);
}
}
/**
* 简单日志记录器(示例)
*/
class SimpleLogger
{
public function log($message, $type = 'info')
{
file_put_contents(
__DIR__ . '/gray_release.log',
date('Y-m-d H:i:s') . " [$type] $message\n",
FILE_APPEND
);
}
}
管理界面示例
<!DOCTYPE html>
<html>
<head>灰度发布管理</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.feature-list { margin: 20px 0; }
.feature-item { border: 1px solid #ddd; padding: 15px; margin-bottom: 10px; }
.percent-input { width: 80px; }
button { margin: 5px; padding: 8px 15px; cursor: pointer; }
.success { color: green; }
.error { color: red; }
</style>
</head>
<body>
<h1>灰度发布管理</h1>
<div class="feature-list">
<div class="feature-item">
<h3>新UI功能</h3>
<label>灰度比例: <input type="number" class="percent-input" id="new_ui_percent" value="30" min="0" max="100">%</label>
<button onclick="updatePercent('new_ui')">更新</button>
<button onclick="forceEnable('new_ui')">强制开启</button>
<button onclick="forceDisable('new_ui')">强制关闭</button>
<div id="new_ui_status"></div>
</div>
<div class="feature-item">
<h3>新版支付</h3>
<label>灰度比例: <input type="number" class="percent-input" id="new_payment_percent" value="50" min="0" max="100">%</label>
<button onclick="updatePercent('new_payment')">更新</button>
<button onclick="forceEnable('new_payment')">强制开启</button>
<button onclick="forceDisable('new_payment')">强制关闭</button>
<div id="new_payment_status"></div>
</div>
<div class="feature-item">
<h3>推荐系统</h3>
<label>灰度比例: <input type="number" class="percent-input" id="recommend_system_percent" value="10" min="0" max="100">%</label>
<button onclick="updatePercent('recommend_system')">更新</button>
<button onclick="forceEnable('recommend_system')">强制开启</button>
<button onclick="forceDisable('recommend_system')">强制关闭</button>
<div id="recommend_system_status"></div>
</div>
</div>
<script>
const token = 'your_admin_token';
function updatePercent(feature) {
const percent = document.getElementById(feature + '_percent').value;
fetch('gray_controller.php?action=set_percent&token=' + token, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'feature=' + feature + '&percent=' + percent
})
.then(response => response.json())
.then(data => {
showMessage(feature + '_status', data.message || '更新成功', 'success');
})
.catch(error => {
showMessage(feature + '_status', error.message, 'error');
});
}
function forceEnable(feature) {
fetch('gray_controller.php?action=force_enable&token=' + token, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'feature=' + feature
})
.then(response => response.json())
.then(data => {
document.getElementById(feature + '_percent').value = '100';
showMessage(feature + '_status', data.message || '已强制开启', 'success');
});
}
function forceDisable(feature) {
fetch('gray_controller.php?action=force_disable&token=' + token, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'feature=' + feature
})
.then(response => response.json())
.then(data => {
document.getElementById(feature + '_percent').value = '0';
showMessage(feature + '_status', data.message || '已强制关闭', 'success');
});
}
function showMessage(elementId, message, type) {
const element = document.getElementById(elementId);
element.innerHTML = `<span class="${type}">${message}</span>`;
setTimeout(() => element.innerHTML = '', 3000);
}
</script>
</body>
</html>
使用建议
配置示例
// config/gray_release.php
return [
'default_percent' => 0,
'whitelist' => [
'admin',
'tester',
'employee1@company.com'
],
'blacklist' => [],
'features' => [
'new_ui' => 30,
'new_payment' => 50,
'recommend_system' => 10,
'social_share' => 5
],
'strategy' => 'hash',
'cache' => [
'type' => 'redis',
'host' => '127.0.0.1',
'port' => 6379,
'prefix' => 'gray:',
'ttl' => 3600
],
'logging' => [
'enabled' => true,
'path' => '/var/log/gray_release/'
]
];
这个解决方案提供了完整的灰度发布功能,包括基础灰度控制、高级策略支持、管理界面等,您可以根据实际需求进行配置和扩展。