正在加载,请稍候…

特性标志与渐进式交付:LaunchDarkly 和 Unleash

使用特性标志实现安全部署。了解金丝雀发布、A/B 测试、熔断开关,以及如何使用 LaunchDarkly 或开源 Unleash 实现特性标志。

特性标志与渐进式交付:LaunchDarkly 和 Unleash

特性标志与渐进式交付

为什么使用特性标志?

无标志:部署 -> 所有用户立即获得新功能
有标志:部署 -> 按用户/分段控制发布

使用场景:
- 金丝雀发布(0% -> 1% -> 10% -> 50% -> 100%)
- A/B 测试(在全面发布前衡量影响)
- 熔断开关(无需重新部署即可禁用有问题的功能)
- 针对特定用户/公司的 Beta 功能
- 暗启动(发布代码,稍后启用)

特性标志与渐进式交付:LaunchDarkly 和 Unleash 插图

简单实现

interface FeatureFlag {
  enabled: boolean;
  percentage?: number;
  userIds?: string[];
  segments?: string[];
}

class FeatureFlagService {
  private flags = new Map<string, FeatureFlag>();

  constructor(private redis: RedisClient) {}

  async isEnabled(flagKey: string, user: { id: string; segment?: string }): Promise<boolean> {
    const flag = await this.getFlag(flagKey);
    if (!flag) return false;
    if (!flag.enabled) return false;

    // 特定用户覆盖
    if (flag.userIds?.includes(user.id)) return true;

    // 基于分段
    if (flag.segments && user.segment && flag.segments.includes(user.segment)) return true;

    // 百分比发布(每个用户一致)
    if (flag.percentage !== undefined) {
      const hash = parseInt(md5(`${flagKey}:${user.id}`).substring(0, 8), 16);
      return (hash % 100) < flag.percentage;
    }

    return flag.enabled;
  }

  private async getFlag(key: string): Promise<FeatureFlag | null> {
    const cached = await this.redis.get(`flag:${key}`);
    if (cached) return JSON.parse(cached);

    const flag = await this.db.findFlag(key);
    if (flag) await this.redis.setEx(`flag:${key}`, 30, JSON.stringify(flag));
    return flag;
  }
}

// 在代码中使用
const featureFlags = new FeatureFlagService(redis);

app.get('/checkout', async (req, res) => {
  const newCheckout = await featureFlags.isEnabled('new-checkout-flow', req.user);

  if (newCheckout) {
    return newCheckoutController.handle(req, res);
  }
  return legacyCheckoutController.handle(req, res);
});

特性标志与渐进式交付:LaunchDarkly 和 Unleash 插图

OpenFeature 标准

// OpenFeature - 供应商中立的特性标志标准
import { OpenFeature } from '@openfeature/server-sdk';
import { LaunchDarklyProvider } from '@openfeature/launchdarkly-provider';

await OpenFeature.setProvider(new LaunchDarklyProvider(process.env.LD_SDK_KEY!));
const client = OpenFeature.getClient('my-app');

// 评估上下文
const context = { targetingKey: user.id, email: user.email, plan: user.plan };

// 布尔标志
const newDashboard = await client.getBooleanValue('new-dashboard', false, context);

// 字符串标志(多变量)
const checkoutVariant = await client.getStringValue('checkout-variant', 'control', context);

// 数字标志
const maxItems = await client.getNumberValue('cart-max-items', 10, context);

特性标志与渐进式交付:LaunchDarkly 和 Unleash 插图

金丝雀发布流程

// 渐进式发布
async function progressiveRollout(flagKey: string): Promise<void> {
  const stages = [1, 5, 10, 25, 50, 100];

  for (const percentage of stages) {
    await flagService.update(flagKey, { percentage });
    console.log(`已将 ${flagKey} 发布到 ${percentage}%`);

    // 监控 10 分钟
    await waitAndMonitor(10 * 60 * 1000, {
      errorRateThreshold: 0.01,    // 1% 错误率
      latencyP99Threshold: 500,    // 500ms P99
      onThresholdExceeded: async () => {
        await flagService.update(flagKey, { percentage: 0, enabled: false });
        throw new Error(`在 ${percentage}% 时触发回滚`);
      }
    });
  }
}

特性标志将部署与发布解耦——随时发布代码,安全地发布功能。