3. 灰度策略设计:基于用户ID、基于IP、基于地域、基于用户属性(VIP/白名单)

灰度策略怎么选?说白了,就是决定「让谁先尝鲜」。

我见过不少团队,一上来就搞随机灰度,结果线上出了事故,连谁受影响都查不到。嗯,这里要敲黑板——灰度策略的核心不是技术实现,而是业务可控性。

3.1 基于用户ID:最常用的精准控制

这是我最推荐的方式。用户ID是天然的唯一标识,不会变,也不会重复。

具体怎么做?取用户ID的哈希值,对100取模。比如哈希值模100后落在0-4的,就是5%的灰度用户。

// 伪代码示例
function isInGray(userId, grayPercent) {
    // 用MD5做哈希,分布更均匀
    let hash = md5(userId.toString());
    let mod = parseInt(hash.substring(0, 8), 16) % 100;
    return mod < grayPercent;
}
我的经验:千万别直接用用户ID的数字取模。用户ID往往是自增的,前100个用户全进灰度,后面全不进,这不科学。一定要先做哈希。

我在项目中遇到过一个问题:用户反馈说「为什么我昨天在灰度里,今天不在了?」。原因很简单——灰度比例调整了。所以,如果你希望用户「一旦进入灰度就固定下来」,可以用用户ID + 功能标识做组合哈希。

3.2 基于IP:适合网络层面的灰度

基于IP的灰度,说白了就是按「来源」控制。比如只让公司内网IP访问新版本,或者只让某个城市的IP先体验。

但这里有个坑——NAT。公司几百号人可能共享一个公网IP。你开了10%的灰度,结果要么全进,要么全不进。

// IP灰度示例:取IP最后一段
function isInGrayByIp(clientIp, grayPercent) {
    let segments = clientIp.split('.');
    let lastSegment = parseInt(segments[3]);
    return lastSegment % 100 < grayPercent;
}
我曾经踩过的坑:用IP做灰度,结果把整个办公楼的人都拉进了灰度。因为他们的出口IP是一样的。后来我加了一层「IP段匹配」,比如只灰度 10.0.1.x 这个C段。

基于IP还有一个好处——可以配合CDN或网关做灰度。不需要改代码,直接在Nginx层配规则就行。

3.3 基于地域:适合区域性发布

这个策略很直观。比如新功能先在上海上线,没问题再推到北京、深圳。

实现方式有两种:

  • IP地理库:根据用户IP查归属地。我建议用MaxMind的GeoIP库,准确率还行。
  • 用户资料中的地域字段:如果用户注册时填了城市,直接用这个字段更准。

我个人习惯用第二种。为什么?因为IP地理库有时候会把「北京联通」的用户定位到「天津」,这偏差就大了。

// 地域灰度示例
const GRAY_CITIES = ['上海', '杭州', '深圳'];

function isInGrayByRegion(user) {
    // 优先用用户资料中的城市
    if (user.city && GRAY_CITIES.includes(user.city)) {
        return true;
    }
    // 降级:用IP查
    let ipCity = geoIpLookup(user.ip);
    return GRAY_CITIES.includes(ipCity);
}
注意:地域灰度要配合「回退机制」。万一上海出问题了,你得能快速关掉上海地区的灰度,而不是等用户投诉。

3.4 基于用户属性:VIP/白名单

这是最「政治正确」的灰度策略。VIP用户先体验,白名单用户先验证。

你想想看,如果新功能有bug,VIP用户遇到了,那后果很严重。所以VIP灰度要谨慎——我一般只给「VIP中的VIP」开灰度,比如年消费10万以上的用户。

白名单就简单多了。内部员工、测试账号、核心合作伙伴,直接写死在配置中心里。

// 用户属性灰度示例
const VIP_THRESHOLD = 100000; // 年消费10万
const WHITE_LIST = ['user_001', 'user_002', 'test_admin'];

function isInGrayByAttribute(user) {
    // 白名单优先
    if (WHITE_LIST.includes(user.id)) {
        return true;
    }
    // VIP用户:按等级灰度
    if (user.vipLevel >= 5 && user.annualSpend > VIP_THRESHOLD) {
        return true;
    }
    return false;
}
我的建议:白名单一定要支持热加载。我曾经有一次把测试账号写死在代码里,结果上线后发现测试账号不在白名单里——因为代码没部署。后来我改用配置中心(比如Apollo或Nacos),改配置即时生效。

3.5 四种策略的对比与选择

策略 优点 缺点 适用场景
基于用户ID 精准、稳定、可追溯 需要用户登录态 大部分业务场景
基于IP 无需登录、网关层实现 NAT问题、不够精准 网络层灰度、内测
基于地域 区域性控制、风险隔离 依赖地理库准确度 区域性发布、合规要求
基于用户属性 业务价值高、风险可控 规则复杂、维护成本高 VIP体验、内部测试

实际项目中,我很少只用一种策略。通常是组合使用:

  • 先基于用户属性,把白名单和VIP放进去
  • 再基于地域,控制某个城市先上线
  • 最后基于用户ID,按比例逐步放开
核心原则:灰度策略不是「选一个」,而是「组合拳」。你想想看,如果只基于IP灰度,那VIP用户可能永远进不了灰度。所以,我一般把用户属性作为「最高优先级」,用户ID作为「兜底策略」。

嗯,灰度策略设计这块,说白了就是「谁先上、谁后上、谁不上」。选对策略,能让你在出问题时快速定位、快速回滚。选错了,那就是给自己挖坑。