/ 团队介绍 /
我们是光大科技智能云计算部智能化平台团队Devops项目组,专注于金融传统行业研发效能的提升和快速交付。通过自动化技术的应用,以及敏捷协同管理工具链的研发,助力金融传统行业Devops落地。团队拥有Devops领域经验丰富的研发工程师和专家,将不定期分享敏捷领域的原创文章,共同探索Devops落地最佳实践。
1
什么是特性开关
在开始之前,输入黄金秘籍:show me the code!
if (fetureToggle.isEnabled(\'AwesomeFeature\')) {
// 这里放新功能代码
} else {
// 这里是老代码
}
眼熟吗?这就是代码中的特性开关,它是一种历史悠久的软件开发技术,用来远程启用或禁用功能,而无需重新部署代码。
使用特性开关能够让团队持续在主干上工作,避免合并代码分支给团队带来的痛苦,同时防止没有完成开发的功能影响其他成员或破坏代码稳定性。因此,使用特性开关的团队可以持续将没有开发完成的代码部署到生产环境,也就是说特性开关能够将功能发布与代码部署分离。
这样做让你能够有效管理一项功能的完整生命周期:减少代码部署的回滚次数、渐进向用户滚动推出新功能、最小化功能发布风险、检验用户接受程度。正因如此,特性开关的用户并不只有工程师们,比如:
-
销售和客服人员用来给客户发津贴。
-
产品人员用来管理beta版程序。
-
市场人员开展A/B测试。
接下来,我们将从更大的视角去看看特性开关的典型使用场景。
2
特性开关的典型应用场景
我们将从开关的有效寿命和动态程度两个维度去划分场景。
场景1 ▉
功能发布开关
这些开关让持续交付团队能够进行基于主干的开发。它们允许将正在开发的功能嵌入到公共分支(比如主分支或主干),同时还允许该分支随时部署到生产环境中。功能发布开关允许将不完整且未经测试的代码路径作为永远不会执行的潜在代码部署到生产环境。
产品经理也可以围绕产品版本使用同样的方式,避免完成一半的功能暴露给最终用户。比如,电商网站的产品经理可能不希望用户看到新添加的“物流详情”功能,因为有几家物流公司还没有实现该功能。有的时候,即便功能已经完成了实现和测试,也有一些其他的原因会让产品经理不愿开放功能。比如,功能可能要和市场营销活动进行同步发布。这种特性开关的使用场景是“将【功能】版本与【代码】部署分离”的持续交付原则的最常见形式。
功能发布开关在本质上是过渡性的,通常这些开关的寿命不应超过两周,而功能发布开关的切换通常是相对静态的。
场景2 ▉
实验开关
实验开关用于执行多变量测试或A/B测试。系统中的每个用户都会被分配一条代码路径并始终沿着这条路径执行。通过跟踪不同路径的汇总行为,我们就可以比较不同代码路径带来的业务效果。这项技术通常用来进行业务指标优化,比如电商系统的购买流程或CTA按钮的转化率。
实验开关必须存活足够长的时间,才能够获得有足够统计意义的结果。在不同的流量下,实验开关可能要存活数小时或数周。更长的时间不会带来更好的结果,因为系统的其他变更会让实验数据失效。而实验切换必须是高度动态的,每个传入的网络请求都可能代表不同用户。
场景3 ▉
运维开关
这些开关用于从运维的角度对系统进行控制。比如我们在推出新功能时,由于功能对性能的影响无法评估,所以系统运维人员可以用运维开关按需进行功能禁用或服务降级。
大多数运维开关的寿命较短,因为一旦新功能稳定运行,就可以去掉该开关了。但是,偶尔也会需要一些长期存在的杀手锏开关,当系统承受异常升高的负载时,这些杀手锏开关能够让系统运维人员优雅地对不太重要的系统功能进行服务降级。比如,当负载很大时,我们可能会希望禁用掉首页上的“千人千面推荐”板块,因为生成这个板块对硬件的消耗很大。对于在线零售行业,可能需要在爆款产品发布之前故意禁用掉购买流程中的关键节点。
所以,运维开关中的大部分只是短暂存活,但需要无限期地为运维人员保留一些关键控制,让运维人员能够快速响应生产问题。如果使用快速配置线上环境或重新部署版本,很可能会让运维人员不爽。
场景4 ▉
权限开关
权限开关用于对某些用户获得的功能或产品体验进行定制。例如,我们有一些高级功能,仅针对付费的客户启用;或者我们有些内测(alpha)版本功能仅对内部用户开放,同时还有一些公测(beta)版本对内部用户和beta用户开放。这种方式通常被称为“喝自己的香槟”。香槟早餐在很多方面和金丝雀发布类似,而区别在于:金丝雀发布的功能随机选择客户群体开放,而“香槟早餐”功能只对特定的用户群体开放。
当用来进行高级付费功能管理时,这些功能可能需要存活很久,甚至好几年。由于不同的权限需要根据网络请求判定,因此动态性很强。
3
特性开关为DevOps带来了什么
《加速度:DevOps现状报告(2019)》勾勒了高效能组织的特征。相比之下,高效能团队部署代码的频率是低效能团队的46倍,从代码提交到部署到生产环境比低效能团队快2555倍。它们不仅部署代码更快,失败的频率也更低。高效能团队有更低的失败率,从事故中恢复也更快。其背后的原因就是采用了正确的系统和流程来支持团队。
必须创建CI/CD流水线以支持每天能够进行多次代码部署,因为CI/CD就是要让向最终用户交付软件更快。要想快速前进,必须有安全措施来保证,而特性开关就是这些安全保证之一。借助特性开关,能让你直接关闭在生产环境中运行异常的功能而无需进行代码回退。
特性开关还使团队能开展基于主干的开发。基于主干的开发是高效能团队的另一个重要实践,指所有人提交代码到被称为trunk或main的单一分支上,而不是提交到特性分支或开发分支。单一分支能帮助团队消灭合并冲突和失败的构建,同时,还必须要使用开关来关掉那些不打算公开的功能,从而代替特性分支开发实现隔离半成品功能的目的。
4
从硬编码到特性开关即服务
可以设想这样一个场景:你正在参与数据可视化项目,你的团队负责核心的3D渲染引擎,而你现在接到的需求是提高3D网格线的算法效率。你知道这要对现在的实现方式进行大刀阔斧地修改,需要几个星期才能做好。在此期间,团队的其他成员也要对代码库中的这部分相关的代码进行持续的修改。
你非常想要避免使用分支,因为曾经在合并长时间没有合并的分支时,你经历过那种巨大的痛苦。所以,你让团队全都在主干上修改代码。不过,为了避免在修改这部分代码的过程中影响团队中的其他人或者导致代码库不稳定,你决定使用一个特性开关。
特性开关的诞生 ▉
现在,就像上面故事情节所介绍的,我们有这样一个网格线计算函数:
function reticulateSplines() {
// 当前的实现代码
}
我们现在要引入一些修改:
function reticulateSplines() {
let useNewAlgorithm = false
// useNewAlgorithm = true // 要用新算法,请将本行注释取消
if (useNewAlgorithm) {
return enhancedSplineReticulation()
} else {
return oldFashionedSplineReticulation()
}
function oldFashionedSplineReticulation() {
// 当前的实现代码
}
function enhancedSplineReticulation() {
// TODO: 实现更好的算法
}
}
我们将当前的算法实现移动到了一个叫做oldFashionedSplineReticulation的函数中,并且将reticulateSplines改成了一个“开关点”。所以当有人想要编辑新算法时,就可以通过取消useNewAlgorithm = true这行的注释就可以启用“使用新算法”这个特性了。
让特性开关更加动态 ▉
几个小时过去了,你的团队已经可以让新算法通过集成测试运行了。他们现在想要老算法也能通过同样的集成测试。他们现在必须能够动态地打开或者关闭这个特性。也就是说,现在必须要搞一搞对useNewAlgorithm = true进行注释或取消注释的这个机制了。
function reticulateSplines() {
if (featureIsEnabled(\'use-new-algorithm\')) {
return enhancedSplineReticulation()
} else {
return oldFashionedSplineReticulation()
}
}
现在,我们引入了featureIsEnabled函数,这是个开关路由,用来动态控制哪一条代码路径被激活。从简单的内存存储到拥有华丽界面的高度复杂的分布式开关系统,实现开关路由的方式多种多样。让我们从最简单的方式开始:
function createToggleRouter(featureConfig) {
return {
setFeature(featureName, isEnabled) {
featureConfig[featureName] = isEnabled
},
featureIsEnabled(featureName) {
return featureConfig[featureName]
}
}
}
这样,我们可以基于默认配置(比如读取配置文件)来新建一个开关路由,而且还能动态地打开或关闭这个开关。这样,自动化测试就可以同时检查这两种情况了。
describe(\'计算网格线算法\', function () {
let toggleRouter, simulationEngine
beforeEach(function () {
toggleRouter = createToggleRouter()
simulationEngine = createSimulationEngine({ toggleRouter })
})
it(\'用老算法应当正常运行\', function () {
// 假设
toggleRouter.setFeature(\'use-new-algorithm\', false)
// 当
const result = simulationEngine.doSomethingWhichInvolvesSplineReticulation()
// 那么
verifySpineReticulation(result)
})
it(\'用新算法应当正常运行\', function () {
// 假设
toggleRouter.setFeature(\'use-new-algorithm\', true)
// 当
const result = simulationEngine.doSomethingWhichInvolvesSplineReticulation()
// 那么
verifySpineReticulation(result)
})
})
特性开关即服务 ▉
特性开关即服务(Feature Toggle as a Service)是指用复杂的分布式系统实现的特性路由平台,用户可以通过图形化界面对所有特性开关、开关启停、激活策略、应用服务和数据度量进行动态管理的一套SaaS自服务平台,能够极大方便企业对开发、测试、生产环境中所运行的应用服务进行控制,有效落地“将【代码部署】和【功能发布】分离”的原则。
云敏缓释平台的架构组件
特性开关即服务平台通常由以下几部分组成:
-
用户界面:提供开关、策略、应用、数据统计的管理界面。
-
服务API:一套用于询问开关激活策略、推送统计数据的高性能、高可用接口。
-
客户端:一套支持多种开发语言的SDK,为业务应用提供用来查询是否开启指定的开关的接口。
既然特性开关让没有经过测试的代码部署到生产环境中,那么如何才能保障符合商业银行内部的安全、监管、审计、合规性的要求呢?从本质上说,我们可以基于特性开关即服务平台的特点,将用代码仓库隔离的方式融合进来。
可以看到,传统的成熟度分支模式很难做到持续部署。究其原因,是由下面几点客观事实所决定的:
-
所有的功能都是由代码实现的;
-
已经实现的功能不一定要发布上线;
-
新功能的开发是持续进行的;
-
实现新功能需要对修改已有功能的代码(无论已有功能发布与否);
-
代码越迟合并,合并差异越大,合并难度越大、冲突越多、合并后产生的缺陷越多;
-
代码合并变动后涉及到的功能都要测试,合并差异越大,要测试的工作越多,时间越长。
在这样的客观情况下,如果将代码的部署与功能的发布捆绑,就会导致不上线的功能的代码得不到集成,最终导致质量与效率低下。因此,为了合规性将代码部署与功能发布绑定的方式并不可取。对此,主干开发+特性开关即服务的方式给出了破局的钥匙。
可以看到,主干开发帮团队实现了代码的持续部署,而特性开关即服务帮团队实现了合规的功能发布。同时,由于采用了特性开关,团队更有勇气采用主干开发。由于缩短代码部署上线的周期,也就避免恶性代码合并给团队带来的痛苦,避免团队遭受代码低效率集成和低质量功能发布给团队带来的双重打击。
写在最后
特性开关是确保团队真正能够实现主干开发的关键,并能够切实有效地降低功能发布风险。然而,团队也需要注意积极主动地删除掉不再需要的开关,减少开关库存,才能确保开关的维护成本尽可能低。比如,有些团队的规则是:每个特性开关被添加时,都要设定一个开关“到期时间”。如果功能标记在到期之后仍然存在,就无法通过自动化测试,甚至无法启动程序。最后,祝大家玩特性开关愉快!
金融智变,云管未来-智能多云管理平台(AICMP)
HTTPS为何封神,密码学是如何将其推向神座
Nginx常见配置总结
一个Java对象究竟占用多大内存?-Java性能优化基础
作者:李淳
原创文章,作者:EBCloud,如若转载,请注明出处:https://www.sudun.com/ask/33823.html