出了什么问题
大多数客户端都有远程配置的功能和需求,项目规模由小到大以后,对客户端动态配置的需求就会迅速增加。就会出现新的问题和需求。
- 配置项逐渐增多,配置内容大小逐步增大
- 客户端版本逐渐曾多,版本之间的配置差异较大,配置管理混乱,牵一发而动全身
- 运营和产品对业务配置的需求逐渐增加,需要有独特的配置关联逻辑
- 配置不断庞大,下发到客户端的时候需考虑流量问题
- 配置服务端考虑并发问题
- 配置动态下发,考虑不同的更新策略,考虑安全性
历史及现状
可能很多移动端的开发者都有这样的经历:
通过某一个接口,获取某一业务功能的配置信息,有时候需要根据业务触发的逻辑,在不同的时机调用接口,获取配置,又有些时候需要把客户端的一些信息,如版本号、时间、上次的缓存等等,传入接口获取最新配置,这其实就是最简单的远程配置需求,根据配置相关条件,通过不同的更新策略,获取远程配置。
1.0阶段
设计接口如下:
$ curl https://api.xxx.com/v0/configuration/getMessageConfig?appVersion=1.0.0&platform=iOS&messageType=1 \
-H "SS-Cache-Version: XXXXXXXXX"
{
"code":"0",
"result":{
"config_value":"******",
"cache_version":"******"
}
}
通过不断开发新的接口,满足各个业务线对配置功能需求。
优点:需求较少的时候,开发特别快
缺点:客户端需要不断开发新的API调用,服务端则需要不断开发新接口,配置与客户端的关联信息发生变化,API则需要进行升级,而客户端存在多版本并存的情况,老版本API也无法下线,占用服务端资源。
2.0阶段
为减轻服务端开发依赖,有的开发者通过使用第三方开发平台进行配置分发。
例如,使用umeng的在线参数进行远程配置
优点:
无需服务端开发,第三方实现SDK与其服务端的通信,不需要做客户端开发
缺点:
无法与客户端信息做相关,只能通过约定配置参数名称例如 配置项名称+平台名称+客户端版本作为配置项名称,做硬关联,大量生成重复配置项。无法控制更新策略。业务相关配置放置在第三方平台,无法保证数据安全。
3.0阶段
由此前的经历和需求痛点,我们设计出新的移动端配置中心。
主要有如下功能
- 支持配置客户端信息与配置项相关,做到不同的客户端获取不同的配置项
- 支持配置增量更新
- 支持客户端配置加密下发,保证安全
- 提供统一后台配置界面,方便进行配置的管理
- 做到配置更新,主动推送到客户端
- 支持配置回滚,删除恢复
解决思路
根据以上需求,整体方案的解决思路可以是如下这样:
- 移动端开发远程配置组件,用于获取最新的后端配置
- 客户端与接口交互的时候,将诸如appid(存在对个应用)、appVersion、platform、channel(渠道)、systemVersion等基础信息传入接口,方便配置项与其关联
- 客户端与接口交互的时候,支持全量更新,支持通过生成增量包进行增量更新
- 服务端与客户端进行数据传输的时候,进行通信数据加密
- 对于服务端,客户端提供定时配置同步策略,服务端通过推送和长连接通讯主动推送配置
- 配置项后台的增删改查,都采取版本叠加,而不是重新覆盖,支持回滚与删除回复
- 通过标签系统,提供更灵活的配置项与客户端的匹配关系
涉及方面
有了大概的解决思路以后,可以对涉及到的方面进行整体盘点,其实这也是移动端基础设施的摸查,涉及以下几个方面:
- 客户端配置组件,与配置中心交互,获取配置信息,定时轮询更新策略,内存缓存与本地持久化配置信息,增量包应用,接收服务端推送事件
- 配置中心,提供对客户端获取配置的API接口服务,接口支持根据客户端信息和标签返回增量数据包/全量更新包
- 后台管理系统,提供应用管理和配置管理,支持新建配置,修改配置,回滚和删除配置
- 认证服务,客户端通过认证服务以后,获取通讯管道加密的会话密钥,提供接口数据加密功能
- 标签系统,提供对客户端、设备、用户等多个维度的标签设置和获取服务
- 消息中心,提供根据标签系统,将配置更新的事件推送到客户端
- 定时任务系统,提供定时服务,将配置生效、推送配置等设置为定时任务
整体设计
API设计
客户端与配置中心进行API交互,接口定义如下:
queryConfiguration
入参:
- clientInfo,包含appid、appVersion、platform、channel等
- cacheInfo
{
"clientInfo":{
"appid":"***",
"appVersion":"1.0.0",
"platform":"1", //1为iOS 2为Android
"channel":"AppStore"
},
"cacheInfo":[{
"configName":"routerConfigMap",
"version":"*****"
},
{
"configName":"conditionConfiguration",
"version":"*****"
}
]
}
返回值:
- configurationData,一个数组,包含配置项全量或者增量数据包
code | 含义 | 备注 |
---|---|---|
0 | 增量包 | 返回增量包数据 |
1 | 全量数据 | 返回配置全量数据 |
2 | 已删除 | 表示该配置项已删除 |
{
configurationData:[
{
"configName":"routerConfigMap",
"version":"*****",
"code":0,
"hash":"*****",
"patch":[]
},
{
"configName":"orderActivity",
"version":"xxxxxx",
"code":1,
"hash":"*****",
"value":"xxxxx"
},
{
"configName":"conditionConfiguration",
"version":"xxxxxx",
"code":2
},
]
}
客户端与API交互流程图
配置中心管理平台
App管理平台
配置平台
配置管理后台,提供对应用的管理与配置项的管理,配置项与基本客户端信息关联。
配置项存储与客户端信息进行关联映射,关系有OR和AND两种,每个配置项设置关联条件。例如配置项的关联条件为:
configId <—> appId AND appVersion AND channel AND platform OR tag
新建、更新或者删除某一配置,可以设置定时生效,Job系统会定时执行操作,并调用消息中心,按照关联条件,进行推送通知给客户端,客户端接到更新事件后,再调用API接口完成配置项更新。
服务端查询配置项流程
这个流程里有三个关键点:
- 根据clientInfo查询匹配的配置列表,其中,AND关系和OR关系,需要通过构建一个配置id与条件的字典表,查询的时候将AND或者OR的条件相关的配置id全部查询出来,然后进行AND计算(取交集),OR计算取并集,其中appVersion是一个范围,例如设置某一配置项的appVersion为”<=2.3.0 & >=2.1.0”, 根据语义化版本,进行单独匹配,语义化版本 2.0.0 | Semantic Versioning的规范请详细查看文档
- 如果客户端上传有cacheInfo,将缓存的配置id列表上传,那么,根据条件查出的最新配置id列表为A,上传的缓存配置id列表为B,分别计算出A与B的交集,逐个对比hash值,算出增量包,再算出属于A但不属于B的部分,属于新增配置项,做全量包,再算出属于B不属于A的,属于要删除的部分。进行上述计算,只需要定义一套数组的交集、并集与补集的计算,就可计算出结果。
- 生成增量包的时候,需要根据客户端配置id和对应hash值,找到旧版本的配置项,再跟最新的配置项做文本对比生成增量包。文本增量算法有google官方实现的一套:GitHub - google/diff-match-patch: Diff Match Patch is a high-performance library in multiple languages that manipulates plain text.
推送更新消息
这个比较简单,通过后台增删改查的配置项,根据其标签和客户端信息,使用消息中心,将更新事件静默推送到设备,而设备客户端信息和标签,与设备唯一标示的绑定关系,在标签系统维护。
总结改进
新的配置中心,满足了现阶段的需求,通过将配置项的关联条件抽象成标签,再加上增量更新,满足了节省流量的需求。
未来还有进一步改进的空间:
- 配置后台通过jsonschema来效验配置的格式,方式配置错误
- 客户端的配置组件改善通讯方式,使用推拉结合的方式,目前的推送基于消息中心,未来可以使用UDP+心跳方式维持客户端与服务端的数据同步
- 实现更多的更新发布场景,例如,更新配置项,可按照一定比例,逐步进行灰度更新,失败可回退,提高可用性
多谢您花费宝贵的时间阅读,希望能够与大家多多交流