Flag 如何到达你的应用
你的应用不会每次检查 flag 时都调用 FortressFlag。Flag 的读取是本地的——即时、离线安全、路径上没有网络。经过网络的是一次周期性的轮询,而那次轮询取回什么,取决于你在两个平面中的哪一个上。
客户端平面:你的应用拿到的是答案,不是规则
Section titled “客户端平面:你的应用拿到的是答案,不是规则”iOS、Android 和 Web SDK 使用客户端平面。设备发送它的标识和标签;FortressFlag 在服务端评估你的定向规则,只返回这台设备的值——一个小小的映射,比如 {"new-checkout": true, "dark-mode": false}。
设备永远不会收到你的规则。这是双重有意的:你的定向逻辑(“beta 分组”、“内部设备”)是你的业务,不应该被打包进任何人都能从应用二进制里解出来的东西——而且负载也保持得很小。
客户端平面用客户端密钥(ffc_…)做身份验证,它随你的应用一起发布,设计上就是公开的——更多内容见密钥。
服务端平面:你的后端拿到的是 ruleset
Section titled “服务端平面:你的后端拿到的是 ruleset”Go SDK 使用服务端平面。你的服务器下载一个项目 + 环境的完整 ruleset——类型、默认值、规则,细分已经展平在内——然后在它自己的进程里评估 flag。一次 flag 检查就是对已在内存中的数据的一次函数调用:每次检查没有网络跳转,没有每请求延迟,而且无论进程处理多少请求,一次轮询就服务整个进程。
因为 ruleset 包含你的定向规则,这个平面放在服务器密钥(ffs_…)后面——一个真正的机密,像数据库密码一样对待。而既然你的规则会到达每一台持有该密钥的服务器,推论就要由你来落实:不要把机密或你用户的个人数据放进规则条件里。
在这个平面上没有设备标识。你的代码在每次评估时传入一个上下文 key——用户 ID、会话 ID、任何你用来分桶的东西——灰度算术使用它的方式和客户端平面使用设备 ID 的方式完全一样,所以同样的输入无论由哪个平面评估,都落在同一个分组里。
轮询,以及它为什么便宜
Section titled “轮询,以及它为什么便宜”两个平面都按间隔轮询,并且都使用条件请求:每个响应带一个校验值(ETag),当自上次轮询以来没有任何变化时,回答就是一个小小的“没有变化”——没有正文,不重新评估。一个不在改动 flag 的设备群,稳态就是一连串近乎免费的检查。
当你确实改了什么——一次切换、一条规则、一个定时触发——下一次轮询就会完整地取到它。实践中,一次变更会在大约一分钟内到达在线的客户端设备。
(一个计费上的说明:一个“没有变化”的响应仍然算作月活设备计数的活动——但请记住,一台设备每月只算一次,无论它轮询多频繁。)
FortressFlag 的故障绝不能拖垮你的应用。每个 SDK 都把它最后收到的值保存在一个持久的本地缓存里,并通过同一条级联解析 flag:
- 最后收到的值——哪怕来自上一次运行。一台离线一个月的设备提供它最后看到的东西;宁可陈旧,也不要意外的回退。
- 你代码里的默认值——调用处的那个值——在从未收到过任何东西时使用。(客户端 SDK 对没有默认值的布尔读取,最终落到
false。)
两个值得知道的后果:
- 被吊销或失效的凭证不会弄坏你的应用。SDK 继续提供它的缓存,并继续尝试。错误永远不会通过一次 flag 读取到达你的用户。
- SDK 无法验证的负载会被当作根本没有负载来处理。每一个响应都有签名,SDK 会在任何东西改变之前检查签名——见 Payload 签名。
- 归档一个 flag 是真的把它关掉。当一个 flag 离开负载时,SDK 会在同一次轮询里把它从实时值和缓存中一并移除——大约一分钟内,各处的在线设备都解析为你代码里的默认值。缓存永远不会保留已移除的 flag——如果会,归档就无法彻底结束一次灰度。