有赞移动如何做到并行灰度的复杂场景?

使用场景

    作为移动开发的我们经常会遇到两种需求: 1. 展示逻辑线上需要随时变更,例如登录注册页面用户协议链接的变更
2. 新业务的灰度下发,如订单等核心业务的重构,如何去保证灰度到指定的商家或适当的灰度比例,在确保线上业务的稳定性的情况下来进行数据的灰度下发。

现状

    为了解决以上问题,就需要App侧具备一定的动态化能力。 目前增强App端的动态性方式,通常来说有下面几类:
1. Hybrid

这种方案是最常见的,Webview 中嵌套 H5 页面,通过 JSBridge 来与原生页面进行交互,也是成本最低的方案,但是一般而言用户的体验会比较差,比较依赖用户的网络环境,通常用于 App 内展示频率不高的页面。
2. Weex、RN

使用 Weex 和 RN 应该是现在电商 App 中使用较多的动态化框架,与原生相比,多了一层 JS 解析,渲染速度会比原生慢一点,但是在可接受的范围内,带来的动态发版的优势是原生所不具备的优势,同时相对于 H5 而言渲染性能要好很多,在 App 中经常会用于新业务中交互不复杂,对用户体验要求不是特别高的场景。
3. Tangram

从实现方式看来,Tangram 的实现思路与 Weex 和 RN 十分相似,不同点只是 Weex 解析是使用 JS 解析成原生组件,Tangram 使用 Json 来解析成原生组件。
4. 通过接口动态控制返回的数据

这种场景较为常见,但是有两个较为明显的缺点,第一点是不同的业务都需要新增接口来完成下发数据的控制,开发成本大,第二点是网络请求是个异步的过程,每次去请求(尤其是在页面跳转时的判断)会给用户带来不好的使用体验。

在面向商家端的 App 上,商家需要保证线上功能的稳定性,在功能迭代较快的场景下,如何保证 App 端上线后可以动态调整,不影响用户的使用呢?

    在微商城 App 中,Hybrid 和 Weex 都有在使用,但是常用功能为了用户体验及改造成本,大家一般还是会使用原生开发。那么如何使得原生页面的 App 端每次上新功能的时候,可以让少部分的数据动态变更呢?
    在这一点上后端的 Apllo 给了我们灵感,App 侧其实也是可以使用类似的方案,来实现线上业务的降级配置的。当线上新功能出现问题时,使用配置中心进行降级处理,待 Weex 发版或热修复下发后,重新打开新功能,可以最大限度的降低 App 端线上业务的影响。同时,业务方也可以利用配置的下发能力来限定功能的升降级范围,以对业务进行灰度测试。为了补齐原生侧的这部分能力,我们开始了对配置中心的设计。

我们需要一种怎样的能力?

    从开发的角度获取了配置中心需要提供以下能力:

  1. 配置可动态变更。
    动态变更是配置中心最核心的需求

  2. 便捷易用

    对开发而言,使用起来尽量简洁,不需要去关注更新的时机和线程切换。

  3. 可以根据业务和版本做动态下发,而非单纯的简单 KV 配置

    与 H5 和后端不同,App 端由于自身特性,线上往往都是多个版本共存的,这就造成了下发配置的时候需要对 App的版本做区分,一个 App 中同时会有很多基础组件,如商品、订单、IM等,这些组件往往需要有自己的配置和版本,对于组件配置的区分也是有必要的。
  4. 有版本回溯功能,一旦配置错误可以快捷撤回上一个版本

    新版本配置有问题,可以快速回滚到上一个版本。
  5. 可区分环境

    开发环境可调试,每个环境配置独立。

架构设计

    配置中心自开始开发以来,一共进行了两次大的迭代,分为了 1.0 和 2.0 两个版本

配置中心 V1.0

后端设计

在配置中心后端下发的整个过程中,大体分为4个步骤:

  • 组件匹配
  • 配置单匹配
  • 下发状态匹配
  • KV查询下发

    其中,组件是配置中心配置下发的场景下使用范围抽象出的一个概念,可能是一个 App ,也可能是一个 SDK 。配置单则是配置下发的单位,每次发布会新建一份配置单,内部包含了多个 KV 数据,是下发时的最小单位。下发状态匹配的流程,是为了通过检查告知 SDK 是否需要更新本地缓存的配置,减少组件 KV 较多的情况下,每次更新重新啦取所有 KV 造成的重复读写和网络流量的消耗。

具体配置中心的下发流程,可以参照下图来进行理解 image 除了以上下发流程的调整外,配置中心还通过接入移动基础保障平台来完成配置发布的审核,防止出现线上数据被误操作导致故障的场景出现。

