本文翻译自uber技术团队博客: Flipr: Making Changes Quickly and Safely at Scale 原文链接 https://eng.uber.com/flipr/
介绍
在uber的众多软件系统中,每天都有着大量的配置变更需求。由于系统的庞大规模和复杂度,如何在不发声意外故障的情况下进行配置变更是一项巨大的挑战,这也导致开发生产力的严重下降。为了解决这一问题,uber开发了大量的解决方案,flipr作为这一方案的其中一个部分,主要负责动态配置的变更管理。比如特性开关,白名单,增量发布推送以及其他高级使用场景。在这篇文章中,我们将详细介绍Flipr的架构和特性,以及我们如何快速安全可靠地进行大量的配置变更。
配置系统和运行时
配置系统通过控制一些列的属性值,可以在不需要修改代码的前提下改变应用的行为状态。通常这些kv对被保存在文件 数据库或者应用服务中,因此可以与代码解耦单独进行变更修改,应用程序则通过client libary进行查找访问。配置系统的好处是允许用户在不需要重新编译部署分发代码的前提下通过改变属性值修改应用的操作行为。
Flipr中把这些kv对称为属性。下面的例子使用了JSON格式的返回属性的布尔值,其他的数据类型也是可以进行自定义的:
get 函数返回该属性的值
这种简单的配置系统可以用于存储像如下场景的配置信息:
- 开启#456特性开发
- name 文本框的字符长度限制是256字节
- 地图服务API 的url是http://mapservice:8080/v2”
此外还包含应用特性标记 网络配置 熔断配置以及紧急操作控制。
这种配置场景是一对一的kv映射,因此在不改变代码的情况下,对于拥有大量key配置以及负载配置场景的表达能力将受到极大的限制。例如:
- 特性开发对当前从纽约发起请求的admin的所有成员开启
- 在周三下午4-5点临时禁止该api的访问并返回错误信息
这种使用场景需要的信息只有在运行时才能得知。比如群租用户成员,或则当天时间。在不修改代码的前提下保持动态修改配置生效的方法之一就是能基于运行时返回不同的值。这也是Flipr与其他系统不同的地方。Flipr的属性信息可以根据运行时信息将一个key映射到多个值。
get方法的函数签名变为包含了运行时上下文信息
通过添加该参数,属性的取值可以根据上下文信息决定是否需要被修改覆盖。这些覆盖规则被称为约束条件,应用通过客户端查询属性的最新版本信息,但是现在属性的值取决于上下文状态。
通过Flipr推出新功能
让我们通过具体的例子来详细描述Flipr的应用实践。特性开发是开发者在安全快速推出新功能时采用的一种通用手段。首先我们将包含新feature的代码进行部署,flag值可能就像如下是一个简单的布尔值:
当应用部署的时候,由于属性的默认值设置会false,因此对应应用的操作行为并不会有任何影响,配置例子如下:
接下来,我们对城市id为1的两位特殊用户开启该feature,具体配置信息如下:
当属性被更新的时候,变更将被分发到对应的应用host,当下次client.get被调用的时候,服务将使用最新的规则来处理请求。
如果出现异常故障,我们可以快速回滚关闭该feature,由于配置通过网络调用直接生效,因此可以无需等待漫长的部署或者app store的二进制分发传输。
当feature开启的时候,我们将监控指标信息运行集成测试以查好问题,如果一切正常,那么下一步就是对更多的用户开启feature。比如该城市所有的注册测试账户。当修改通过验证以后,我们将发布到更多的城市直到发布到给所有用户。该流程的任何阶段如果出现问题反馈我们都能进行快速的回滚。通过增量开启feature可以帮助我们快速安全提退出新功能。
特性标签只是一个简单的例子,相同的规则可以被适用于黑白名单控制 试验性测试 地理位置分布或者时间配置。
架构
概览
本节我们将简要介绍Flipr的主要组件,通长Flipr只是一个包含客户端库和UI的组件。由于巨大规模的基础架构,因此需要一系列的组件来分发配置以提升可靠性。
UI
用户通常通过如下截屏的方式进行Ui交互
UI还包含更新属性 回滚配置 互相review 权限管理 历史记录和其他功能
后端API
Flipr 支持广泛的 API,以便其他服务可以通过编程方式使用 Flipr。此 API 使用标准 Uber 软件网络堆栈公开。 API 直接支持 UI 和网关服务,以及其他一些不可用的特殊用例,例如我们的生产工程团队使用的紧急控制和操作功能。
网关
这么多服务器可能会给后端服务带来很大压力,但由于大多数用例都是只读的,我们可以使用网关服务的扇出缓存来保持缓存的最新副本,因此后端不会直接受到请求流量的影响。
配置到到网关的复制是异步的,因此通过客户端进行更改和读取最终是一致的。在实践中,具有扇出缓存的最终一致模型已被证明是可扩展的、可靠的和高性能的,即使对于 Uber 如此庞大的配置规模也能满足性能需求。 Flipr 也正在转向基于订阅模式的更加高效资源利用率的新分发系统来实现同样的功能。
主机代理
如网关部分所述,主机代理是 Flipr 扇出缓存的一部分。主机代理负责从网关中提取数据并将更改持久化到磁盘。持久化到磁盘的一大优势是,如果网关、后端、网络或许多其他问题出现问题,客户端仍然可以继续运行,因为它们只是继续从磁盘上的副本中读取。主机代理还用于指标采集、一致性监控以及从客户端升级和更改。一台主机上可能运行多个容器,但只有一个主机代理会保持所有必需的配置都是最新的。
客户端库
客户端库可以读取 Uber 支持的所有语言的 Flipr 配置数据。这些库将磁盘格式读入内存并监视更新。他们还负责在服务请求配置值时评估约束和异常。客户端的主要功能是我们在示例中看到的 get 函数,但通常还有其他版本,例如 Golang 库中的类型安全的 get 函数。一些客户端库还提供实用函数,用于调用后端 API 以通过 API 进行写入和更新。
特性
属性
编写异常的约束系统具有很大的灵活性。它允许编写考虑到许多逻辑维度的规则,例如地理、城市 ID、用户 ID、驾驶员 ID、车辆 ID、设备类型、应用程序版本、时间、实验处理等。所有这些都可以与各种布尔运算以非常灵活的方式编写任意复杂的异常场景。
属性也可以是具有由模式定义的类型。 Flipr 在 UI 中编辑值时强制执行检验,以降低运行时错误的风险。
操作
Flipr 具有许多有助于保持系统可靠运行的功能。它有一个一致性检查系统,确保磁盘上的文件与后端一致。堆栈中的所有组件都有指标、警报和监控,以确保on-call工程师立即知道是否有问题。有一些工具可以减轻不一致和处理紧急情况,例如破坏缓存和强制更新的工具。还有一个on-call工程师时间表,确保系统和客户全天候得到支持。
部署规模
Flipr 管理着超过 350K 的属性配置,每周大约有 150K 的更改。这些配置数据被 Uber 超过 50K+ 主机的 700 多个服务所使用,给后端系统带来了大约 300 万的 QPS。
安全性
为了保证Flipr 安全、可靠并符合安全和隐私要求,Flipr 具有广泛的安全性、可靠性和审计功能。大多数 Flipr 更改都需要同行评审,这是通过标准化的代码评审工具和 Flipr 中的自定义 UI 强制执行的。有访问控制和权限系统,以确保只有授权用户才能查看和更新配置。部署是在物理维度上逐步执行的,因此工程师可以逐步安全地进行更改,而不是全局应用更改,这会减少任何意外后果的影响范围。同时还与我们的监控系统集成,一旦更改后检测到问题,配置就可以自动回滚。未来,我们还将添加一些令人兴奋的集成测试功能,这些功能将允许我们对预发布环境进行简单易操作的配置变更测试。
总结
这篇文章是对 Flipr 的总体概述——它是什么以及它是如何工作的。在下一篇文章中,我们将介绍一些真实的用例,展示 Flipr 如何帮助 Uber 保持灵活快速的更新而不会影响到日常生产力效率。