前端开发都应该知道的配置中心

24次阅读

共计 3577 个字符,预计需要花费 9 分钟才能阅读完成。

前端开发都应该知道的配置中心

动态化方案一般都是比较大型的,比如 react native、flutter 等都是从 UI,运行逻辑等多方面完整的动态更新。但实际上,移动端还有很多细粒度的配置类数据需要支持动态更新的。

比如某一个文案或者广告的位置希望可以根据用户表现来随时改动,又比如你开开发了一个线上功能,但上线后才发现里面潜藏了一个严重的问题,希望可以同过一个线上开关立即关闭此功能。

这一类需求用一句话来讲就是叫做:千万不要写死。

Android 里面大家都很数据的 sharedpreference,里面可以存储很多的 K - V 类型的数据。那我们如何来实现一套可动态更新的 K - V 存储机制呢?从而将这类小型配置或 AB 开关准时下发到所以的客户端。

为了实现这个功能,很多公司都研发了一套自己的配置中心系统,比如阿里内部的 orange 系统,能狗做到秒级下发所以配置到全量客户端。

要实现这样一套系统,需要考虑到几个问题点:

  • 首先,远端配置更新后,如何实时通知给所有客户端呢?
  • 配置数据体积越来越大,下载失败率变高,如何优化数据包大小呢?
  • 配置数据的安全性如何保证?
  • 如何灰度发布配置?
  • 实时全量下发的话,CDN 会存在什么问题吗?
  • Android 如何实现多进程监听配置变更?

如何通知客户端配置更新

首先我们要解决的问题是: 如何才能尽快告诉客户端,配置数据以及更新了?

1. 主动拉取(Pull)

第一种方式,我们可以让客户端进行主动轮询:

  • Polling 定时轮询:定时发起请求到后端,拉取最新的配置数据。。
  • Long Polling 长轮询:后台起一个 service,持续去发去长轮询判断后端是否有配置数据更新,一旦发现有更新,再发起请求去拉取配置

关于长轮询说明下,普通的短连接是客户端发起 socket 请求,服务端收到后,不管有没有数据,都会立即返回。

而如果长轮询,服务端如果没有数据则会 hold 该请求,将 socket 等请求信息保存起来,不立即返回,等到有数据时才通过 socket 重新写回客户端。当客户端收到结果,或者判断请求超时,便会发起下一次的长轮询请求,此时的超时时间一般会比正常的 http 请求的超时时间长,比如一分钟,比减少请求次数。

这种方式可能能够达到比较好的实时性,但很显然,会带来非常多的流量浪费,而且对后端也会带来非常多无用的请求,造成机器资源的浪费。

2. 推送(Push)

这种方式可以借助长连接

当我们发布更新配置后,可以通过长连接通道向在线用户下发配置变更通知,用户收到通知后便会主动去拉取配置。

这种方式可以极大的降低流量损耗,只有配置变更时才会拉取数据,但也存在一些问题:

* 需要维护稳定长连接通道,存在一定的技术成本。* 如果用户长连接断开,会导致无法接收到消息,从而不能保证实时变更。

因此,这种非方式虽然节省流量,但不能够保证 100% 的实时配置生效率。

3. 推拉结合

既然主动拉取和推送都做不到,很自然的,我们会想到能不能两种方式结合起来,方案如下:

* 客户端与服务端保持一个长连接,当有配置变更时,立即下发;* 为防止长连失效导致更新不及时,客户端还会作定期轮询,拉取配置;

通过这种方式,可以获得较低的流量开销和较高的更新率。业界不少企业采用的是类似的方案,比如携程的配置中心系统 Apollo。

4. 统一网关

这里介绍第四种更新机制,来自阿里的 Orange 移动配置系统,它无需任何轮询请求和长连接通道下发,而且可以做到在线用户 100% 的秒级更新率。

它的秘密就是利用了全集团的统一网关,这套网关同时运行在客户端(Android、iOS)和服务端。移动端网关会接管所有客户端的请求,而后端网关则会承接所有移动端发过来的请求。具体流程如下:

 * 客户端的任何网络请求在经过移动端网关时,带上本地配置的版本号;* 服务端网关收到请求后,抽取出配置版本号,发送给配置中心服务,其他参数透传给业务后端
 * 配置中心服务基于当前 APP 的版本号,配置版本号信息,判断是否有新配置,如果有则返回新配置版本号给网关
 * 当业务后端返回时,带上这个新配置版本号给客户端
 * 客户端发现新配置版本号,则去 CDN 拉取最新配置数据,完成更新。

在这个流程里,我们没有为配置单独发起轮询请求,也不需要依赖长连接服务进行下发,而且借助现有的业务数据,加上网关的协议,来实现配置的动态更新。只要客户端处于活跃状态,就能立即发现配置更细你,而且非常稳定。

