乐趣区

关于部署:万字长文-解读功能开关-IDCF

性能开关 Feature Toggle(通常也称为性能标记 Feature Flag)是一种弱小的技术,容许团队在不更改代码的状况下批改零碎行为。包含各种应用类别,在施行和治理开关时思考该形式十分重要。开关引入了复杂性。咱们能够通过应用智能开关实现实际和适当的工具来治理咱们的开关配置来管制这种复杂性,但咱们还应该致力于限度零碎中开关的数量。

作者 Pete Hodgson 是旧金山湾区的一名独立软件交付参谋。他善于帮忙初创工程团队改良他们的工程实际和技术架构。Pete 之前曾在 Thoughtworks 负责过六年的参谋,领导其西海岸业务的技术实际。他还曾在旧金山多家初创公司负责技术主管。

内容

  • 一个开关的故事
  • 开关的类别
  • 治理不同类别的开关
  • 施行技术
  • 开关配置
  • 应用带有特色开关的零碎

“性能开关”是一组模式,能够帮忙团队疾速但平安地向用户提供新性能。在这篇对于性能开关的文章中,咱们将从一个简短的故事开始,展现性能开关有用的一些典型场景。而后咱们将深入研究细节,涵盖有助于团队通过性能开关取得成功的特定模式和实际。

性能开关也称为性能标记、性能位或性能翻转器。这些都是同一组技术的同义词。在本文中,我将交替应用性能开关和性能标记。

一、一个开关的故事

场景描述:你隶属于从事简单城市规划模拟游戏的多个团队之一,你的团队负责外围模仿引擎,你的工作是进步网络渲染算法的效率。你晓得这将须要对施行进行相当大的检修,这将须要数周工夫。同时,你团队的其余成员将须要在代码库的相干畛域持续一些正在进行的工作。

依据过来合并长期存在的分支的苦楚经验,如果可能的话,你心愿防止为这项工作打分支。相同,你决定整个团队将持续在骨干上工作,但致力于网络渲染算法改良的开发人员将应用性能开关来避免他们的工作影响团队的其余成员或毁坏代码库的稳定性。

1.1 性能标记的诞生

以下是钻研算法的两人引入的第一个变动:

