当前位置:Java -> 微服务中的特性标志和金丝雀发布

微服务中的特性标志和金丝雀发布

功能标志通常是使用的结构,并且已经存在一段时间了。但在过去的几年里,事情已经发生了变化,功能标志在交付持续无风险的版本中扮演着重要角色。一般来说,当一个新的功能还没有完全开发好,我们仍然想要从主流分支中进行发布,我们可以隐藏我们的新功能,并在生产中将其切换关闭。另一个用例是当我们想将我们的功能发布给只有小部分用户的情况下,我们为一个段/地理区域设置功能为“开”,并为其他地方设置为“关”。在不进行源代码更改的情况下切换功能的能力给开发人员带来了额外的优势,可以利用生产流量进行冲突特性的实验。让我们深入了解功能标志的更多细节以及Springboot中的示例实现。

当我们引入新的功能标志时需要考虑的事项

  1. 在应用程序中建立一个一致的命名约定,以便其他开发人员和产品团队能够轻松理解功能标志的目的。
  2. 在哪里维护功能标志?
    1. 在应用程序属性文件中:根据环境切换功能。在开发中进行实验,同时在生产中关闭功能。
    2. 在配置服务器或保险库中:假设你在深夜发布后感到疲惫,你的运维团队在凌晨4点打电话给你,告诉你新功能在监控工具中引发了红色警报,这时功能切换就派上用场了。首先,在配置服务器中关闭功能,然后仅重新启动计算POD。
    3. 在数据库或缓存中:从数据库或外部缓存系统如Redis中读取配置或标志值,无需重新部署或重新启动计算,因为值可以动态地定时从源中读取,POD可以得到更新的值而无需重新启动。

您还可以探索为功能标志构建的开源或第三方SDK,其中市场上已经有一些。它们还带来了帮助功能标志的生命周期管理的额外优势。

3.设定影响范围:在引入新功能时,始终从较小的占比开始(这意味着仅有一小部分用户可以体验这个新功能),一旦获得信心,逐渐增加。较小的环境中一切都进行得非常好,但在生产环境中可能会表现不同,因此建议仅向一部分用户投放新功能,比如您自己的开发团队在生产环境中的账户。对引入新功能后的性能变化或用户体验变化等数据点进行监控和收集。将其带回团队评估其价值。进行彻底测试,并在所有数据点都对其有利时逐渐增加占比到100%。 

4. 保持功能标志与业务逻辑层紧密联系。 

5. 尝试在将来保留功能标志的元数据,比如创建者、创建时间、创建目的和到期时间戳。

如何维护功能标志

随着时间的推移,如果未使用的功能标记在其生命周期结束时未被删除,那么维护功能标志可能会成为一场噩梦。为了有效地处理这些混乱,可以在待办事项中加入一些任务,作为代码和配置存储库中发布后清理的提醒。另一种应对这种繁琐工作的方法是,为每个功能标志设置一个存活时间(time to live,TTL),这可以是一个完整发布的粗略估计时间,一旦标志的TTL到期,我们可以记录在elf中并在监控工具中设置警报来通知我们。

让我们看看在Java SpringBoot应用程序中实现功能标志的示例:

假设我们要在电子商务网站中引入一个新的功能,在此功能中,每次用户使用自提送货方式下订单时,订单摘要中会为用户增加5美元的信用。

我们决定以1%的占比推出新版本。在所有客户中选择这1%客户的策略不在本文的范围内。

现在,让我们来看一下Java代码

在开发环境的application.property文件中添加一个新的属性。

app.checkout-summary.delivery-pickup.enabled = true

编写一个名为CheckoutSummary.java的接口

public interface CheckOutSummary{

double calculateOrderTotal(Cart cart);

}


为上述接口提供两种不同的实现。在这里,我们使用装饰器模式而不是修改现有实现类的if和else语句来引入开关,它可以帮助我们在不修改源代码的情况下扩展现有对象的功能。

public class CheckOutSummaryImpl{

double calculateOrderTotal(Cart cart)

{

//standard logic

}

}


public class PickUpCheckOutSummaryImpl{

public final CheckOutSummaryImpl checkoutSummary;

Public PickUpCheckOutSummaryImpl(CheckOutSummary checkoutSummary)

{

this.checkoutSummary = checkoutSummary;

}

double calculateOrderTotal(Cart cart)

{

 var newOrderAmt =  checkoutSummary.calculateOrderTotal() - 5;

return newOrderAmt;

}

}


继续进行配置。我们需要有条件地注入上述实现的bean。

@Configuration

public class SummaryBeans{

@Bean

@ConditionalOnProperty(value="app.checkout-summary.delivery-pickup.enabled", havingValue="false")

public CheckOutSummary checkoutSummary()

{

return  new CheckOutSummaryImpl();

}



@Bean

@ConditionalOnProperty(value="app.checkout-summary.delivery-pickup.enabled", havingValue="true")

public CheckOutSummary checkoutSummaryWithPickUp( CheckOutSummary summary)

{

return  new PickUpCheckOutSummaryImpl(summary);

}

}


除了上面的示例之外,如果开关关闭时不需要任何操作,我们还可以创建无操作实现bean。

有时,我们的功能标志可能需要评估不止一个条件来满足“开”路线。我们可以使用下面的Spring注释来嵌入这样的条件。

@ConditionalOnExpression("${properties.first.property.enable:true} && ${properties.second.property.enable:false}")

相较于简单的if和else块,编写实现到接口和有条件地创建bean看起来更加优雅和成熟。

总之,当小心使用时,功能标志是一个很好的工具。通过删除未使用的功能标志,消除繁琐工作,并保持干净的代码。

推荐阅读: 剑指offer 59-2. 队列的最大值

本文链接: 微服务中的特性标志和金丝雀发布