配置文件的增量更新,压缩与加密

很多开发者担心自己开发的功能上线后悔出现问,所以会加上各种各样的配置开关,随着版本的不断迭代,配置数据肯定会越来越大,如果每次数据更新,都需啊哟去全量下载,那消耗的流量会越来越多,而且,数据包越大,下载更新失败率会也会逐步变高。

因为,我们还要想办法减少下载配置数据包大小,一般会从两个角度考虑

1. 增量更新
2. 压缩

除此之外,有些配置数据是敏感的,所以应该要实现加密。

增量更新

增量算法有很多,我们可以结合具体场景来选择。

由于我们的配置都是纯文本,所以可以优先考虑文本 Diff 算法,如 Google 的 diff-match-patch,就是专门针对纯文本的高性能 Diff/Patch 算法。而且它提供了多种语言版本实现,包括 Java、Objective-C、Python、Dart 等,

diff-match-patch 内部是基于 Myers 算法,这个算法就是我们天天用的 git diff 和 RecyclerView 里的 DiffUtil 的实现原理。而且 diff-match-patch 在这个基础上还做了不少性能优化。

当然,除了纯文本 Diff,我们也可以考虑二进制 Diff 算法,如 BsDiff 算法,一般 apk 的更新、Tinker 补丁包更新都可以采用这个增量算法。

压缩

对于文本压缩,用的最多的就是 Gzip 了,Http 协议传输也是用的 Gzip 压缩,兼具压缩率和性能。所以此处也可以考虑 Gzip 压缩,接入成本最低,效果也不错。另外也可以考虑 zStd 压缩和 Brotli 压缩,感兴趣的读者可以去深入了解下。腾讯云 CDN 服务目前已经支持了 Gzip 压缩和 Brotli 压缩,当文本文件大小在 256Byte – 2048KB 之间时,采用 Gzip 压缩;更大的文件则采用 Brotli 压缩。

加密

不少情况下,我们会用配置中心来下发一些敏感数据。比如双十一活动,某个优惠券的生效时间戳,如果用户分析出来了,可以人为修改配置数据从而导致程序运行异常。因此,为了保护下发的敏感配置数据,我们需进行加密。

第一种方式,下发链路加密。我们可以对下发的配置数据压缩包进行整体加密,客户端拿到后,流式解密和解压到本地文件。以后客户端读取配置时可以直接读取明文。这种方式的问题就是配置数据是以明文形式落盘(存储到磁盘)的,这可能存在数据泄漏风险。

第二种方式,单 Value 加密。我们会对每个 Key 对应的 Value 进行加密,然后下发到客户端,解压后存储在磁盘里的是明文 Key+ 密文 Value,在需要读取某个配置时,再实时解密并缓存在内存里,从而降低数据泄漏风险。

在选择具体加密方式方面,考虑到客户端的解密耗时,可以使用类似 AES/CBC/PKCS7Padding 加密算法,密钥可以存储到客户端保护起来(为了安全可以放到 so 内部,通过混淆提高安全性,或者采用白盒加密方案,将密钥与算法一起编译生成代码从而隐藏密钥),也可以支持动态更新。

另外,Value 加密后是二进制数据,此时要利用 Base64 编码,把二进制数据转成文本写入配置文件中。

灰度、回滚及 CDN 压力

前面说了,我们的配置中心能够做到秒级下发,那这里会存在一个问题:如果配置有问题,可能会立即导致大规模线上故障。因此,这里需要支持配置的灰度发布。

一般可以支持多维度灰度,比如基于用户的 App 版本区间、Uid 或者 DeviceId、城市、渠道等维度信息来进行灰度。灰度发布后开始观察线上数据表现,确定逻辑运行正常且稳定,如果没问题,才可以全量发布。

那如果出了问题呢?那就要进行回滚。这里我们可以通过重新发布一个历史版本的配置数据来实现回滚。

另外,全量发布后,我们会将更新的配置数据压缩发布到 CDN。这时,大量客户端在检测到更新后,会立即访问 CDN 下载对应的更新配置数据。如果用户量级很大,需要考虑会出现的 CDN 请求高峰,要确保 CDN 能承受对应的访问压力,不能被打挂。而且,由于 CDN 同步需要一段时间,所以此时肯定会有很多 CDN 发生回源,对源服务器也会造成一定的压力。因此,在每次发布后,可以结合具体场景及用户 QPS,有选择地考虑对 CDN 进行预热。

小结

配置中心逐步成为各家公司的标配,一套好的配置中心系统,不仅能够支持各种维度的配置下发,如业务配置、技术配置、开关配置等,也要能够做到实时下发,能够尽快触达所有用户。尤其在一些用户量较大的场景下,要能够考虑流量压力,保证 CDN 等基础设施能够稳定运行。

正文完
 0