退出开关之前

  function reticulateSplines () {// 以后的实现在这里}

这些示例都应用 JavaScript ES2015

退出开关之后

  function reticulateSplines(){
    var useNewAlgorithm = false;
    // useNewAlgorithm = true; // UNCOMMENT IF YOU ARE WORKING ON THE NEW SR ALGORITHM
  
    if(useNewAlgorithm){return enhancedSplineReticulation();
    }else{return oldFashionedSplineReticulation();
    }
  }
  
  function oldFashionedSplineReticulation(){// current implementation lives here}
  
  function enhancedSplineReticulation(){// TODO: implement better SR algorithm}
  
  function oldFashionedSplineReticulation () {// 以后的实现在这里}
  
  function enhancedSplineReticulation () {// TODO:实现更好的 SR 算法}

这对曾经将以后的算法实现挪动到一个 oldFashionedSplineReticulation 函数中,并将 reticulateSplines设置为一个开关点。当初,如果有人正在钻研新算法,他们能够通过勾销正文来启用“应用新算法”性能useNewAlgorithm = true

1.2 使标记动态化

几个小时过来了,这对搭档筹备好通过一些模仿引擎的集成测试来运行他们的新算法。他们还想在同一集成测试运行中应用旧算法。他们须要可能动静地启用或禁用该性能,这意味着是时候解脱正文或勾销正文该useNewAlgorithm = true 行的蠢笨机制了:

function reticulateSplines () {if ( featureIsEnabled( "use-new-SR-algorithm") ){return enhancedSplineReticulation();
  } else {return oldFashionedSplineReticulation();
  }
}

咱们当初介绍了一个 featureIsEnabled 性能,一个开关路由器,它能够用来动态控制哪个代码门路是流动的。实现开关路由器的办法有很多,从简略的内存存储到具备精美 UI 的高度简单的分布式系统。当初咱们将从一个非常简单的零碎开始:

function createToggleRouter(featureConfig){
  return {setFeature(featureName,isEnabled){featureConfig[featureName] = isEnabled;
    },
    featureIsEnabled(featureName){return featureConfig[featureName];
    }
  };
}

请留神,咱们应用的是 ES2015 的办法简写

咱们能够基于一些默认配置创立一个新的开关路由器——兴许从配置文件中读取——但咱们也能够动静地关上或敞开一个性能。这容许自动化测试来验证开关性能的两侧:

describe('spline reticulation', function(){
  let toggleRouter;
  let simulationEngine;

  beforeEach(function(){toggleRouter = createToggleRouter();
    simulationEngine = createSimulationEngine({toggleRouter:toggleRouter});
  });

  it('works correctly with old algorithm', function(){
    // Given
    toggleRouter.setFeature("use-new-SR-algorithm",false);

    // When
    const result = simulationEngine.doSomethingWhichInvolvesSplineReticulation();

    // Then
    verifySplineReticulation(result);
  });

  it('works correctly with new algorithm', function(){
    // Given
    toggleRouter.setFeature("use-new-SR-algorithm",true);

    // When
    const result = simulationEngine.doSomethingWhichInvolvesSplineReticulation();

    // Then
    verifySplineReticulation(result);
  });
});

1.3 筹备公布

更多的工夫过来了,团队置信他们的新算法功能齐全。为了确认这一点,他们始终在批改他们的更高级别的自动化测试,以便他们在性能敞开和关上的状况下运行零碎。该团队还心愿进行一些手动探索性测试,以确保所有按预期工作——毕竟,样条网状结构是零碎行为的要害局部。

要对尚未被验证为可用于个别用处的性能执行手动测试,咱们须要可能为生产中的个别用户群敞开该性能,但可能为外部用户关上它。有很多不同的办法能够实现这个指标:

  • 让开关路由器依据开关设置做出决策,并针对特定环境进行配置。仅在预生产环境中启用新性能。
  • 容许通过某种模式的治理 UI 在运行时批改开关配置。应用该治理 UI 在测试环境中启用新性能。
  • 教开关路由器如何依据申请做出动静的开关决策。这些决策将开关上下文思考在内,例如通过查找非凡的 cookie 或 HTTP 标头。通常开关上下文用作辨认发出请求的用户的代理。

(稍后咱们将更具体地钻研这些办法,所以如果其中一些概念对你来说是新的,请不要放心。)

团队决定应用按申请开关路由器,因为它为他们提供了很大的灵活性。该团队特地感谢,这将使他们可能在不须要独自的测试环境的状况下测试他们的新算法。相同,他们能够在生产环境中简略地关上算法,但仅限于外部用户(通过非凡 cookie 检测到)。团队当初能够本人关上该 cookie 并验证新性能是否按预期执行。

1.4 金丝雀公布

基于迄今为止所做的探索性测试,新的网络渲染算法看起来不错。然而,因为它是游戏模仿引擎的重要组成部分,因而依然有些人不违心为所有用户启用此性能。团队决定应用他们的性能开关基础设施来执行金丝雀公布,只为他们总用户群的一小部分——“金丝雀”群组开启新性能。

该团队通过向开关路由器传授用户群组的概念来加强开关路由器 – 用户群组始终体验始终开启或敞开的性能。一组金丝雀用户是通过 1% 的用户群的随机抽样创立的——可能应用用户 ID 的模数。这个金丝雀队列将始终启用该性能,而其余 99% 的用户群仍应用旧算法。监控两组的要害业务指标(用户参与度、总收入等),以确保新算法不会对用户行为产生负面影响。一旦团队确信新性能没有不良影响,他们就会批改他们的开关配置,以便为整个用户群启用它。

1.5 A/B 测试

团队的产品经理理解到这种办法,十分兴奋。她倡议团队应用相似的机制来执行一些 A/B 测试。对于批改犯罪率算法以思考净化程度是否会减少或升高游戏的可玩性,始终存在长期争执。他们当初有能力应用数据来解决争执。他们打算推出一个便宜的实现,它能够捕捉到这个想法的实质,并通过一个性能标记来管制。他们将为相当多的用户群启用该性能,而后钻研这些用户与“管制”群相比的行为形式。这种办法将容许团队依据数据解决有争议的产品答辩,

这个简短的场景旨在阐明性能开关的基本概念,同时也强调该外围性能能够有多少不同的应用程序。当初咱们曾经看到了这些应用程序的一些示例,让咱们更深刻地钻研一下。咱们将摸索不同类别的开关,看看是什么让它们不同凡响。咱们将介绍如何编写可保护的开关代码,最初分享实际以防止性能开关零碎的一些陷阱。

二、开关的类别

咱们曾经看到了性能开关提供的基本功能 – 可能在一个可部署单元中提供代替代码门路并在运行时在它们之间进行抉择。上述场景还表明,该工具能够在各种状况下以各种形式应用。将所有性能开关集中到同一个桶中可能很迷人,但这是一条危险的路线。不同类别开关的设计驱动是齐全不同的,并且以雷同的形式治理它们可能会导致苦楚。

性能开关能够分为两个次要维度:性能开关将存在多长时间,以及开关决策必须有多动静。还有其余因素须要思考——例如,谁来治理性能开关——但我认为生存周期和动态性是两个重要因素,能够帮忙领导如何治理开关。

让咱们通过这两个维度的视角思考各种类别的开关,看看它们适宜什么。

2.1 公布开关

公布开关容许将不残缺和未经测试的代码门路作为可能永远不会关上的潜在代码交付到生产环境。

这些是用于为实际继续交付的团队启用基于骨干的开发的性能标记。它们容许将正在进行的性能检入到共享集成分支(例如 master 或 trunk)中,同时依然容许随时将该分支部署到生产中。公布开关容许将不残缺和未经测试的代码门路作为可能永远不会关上的潜在代码交付到生产环境。

产品经理也能够应用同样办法的以产品为核心的版本来避免半成品的产品性能裸露给最终用户。例如,电子商务网站的产品经理可能不想让用户看到仅实用于网站的一物流合作伙伴的新预计物流日期性能,而是心愿等到该性能已为所有物流合作伙伴施行。产品经理可能有其余起因不想公开性能,即便它们曾经齐全实现和测试。例如,性能公布可能与营销流动相协调。以这种形式应用公布开关是实现“将 [性能] 公布与 [代码] 部署离开”的继续交付准则的最常见形式。

公布开关实质上是过渡的。只管以产品为核心的开关可能须要保留更长的工夫,但它们通常不应停留超过一两周。公布开关的开关决定通常是十分动态的。给定公布版本的每个开关决策都是雷同的,通过推出具备开关配置更改的新版原本更改开关决策通常是齐全能够承受的。

2.2 试验开关

试验开关用于执行多变量或 A/B 测试。零碎的每个用户都被搁置在一个群组中,并且在运行时,开关路由器将依据他们所在的群组始终如一地将给定的用户发送到一个或另一个代码门路。通过跟踪不同群组的聚合行为,咱们能够比拟成果不同的代码门路。这种技术通常用于对电子商务系统的购买流程或按钮上的宣传性用语等事物进行数据驱动的优化。

试验开关须要在雷同配置下保持足够长的工夫以产生具备统计意义的后果。取决于可能意味着生命周期数小时或数周的流量模式。更长的工夫不太可能有用,因为对系统的其余更改可能会使试验后果有效。就其性质而言,试验开关是高度动静的 – 每个传入申请都可能代表不同的用户,因而可能会以不同于上一个的形式路由。

2.3 运维开关

这些标记用于管制咱们零碎行为的操作方面。咱们可能会在推出具备不确定性能影响的新性能时引入运维开关,以便零碎操作员能够在须要时在生产中疾速禁用或降级该性能。

大多数运维开关的寿命都绝对较短——一旦对新性能的操作方面取得信念,就应该停用该标记。然而,零碎领有大量长寿命的“终止开关”并不少见,它们容许生产环境的操作员在零碎接受异样高负载时优雅地升高非重要零碎性能。例如,当咱们处于沉重的负载下时,咱们可能心愿禁用咱们主页上的举荐面板,该面板的生成老本绝对较高。我征询了一家保护运维开关的在线零售商,这可能会在高需要产品公布之前成心禁用其网站次要洽购流程中的许多非关键性能。这种长期存在的运维开关能够视作是一种手工治理的断路器。

如前所述,这些标记中的许多只存在很短的工夫,但一些要害控件可能简直无限期地保留给操作员。因为这些标记的目标是让操作员对生产问题做出快速反应,因而他们须要十分疾速地重新配置 – 须要推出新版本以翻转运维开关 不太可能让操作人员称心。

2.4 许可开关

这些标记用于更改某些用户收到的性能或产品体验。例如,咱们可能有一组“高级”性能,只为付费客户启用。或者,兴许咱们有一组仅供外部用户应用的“alpha”性能和另一组仅供外部用户和 beta 用户应用的“beta”性能。我将这种为一组外部或测试版用户关上新性能的技术称为香槟早午餐 – 一个“喝本人的香槟”的晚期机会。

香槟早午餐在很多方面与金丝雀公布类似。两者之间的区别在于,金丝雀公布的性能面向随机抉择的一组用户,而香槟早午餐性能面向一组特定用户。

当用作治理仅向高级用户公开的性能时,与其余类别的性能开关相比,许可开关的寿命可能十分长 – 以多年为规模。因为权限是特定于用户的,因而许可开关的开关决定将始终按申请进行,因而这是一个十分动静的开关。

三、治理不同类别的开关

当初咱们有了一个开关分类计划,咱们能够探讨动态性和寿命这两个维度如何影响咱们应用不同类别的特色标记的形式。

3.1 动态与动静开关

做出运行时路由决策的开关必然须要更简单的开关路由器,以及这些路由器的更简单配置。

对于简略的动态路由决策,开关配置能够是每个性能的简略开或关,开关路由器仅负责将动态开 / 关状态中继到开关点。正如咱们之前所探讨的,其余类别的开关更加动静并且须要更简单的开关路由器。例如,试验开关的路由器为给定用户动静地做出路由决策,可能应用某种基于该用户 id 的统一群组算法。这个开关路由器不须要从配置中读取动态开关状态,而是须要读取某种队列配置,定义诸如试验队列和管制队列应该有多大。

稍后咱们将深入探讨治理此开关配置的不同办法。

3.2 长期开关与瞬态开关

咱们还能够将开关类别划分为实质上是短暂的类别与长期存在且可能存在多年的类别。这种区别应该对咱们实现性能的开关点的办法有很大的影响。如果咱们要增加一个将在几天后删除的公布开关,那么咱们可能会应用一个开关点,它对 开关路由器进行简略的 if/else 查看。这就是咱们之前应用样条网格示例所做的:

function reticulateSplines () {if ( featureIsEnabled( "use-new-SR-algorithm") ){return enhancedSplineReticulation();
  } else {return oldFashionedSplineReticulation();
  }
}

然而,如果咱们要创立一个带有开关点的新许可开关,咱们心愿它会继续很长时间,那么咱们当然不心愿通过不加选择地分布 if/else 查看来实现这些开关点。咱们须要应用更易于保护的实现技术。

四、施行技术

性能标记仿佛会产生相当凌乱的开关点代码,并且这些开关点也有在整个代码库中扩散的趋势。放弃这种趋势查看代码库中的任何性能标记很重要,如果标记将长期存在,这一点至关重要。有一些实现模式和实际有助于缩小这个问题。

4.1 将决策点与决策逻辑解耦

性能开关的一个常见谬误是将做出开关决策的地位(开关点)与决策背地的逻辑(开关路由器)联合起来。让咱们看一个例子。咱们正在开发下一代电子商务系统。咱们的一项新性能将容许用户通过单击订单确认电子邮件(又称发票电子邮件)中的链接轻松勾销订单。咱们正在应用性能标记来治理咱们所有下一代性能的推出。咱们最后的特色标记实现如下所示:

invoiceEmailer.js
  const features = fetchFeatureTogglesFromSomewhere();

  function generateInvoiceEmail(){const baseEmail = buildEmailForInvoice(this.invoice);
    if(features.isEnabled("next-gen-ecomm") ){return addOrderCancellationContentToEmail(baseEmail);
    }else{return baseEmail;}
  }

在生成发票电子邮件时,咱们的 InvoiceEmailler 会查看该 next-gen-ecomm 性能是否已启用。如果是,那么电子邮件发送者会在电子邮件中增加一些额定的订单勾销内容。

尽管这看起来是一种正当的办法,但它十分软弱。对于是否在咱们的发票电子邮件中蕴含订单勾销性能的决定间接与 next-gen-ecomm 性能绑定 – 应用魔术字符串。为什么发票电子邮件代码须要晓得订单勾销内容是下一代功能集的一部分?如果咱们想在不裸露订单勾销的状况下关上下一代性能的某些局部会怎么?或反之亦然?如果咱们决定只向某些用户推出订单勾销性能怎么办?随着性能的开发,这种“开关范畴”更改很常见。还要记住,这些开关点往往会在整个代码库中激增。

令人高兴的是,软件中的任何问题都能够通过增加一个间接层来解决。咱们能够将开关决策点与该决策背地的逻辑解耦,如下所示:

featureDecisions.js
  function createFeatureDecisions(features){
    return {includeOrderCancellationInEmail(){return features.isEnabled("next-gen-ecomm");
      }
      // ... additional decision functions also live here ...
    };
  }
invoiceEmailer.js
  const features = fetchFeatureTogglesFromSomewhere();
  const featureDecisions = createFeatureDecisions(features);

  function generateInvoiceEmail(){const baseEmail = buildEmailForInvoice(this.invoice);
    if(featureDecisions.includeOrderCancellationInEmail() ){return addOrderCancellationContentToEmail(baseEmail);
    }else{return baseEmail;}
  }

咱们引入了一个 FeatureDecisions 对象,它充当任何性能开关决策逻辑的收集点。咱们为代码中的每个特定开关决策在此对象上创立一个决策办法 – 在这种状况下,“咱们是否应该在发票电子邮件中蕴含订单勾销性能”由 includeOrderCancellationInEmail决策办法示意。

当初,决策“逻辑”是微不足道的通过传递 next-gen-ecomm 查看状态,但当初随着逻辑的倒退,咱们有一个对立的中央来治理它。每当咱们想批改特定开关决策的逻辑时,咱们只须要去一个中央。咱们可能想要批改决策的范畴——例如哪个特定的性能标记管制决策。或者,咱们可能须要批改做出决定的起因——从由动态开关配置驱动到由 A/B 试验驱动,或者由操作问题驱动,例如咱们的一些订单勾销基础设施的中断。在所有状况下,咱们的发票电子邮件发送者都可能会很快乐地不晓得如何或为什么做出该开关决定。

4.2 反转决定

在后面的示例中,咱们的发票电子邮件发送器负责询问性能标记基础设施应该如何执行。这意味着咱们的发票电子邮件发送器有一个须要留神的额定概念 – 性能标记 – 以及与之耦合的额定模块。这使得发票电子邮件更难独自应用和思考,包含使其更难测试。随着性能标记在零碎中变得越来越广泛,咱们将看到越来越多的模块作为全局依赖项与性能标记零碎耦合。不是现实的状况。

在软件设计中,咱们通常能够通过利用管制反转来解决这些耦合问题。在这种状况下的确如此。以下是咱们如何将咱们的发票电子邮件与咱们的性能标记基础设施拆散:

invoiceEmailer.js
  function createInvoiceEmailler(config){
    return {generateInvoiceEmail(){const baseEmail = buildEmailForInvoice(this.invoice);
        if(config.includeOrderCancellationInEmail){return addOrderCancellationContentToEmail(email);
        }else{return baseEmail;}
      },
  
      // ... other invoice emailer methods ...
    };
  }
featureAwareFactory.js
  function createFeatureAwareFactoryBasedOn(featureDecisions){
    return {invoiceEmailler(){
        return createInvoiceEmailler({includeOrderCancellationInEmail: featureDecisions.includeOrderCancellationInEmail()
        });
      },
  
      // ... other factory methods ...
    };
  }

当初,不是咱们 InvoiceEmailler 接触它,而是在构建时通过一个对象 FeatureDecisions将这些决定注入它。当初对性能标记无所不知。它只晓得能够在运行时配置其行为的某些方面。这也使得 testing 的行为更容易 – 咱们能够通过在测试期间传递不同的配置选项来测试它生成带有和不带有订单勾销内容的电子邮件的形式:configInvoiceEmaillerInvoiceEmailler

describe('invoice emailling', function(){it( 'includes order cancellation content when configured to do so', function(){
    // Given 
    const emailler = createInvoiceEmailler({includeOrderCancellationInEmail:true});

    // When
    const email = emailler.generateInvoiceEmail();

    // Then
    verifyEmailContainsOrderCancellationContent(email);
  };

  it('does not includes order cancellation content when configured to not do so', function(){
    // Given 
    const emailler = createInvoiceEmailler({includeOrderCancellationInEmail:false});

    // When
    const email = emailler.generateInvoiceEmail();

    // Then
    verifyEmailDoesNotContainOrderCancellationContent(email);
  };
});

咱们还引入了一个 FeatureAwareFactory 集中创立这些决策注入对象的办法。这是个别依赖注入模式的利用。如果管制反转零碎在咱们的代码库中发挥作用,那么咱们可能会应用该零碎来实现这种办法。

4.3 防止条件

到目前为止,在咱们的示例中,咱们的开关点是应用 if 语句实现的。这对于一个简略的、短暂的开关可能是有意义的。然而,在某个性能须要多个开关点或你心愿开关点长期存在的中央,不倡议应用点条件。一种更易于保护的代替办法是应用某种策略模式来实现代替代码门路:

invoiceEmailler.js
  function createInvoiceEmailler(additionalContentEnhancer){
    return {generateInvoiceEmail(){const baseEmail = buildEmailForInvoice(this.invoice);
        return additionalContentEnhancer(baseEmail);
      },
      // ... other invoice emailer methods ...
  
    };
  }
featureAwareFactory.js
  function identityFn(x){return x;}
  
  function createFeatureAwareFactoryBasedOn(featureDecisions){
    return {invoiceEmailler(){if( featureDecisions.includeOrderCancellationInEmail() ){return createInvoiceEmailler(addOrderCancellationContentToEmail);
        }else{return createInvoiceEmailler(identityFn);
        }
      },
  
      // ... other factory methods ...
    };
  }

在这里,咱们通过容许咱们的发票电子邮件程序配置内容加强性能来利用策略模式。FeatureAwareFactory在创立发票电子邮件时抉择一种策略,由 FeatureDecision. 如果订单勾销应该在电子邮件中,它会传递一个加强性能,将内容增加到电子邮件中。否则,它会传入一个identityFn 增强器——它没有任何成果,只是简略地将电子邮件传回而不做任何批改。

五、开关配置

5.1 动静路由与动静配置

早些时候,咱们将性能开关分为对于给定的代码部署而言开关路由决策基本上是动态的,与那些决策在运行时动态变化的。须要留神的是,标记的决定可能会在运行时以两种形式发生变化,这一点很重要。

  • 首先,像运维开关这样的货色可能会被动静重新配置从 On 到 Off 以响应零碎中断。
  • 其次,某些类别的开关(例如许可开关和试验开关)依据某些申请上下文(例如哪个用户发出请求)为每个申请做出动静路由决策。

前者是通过重新配置动静实现的,而后者则在实质上就是动静的。这些固有的动静开关可能会做出高度动静的决策,但依然具备配置动作,这是相当动态的,兴许只能通过重新部署来扭转。试验开关是此类性能标记的一个示例——咱们实际上并不需要可能在运行时批改试验的参数。事实上,这样做可能会使试验在统计上有效。

5.2 首选动态配置

如果个性标记的性质容许的话,最好通过源代码管制和重新部署来治理开关配置。通过源代码管制治理开关配置给咱们带来的益处,与咱们通过将源代码管制用于基础设施即代码之类的货色所取得的益处雷同。

它能够容许开关配置与正在开关的代码库一起存在,这提供了一个十分大的益处:开关配置将以与代码更改或基础架构更改完全相同的形式在你的继续交付流水线中挪动。这能够充分发挥 CD 的劣势 – 可反复的构建,这些构建以统一的形式跨环境进行验证。它还大大减少了性能标记的测试累赘。很少须要验证版本将如何通过开关敞开和关上来执行,因为该状态已被打包到版本中并且不会更改(至多对于较少动静的标记)。开关配置在源代码管制中并行存在的另一个益处是,咱们能够轻松查看以前版本中开关的状态,并在须要时轻松从新创立以前的版本。

5.3 治理开关配置的办法

尽管动态配置更可取,但在某些状况下,例如运维开关,须要更动静的办法。让咱们看一下用于治理开关配置的一些选项,从简略但动态性较低的办法到一些高度简单但具备许多额定复杂性的办法。

5.4 硬编码开关配置

最根本的技术——兴许根本到不被认为是性能标记——是简略地正文或勾销正文代码块。例如:

function reticulateSplines(){//return oldFashionedSplineReticulation();
  return enhancedSplineReticulation();}

比正文办法略微简单一点的是应用预处理器的 #ifdef 性能,如果可用的话。

因为这种类型的硬编码不容许动静重新配置开关,它仅实用于咱们违心遵循部署代码模式以重新配置标记的性能标记。

5.5 参数化开关配置

硬编码配置提供的构建时配置对于许多用例(包含许多测试场景)来说不够灵便。至多容许在不从新构建应用程序或服务的状况下,重新配置性能标记的简略办法,是通过命令行参数或环境变量指定开关配置。这是一种简略且历史悠久的开关办法,早在有人将该技术称为性能开关或特色标记之前就曾经存在。然而,它有局限性。跨大量过程协调配置可能会变得不不便,而且开关配置的更改须要重新部署或者至多重新启动过程(并且可能须要重新配置开关的人对服务器进行特权拜访)。

5.6 开关配置文件

另一种抉择是从某种结构化文件中读取开关配置。这种开关配置的办法很常见,它作为更通用的应用程序配置文件的一部分开始应用。

应用开关配置文件,你当初能够通过简略地更改该文件而不是从新构建利用程序代码自身来重新配置性能标记。然而,只管在大多数状况下你不须要从新构建应用程序来开关性能,但你可能仍须要执行重新部署以重新配置标记。

5.7 开关利用数据库中的配置

一旦达到肯定规模,应用动态文件来治理开关配置可能会变得很麻烦。通过文件批改配置绝对繁琐。确保一组服务器的一致性成为一项挑战,使更改始终如一地更是如此。作为对此的回应,许多组织将开关配置转移到某种类型的集中式存储中,通常是现有的应用程序数据库。这通常随同着某种模式的治理 UI 的构建,它容许零碎操作员、测试人员和产品经理查看和批改性能标记及其配置。

5.8 分布式开关配置

应用曾经是零碎架构一部分的通用数据库来存储开关配置十分广泛;一旦引入性能标记并开始取得牵引力,这是一个显著的形式。然而,当初有一种非凡用处的分层键值存储更适宜管理应用程序配置 – 像 Zookeeper、etcd 或 Consul 这样的服务。这些服务造成一个分布式集群,它为连贯到集群的所有节点提供环境配置的共享源。能够在须要时动静批改配置,并且集群中的所有节点都会主动收到更改告诉——这是一个十分不便的附加性能。

其中一些零碎(例如 Consul)带有一个治理 UI,它提供了一种治理开关配置的根本办法。然而,在某些时候,通常会创立一个用于治理开关配置的小型自定义应用程序。

5.9 笼罩配置

到目前为止,咱们的探讨假如所有配置都由繁多机制提供。许多零碎的理论状况更为简单,配置的覆盖层来自各种起源。应用开关配置,具备默认配置以及特定于环境的笼罩是很常见的。这些笼罩可能来自像附加配置文件这样简略的货色,也可能来自像 Zookeeper 集群这样简单的货色。

请留神,任何特定于环境的笼罩都与继续交付的现实南辕北辙,即在交付管道中始终领有完全相同的位和配置流。通常实用主义要求应用一些特定于环境的笼罩,然而致力使你的可部署单元和你的配置尽可能与环境无关,这将导致更简略、更平安的管道。当咱们议论测试性能开关零碎时,咱们将很快从新探讨这个主题。

  • 针对每个申请笼罩

环境特定配置笼罩的另一种办法是容许通过非凡 cookie、查问参数或 HTTP 标头在每个申请的根底上笼罩开关的开 / 关状态。与残缺的配置笼罩相比,这有一些劣势。如果服务是负载平衡的,你依然能够确信无论你点击哪个服务实例,都会利用笼罩。你还能够在生产环境中笼罩性能标记而不影响其余用户,并且你不太可能意外地留下笼罩。

这种按申请办法的毛病是它引入了一种危险,即好奇或歹意的最终用户可能会本人批改性能开关状态。一些组织可能对某些未公布的性能可能对足够动摇的一方公开拜访的想法感到不难受。对笼罩配置进行加密签名是缓解这种担心的一种抉择,但无论如何这种办法都会减少性能开关零碎的复杂性和攻击面。

六、应用带有特色标记的零碎

尽管性能开关相对是一种有用的技术,但它也带来了额定的复杂性。在应用带有特色标记的零碎时,有一些技术能够帮忙简化你的生存。

6.1 公开以后性能开关配置

将构建 / 版本号嵌入到已部署的工件中并在某处公开该元数据始终是一种有用的做法,以便开发人员、测试人员或操作员能够找出在给定环境中运行的特定代码。雷同的想法应该利用于性能标记。任何应用性能标记的零碎都应该为操作员提供某种形式来发现开关配置的以后状态。在面向 HTTP 的 SOA 零碎中,这通常是通过某种元数据 API 端点或端点来实现的。参见例如 Spring Boot 的 Actuator endpoints。

6.2 利用结构化的开关配置文件

通常将根本开关配置存储在某种结构化的、人类可读的文件(通常为 YAML 格局)中,通过源代码管制进行治理,咱们能够从此类文件中取得额定的益处。为每个开关蕴含人类可读的形容十分有用,特地是对于由外围交付团队以外的人治理的开关。在尝试决定是否在生产中断事件期间启用运维开关时,你心愿看到什么:basic-rec-algo,还是“应用简略的举荐算法。这很快并且在后端系统上产生的负载更少,但更少比咱们的规范算法精确。”? 一些团队还抉择在他们的开关配置文件中蕴含额定的元数据,例如创立日期、次要开发人员联系人,甚至是短期开关的到期日期。

6.3 以不同形式治理不同的开关

如前所述,具备不同特色的性能开关有多种类别。应该承受这些差别,并以不同的形式治理不同的开关,即便所有不同的开关都能够应用雷同的技术机器进行管制。

让咱们回顾一下咱们之前的电子商务网站示例,该网站在主页上有一个举荐产品局部。最后,咱们可能会在开发时将该局部放在公布开关前面。而后,咱们可能会将其移至试验开关的背地,以验证它是否有助于增加收入。最初,咱们可能会将它移到运维开关 前面,以便在咱们处于极其负载下时能够将其敞开。如果咱们遵循先前对于从开关点中解耦决策逻辑的倡议,那么开关类别中的这些差别应该对开关点代码没有任何影响。

然而,从性能标记治理的角度来看,这些转换相对应该产生影响。作为从 公布开关过渡到试验开关的一部分,配置开关的形式会发生变化,并且可能会挪动到不同的区域 – 可能会进入治理 UI 而不是源代码治理中的 yaml 文件。产品人员当初可能会治理配置而不是开发人员。同样,从试验开关过渡到运维开关 将意味着开关的配置形式、配置所在的地位以及谁治理配置的另一个变动。

6.4 性能开关引入验证复杂性

应用带有性能标记的零碎,咱们的继续交付过程变得更加简单,尤其是在测试方面。当同一个工件通过 CD 管道时,咱们常常须要测试多个代码门路。为了阐明起因,假如咱们正在交付一个零碎,该零碎能够在启用开关时应用新的优化税收计算算法,或者持续应用咱们现有的算法。在给定的可部署工件正在通过咱们的 CD 管道挪动时,咱们无奈晓得开关是否会在生产中的某个时候关上或敞开 – 毕竟这就是性能标记的全副意义所在。因而,为了验证可能最终在生产中运行的所有代码门路,咱们必须在两种状态:开关开关关上并敞开。

咱们能够看到,通过一个繁多的开关,这引入了至多在咱们的一些测试中加倍的要求。随着多个开关的施展,咱们有可能开关状态的组合爆炸。验证每个状态的行为将是一项艰巨的工作。这可能会导致以测试为重点的人们对性能标记产生一些衰弱的狐疑。

令人高兴的是,状况并没有一些测试人员最后设想的那么蹩脚。尽管带有性能标记的候选版本的确须要应用一些开关配置进行测试,但没有必要测试“每个”可能的组合。大多数性能标记不会互相交互,并且大多数版本不会波及对多个性能标记的配置进行更改。

一个好的约定是在性能标记敞开时启用现有的或旧有的行为,而在它关上时启用新的或将来的行为。

那么,团队应该测试哪些性能开关配置?测试你心愿在生产中失效的开关配置是最重要的,这意味着以后的生产开关配置加上你打算公布的所有开关都已关上。测试回退配置也是明智之举,你打算开释的那些开关也会被敞开。为了防止在将来的版本中呈现任何意外的回归,许多团队还会在所有开关都关上的状况下执行一些测试。请留神,仅当你保持开关语义的约定时,此倡议才有意义,其中在性能敞开时启用现有或旧有行为,而在性能开启时启用新行为或将来行为。

如果你的性能标记零碎不反对运行时配置,那么你可能必须重新启动你正在测试的过程能力触发开关,或者更蹩脚的是,将工件重新部署到测试环境中。这会对验证过程的周期时间产生十分不利的影响,进而影响 CI/CD 提供的所有重要反馈循环。为防止此问题,请思考公开一个端点,该端点容许对性能标记进行动态内存重新配置。当你应用诸如试验开关之类的货色时,这些类型的笼罩变得更加必要,在这种状况下,应用开关的两个门路更加繁琐。

这种动静重新配置特定服务实例的能力是一个十分尖锐的工具。如果使用不当,可能会在共享环境中造成很多苦楚和凌乱。这个工具应该只被自动化测试应用,并且可能作为手动探索性测试和调试的一部分。如果须要在生产环境中应用更通用的开关管制机制,最好应用真正的分布式配置零碎构建,如下面开关配置局部所述。

6.5 在哪里搁置你的开关

  • 在边缘开关

对于须要每个申请上下文的开关类别(试验开关、许可开关),将开关点搁置在零碎的边缘服务中是有意义的——即向最终用户展现性能的公开 Web 应用程序。这是你的用户的集体申请首先进入你的域的中央,因而你的开关路由器有最多的上下文可用于依据用户及其申请做出开关决策。将开关点搁置在零碎边缘的一个附带益处是,它能够将繁琐的条件开关逻辑排除在系统核心之外。在许多状况下,你能够将开关点搁置在你正在出现 HTML 的地位,如以下 Rails 示例所示:

someFile.erb
  <%= if featureDecisions.showRecommendationsSection? %>
    <%= render 'recommendations_section' %>
  <% end %>

当你管制对尚未筹备好公布的面向用户的新性能的拜访时,将开关点搁置在边缘也很有意义。在这种状况下,你能够再次应用简略地显示或暗藏 UI 元素的开关来管制拜访。例如,兴许你正在构建应用 Facebook 登录应用程序的性能,但还没有筹备好将其推广给用户。此性能的实现可能波及架构各个局部的更改,但你能够通过暗藏“应用 Facebook 登录”按钮的 UI 层的简略性能开关来管制性能的公开。
乏味的是,应用其中一些类型的性能标记,大部分未公布的性能自身可能实际上是公开的,但位于用户无奈发现的 url 上。

  • 在外围开关

还有其余类型的较低级别的开关必须搁置在你的架构中更深的地位。这些开关通常在实质上是技术性的,并且管制着某些性能如何在外部实现。一个示例是公布开关,它管制是在第三方 API 前应用新的缓存基础设施,还是间接将申请路由到该 API。在这些状况下,在性能被开关的服务中本地化这些开关决策是惟一理智的抉择。

6.6 治理性能开关的持有老本

性能标记有疾速减少的趋势,特地是在首次引入时。它们有用且创立成本低,因而通常会创立很多。然而,开关的确会带来持有老本。它们要求你在代码中引入新的形象或条件逻辑。它们还引入了显着的测试累赘。骑士资本团体的 4.6 亿美元谬误是一个警示故事,阐明当你没有正确治理性能标记时(除其余外)会呈现什么问题。

精明的团队将他们的性能开关视为带有持有老本的库存,并致力将库存放弃在尽可能低的程度。

精明的团队将其代码库中的性能开关视为带有持有老本的库存,并寻求将库存放弃在尽可能低的程度。为了使性能标记的数量放弃可治理,团队必须被动删除不再须要的性能标记。一些团队的规定是,每当首次引入公布开关时,总是将开关删除工作增加到团队的待办事项中。其余团队将“到期日期”放在他们的开关按钮上。有些人甚至会制作“定时炸弹”,如果性能标记在其到期日期之后依然存在,它将无奈通过测试(甚至回绝启动应用程序!)。

咱们还能够采纳精益办法来缩小库存,对系统在任何时候容许领有的性能标记的数量进行限度。一旦达到该限度,如果有人想要增加新的开关,他们首先须要实现删除现有开关标识的工作。

原文:https://martinfowler.com/arti…

作者:Pete Hodgson

译者:冬哥

玩乐高,学麻利,规模化麻利联合作战沙盘之「乌托邦打算」,2022 年 3 月 5 - 6 日登陆深圳,将“多团队麻利协同”基因内化在研发流程中,为规模化晋升研发效力保驾护航!!🏰⛴公众号回复“乌托邦”可加入

退出移动版