SDK侧设计

  • 配置中心仅在 App 进行前后台切换时检测各个组件配置的更新状态,以确保用户在使用 App 时能快速获取到最新的配置
  • 将拉取到的配置缓存在本地,每次读取配置时均从本地配置获取配置的内容,调用 SDK 获取配置 KV 值的过程中,从缓存中获取
  • 当 App 始终在前台使用时,借助 IM 的长连接功能,将配置中心配置变更的消息推送到 SDK ,触发 SDK 拉取更新 通过以上几点,可以保证App端能够及时准确的获取到最新配置。

SDK侧整个下发流程也就可以简化为下图所示:

老的配置中心配置分发图:

配置中心 V1.0 的优点和缺点

    虽然配置中心上线后大家在使用过程中没有遇到线上问题,配置中心很好的完成了它的任务,但是这一版本的配置中心还是有着明显的优缺点的:

优点:

  1. 匹配逻辑简单明了,这也是配置中心第一版能快速上线的原因
  2. 在组件维护人员不多、项目并行场景少的情况下可以满足初期的大多数需求

缺点:

  1. 第一版上线时没有太多考虑扩展性及数据同步方面的问题,所以当后续想加入一些逻辑时难以扩展(例如灰度机制的引入,就是在原来的匹配机制上硬性加入了一层判断)。
  2. 由于下发的最小单位是配置单,所以判断用户是否有新的 KV 信息,必须要经过1次活多次 DB 查询来筛选出对应配置单,这部分逻辑由于在加入灰度判断后与 dfp (设备指纹)有关,所以无法进行有效的缓存,只能直接查 DB。
  3. 组件下发各层数据之间以表自动生成的 id 作为关联查询的条件,由于对自动生成的 id 有强依赖,所以是的跨环境同步的逻辑无法实现,如果需要实现只能整个复制。每次发布均需要将配置单内的所有 KV 复制一遍,造成部分资源浪费,同时不太容易追溯具体 KV 的修改记录。

配置中心 V2.0

    虽然配置中心 V1.0 的版本有部分缺点,但是对当前配置使用并没有什么影响,尽管在使用过程中也有同学提出,A/B Test 的支持的需求,由于改动成本比较高,所以这项需求的优先度并没有提的很高,直到项目并行越来越多,发现配置中心由部分场景开始难以满足新的需求了。

    随着业务的告诉迭代,很多项目已经开始习惯于使用配置中心来进行新功能的降级以及灰度,在同一个组件同时有多个项目在灰度的过程中,由于灰度条件的限制不同,导致了大家需要相互妥协。也就是在这个时候开始,我们发现现有的设计已经无法满足大家的需求了,大家目前需要的灰度范围不是整个分发配置单,而是具体某个 KV 。所以经过了新一轮的需求的收集,我们决定对配置中心的组件进行一次升级,以满足多项目并行的灰度场景和A/B Test 的测试场景。

    在这次的组件升级中,我们对组件的下发流程进行了一次比较大的优化:

  1. 新版本检查前置

    组件的配置版本标示在检查组件信息时即可获取到,不再需要多次查询DB。
  2. 组件的每个下发 KV 单独控制

    每个业务方再发布新的灰度 KEY 的时候,不再需要去检查是否与其他项目冲突,灰度粒度下沉到 KV 级别。
  3. 在下发的每个查询处理均加上了缓存,以提升查询效率。
  4. 所有数据查询均不依赖数据自增 id ,这部分是为新组件未来的扩展性考虑。

新组件发布后,配置中心的整个下发流程有了较大变化: image

同时,在管理平台发布新组件的逻辑处理流程也变得相对复杂了一些,着些新增的处理逻辑是为了减少 SDK 检测配置更新时减少 DB 查询等待的时间,变更后的 KV 发布时数据库变更逻辑见下图: image

使用展示

在配置中心的最新版本中,每个 KV 都能清晰的展示出其 KV 的类型、功能及适用版本 image 点击进入后,可以查看到具体的具体的 KV 发布记录,可以进行回滚、同步、复制等操作 image 编辑 KV 的过程中,用户可以选择下发的范围及下发 KV 的格式 image

对于客户端而言(以 Android 为例): 只需要初始化和获取 KV 值两部分代码即可使用配置中心。 接入示例:

ConfigCenter.init(this).setConfigKey(configKey,moduleVersion,CONFIG_FILE,CLIENT_ID,CLIENT_SECRET,otherInfo)  

取值示例:

 var result =
            ConfigCenter.getInstance().getStringByKey(STUDY_CONFIG_CENTER_KEY, configKey, "")

经过简单的配置,客户端的业务逻辑即可实现动态切换的效果。

总结

    以上是我们整个配置中心的建设方案,通过配置中心的使用,对原生 App 动态性的短板进行了补齐,也可以帮助业务方对新业务的线上灰度提供帮助,高效快捷的控制灰度范围,希望可以和广大开发同学一起交流改进。

欢迎关注我们的公众号