灰度发布应通过配置开关而非硬编码,laravel可用配置驱动+中间件+缓存实现:开关状态存redis等缓存,用featuregate中间件按请求条件判断,缓存键按"feature:name:scope:value"设计,辅以规则表支持动态管理。

灰度发布靠配置开关,不是改路由或注释代码
硬编码开关(比如 if ($env === 'gray') { ... })会让逻辑散落在各处,上线后难维护。Laravel 原生不提供灰度能力,但可以用「配置驱动 + 中间件 + 缓存」组合实现可控、可回滚的开关。核心是把“是否启用某功能”变成一个可动态读取、可按用户/请求条件判断的值。
- 开关状态必须存在缓存(如
redis或apc),不能每次读配置文件——否则高并发下 IO 拖垮响应 - 不要用
.env控制灰度,它只能全局生效,且修改后需重载配置,无法做到 per-request 级别分流 - 推荐用
config('feature.enable_new_checkout')读取,背后由自定义配置服务提供真实值,而非直接写死在config/features.php里
Laravel 中间件做请求级灰度路由
功能开关常要按用户 ID、Header、IP 段或 A/B 测试分组来决定是否生效,这必须在请求进入控制器前拦截判断。中间件是最自然的位置——它能访问 $request,也能提前终止或打标。
- 新建中间件
FeatureGate,在handle()里调用FeatureManager::shouldEnable('new_search') -
shouldEnable()内部根据当前请求提取$request->header('X-Gray-Group')或$request->user()?->id查规则表或缓存键 - 别在中间件里写业务逻辑,只做“放行 / 404 / 重定向到旧版”,把具体行为留给控制器处理
- 注意中间件顺序:它必须在
auth之后(才能拿到用户),但在throttle之前(避免灰度用户被误限流)
缓存键设计决定灰度灵活性和性能
灰度开关本质是“条件 → 布尔值”的映射,缓存键设计不好,要么查不准,要么击穿数据库。
- 通用键格式建议:
"feature:{$name}:{$scope}:{$value}",例如"feature:new_dashboard:user:12345"表示用户 12345 是否开启新仪表盘 - 全局开关用
"feature:{$name}:global";IP 段用 CIDR 哈希,如"feature:beta:ip:2a0b::/64" - 务必设置过期时间(如 5 分钟),防止规则更新后长期不生效;但别设太短(
- 禁用
cache()->forever()—— 一旦写错没法清理,线上等于埋雷
数据库规则表比硬编码更易协同和审计
当灰度需要运营后台手动开启、设置百分比、指定用户列表时,靠改代码或缓存键就不可持续。此时需要一张轻量规则表。
- 表结构只需
feature_name、scope(user / ip / percent / header)、value(12345 / 2a0b::/64 / 5 / X-Env:staging)、enabled(tinyint) - 查询时用
where feature_name = ? and scope = ? and value like ?,配合索引,单次查询控制在 2ms 内 - 避免在
boot()或__construct()里预加载全部规则——按需查,查完立刻缓存结果 - 上线前务必给该表加
UNIQUE KEY (feature_name, scope, value),否则同名规则重复插入会导致逻辑错乱
灰度最麻烦的从来不是怎么开,而是怎么关得干净——比如某个开关影响了队列任务、WebSocket 连接或缓存 key 命名空间,这些地方容易漏掉判断,上线前得挨个 grep new_search 这类关键词确认覆盖全了。











