有赞 App 动态化配置中心实践

背景

客户端大量的硬编码导致其灵活性大大降低,一些细小的改动只能通过发布版本解决,用户升级更新迭代速度慢,时效性差等原因,催生出了有赞 App 的动态化配置中心,它可以将配置,功能,界面,数据等各种配置数据统一进行管理下发,实时生效,极大地提升了客户端的灵活性。

同时配置中心不仅仅是简单的对配置数据进行修改、读取而已,更需要在容错性、流量优化、带宽节省等各方面的优化上下功夫。本文主要提供了有赞 App 的动态化配置中心解决方案,也总结了版本迭代中所做的优化。

配置中心设计

起初有赞各 App 内都散落着一些写死的链接,但随着 WWDC 16 中,Apple 表示将继续在 iOS 10 和 macOS 10.12 里收紧对普通 HTTP 的访问限制,并且无法使用 NSAllowsArbitraryLoads 来绕过 ATS 限制。我们只能很费时费力的把各 App 内所有访问 HTTP 的链接都修改成 HTTPS,这一个小小的改动就如此麻烦,那后续大的改动更难以想象,所以我们开始思考能否将这些重复且动态的工作抽象成一个配置中心,用它来支撑各个业务。

第一版

第一版设计的相对简单,我们只是专门设计了一个 API,通过 API 请求配置数据,并且每个业务单独维护一份配置文件。

具体流程:客户端进入到前台,也就是应用程序被激活的时候,通过配置中心的接口向服务端请求最新的配置数据,如果配置文件有更新,则下发最新的配置给客户端,并且客户端本地存储这份最新的配置,用于应用运行时使用。



但随着模块化的推进和应用数量的增加,配置文件的体积和数量会逐渐增大,每一次修改都会全量下发到客户端,这部分的流量累积起来是一个非常庞大的数字。并且使用配置文件去管理配置,只能维护到最新的配置,无法做到下发指定版本的配置。

所以针对这些弊端问题,我们衍生出了第二版配置中心。

第二版

为了解决第一版产生的流量浪费和配置管理弱的问题,我们优化了配置下发和管理流程,我们采取的策略是增量更新数据库存储配置

具体流程改动:


增量更新

增量更新的优势主要体现在业务增长的过程中。我们的配置文件可能会从几十 KB 增长到几百 KB 甚至更大,如果还继续使用全量更新,这部分流量对用户来说是非常浪费的。而使用增量更新之后,服务端只需要下发不同配置之间的差异补丁包,补丁包的大小相比于原始配置的大小是非常小的,可以节省下90%左右的流量,这是非常可观的。

增量更新我们目前使用的是 Google 出的 google-diff-match-patch ,支持 Java, JavaScript, Dart, C++, C#, Objective-C, Lua 和 Python,但是官网已经下掉了该 SDK(不明白为什么),需要到 GitHub 上搜索类似于 diff patch language:java 这样的关键词,就能找到对应平台下的 SDK 了,有人已经 fork 出来了,因为只是字符串的比较处理,所以可以放心使用。
该增量更新的原理是先通过比较首部和尾部的相同部分,目的是提升一定的效率,再比较中间差异的部分,差异的部分通过一些字符去表示该改动是 DEL 还是 ADD 还是 EQUAL ,这样最终形成的补丁包比改动部分要大一点,但是相比于全量更新已经减少很多了。

并且生成补丁包基本是毫秒级的,也不必担心接口请求的耗时问题。

服务端把前后配置的字符串进行比较即可得到一组补丁数据:

$dmp = new DiffMatchPatch();
$patches = $dmp->patch_make($oldConfigString, $latestConfigString);
$patchStrings = [];
foreach ($patches as $patchObject) {
    array_push($patchStrings, $patchObject->__toString());
}

生成的补丁以官方为例,补丁内包含两个字符串之间变动部分的 location 和 length,并最终下发给客户端:

@@ -16,21 +16,29 @@
 see 
-yonder
+the
  cloud 
+over there 
 that
@@ -47,18 +47,19 @@
  almost 
-in
+the
  shape o
@@ -86,24 +86,18 @@
  By 
-the mass, and 't
+golly, it 
 is l
@@ -129,21 +129,23 @@
 et: 
-Me
+I 
 think
-s
  it 
-i
+look
 s li
@@ -177,12 +177,12 @@
  is 
-back
+shap
 ed l
@@ -234,11 +234,19 @@
 us: 
-Ver
+It's totall
 y li

以 iOS 为例,客户端获取补丁包后,根据补丁包内变动符号以及变动内容对本地配置文件内容进行字符串拼接或删除操作,形成最终的配置:

DiffMatchPatch *dmp = [[DiffMatchPatch alloc] init];
NSError *error = nil;
NSArray<NSString *> *patchStrings = configDic[@"patches"];
NSMutableArray<Patch *> *patches = [NSMutableArray array];
for (NSString *patchString in patchStrings) {
    NSArray<Patch *> *patch = [dmp patch_fromText:patchString error:&error];
    if (!error) {
        [patches addObjectsFromArray:patch];
    }
}
NSString *newCacheConfig = [[dmp patch_apply:patches toString:cacheConfig] objectAtIndex:0];

为了确保客户端最终生成的配置与服务端保持一致,客户端在打好补丁之后,用该配置生成 MD5 值,与我们在服务端下发补丁包时携带的最新配置的 MD5 值进行比较,在一致的情况下才去缓存配置,避免因为补丁造成 App 无法使用的问题。

数据库管理配置

实现增量更新的前提是我们需要有不同版本的配置记录,而用文件去管理是非常不可控的,取而代之的是我们可以通过数据库来管理,同时利于后期的横向扩展,可以针对不同平台,不同渠道,不同版本等规则下发。



目前我们是根据版本和应用来关联到配置,所以表结构设计的很简单,把应用、版本和配置放在一张表里面,后面针对多渠道和多平台等规则之后,表结构上会做一定调整。

由于需要 App 能及时获取到最新配置,我们选择在 App 激活的时机去获取配置,通过数据统计中心我们可以看到 App 当日启动次数的时段分析,再结合配置文件大小,我们可以预估所占带宽的大小,运维是非常担心你把他的带宽跑满,影响到其他业务,所以我们需要做两个优化,一个是压缩配置,一个是本地缓存。

压缩配置

为了不占用过多的带宽,我们需要在写入之前把配置压缩,读取之后解压配置。

这里我们通过 gzip 来压缩配置:

$data['config'] = base64_encode(gzcompress(json_encode($config)));

需要注意的是 gzip 可以设置压缩等级,范围是0 - 9,默认是6,但是提升压缩等级会占用较多 CPU 时间和内存,我们使用默认等级压缩之后,配置文件体积减少了75%,看来是配置内相同的部分比较多,所以压缩效果还是比较明显的。

这里有人会问,为什么 gzip 之后还要 base64 编码一下,因为二进制数据不能直接写到text字段里面,写进去之后读出来也是解压不了的。进行 base64 编码之后,我们的压缩配置内容会比原来多1/3的长度。

本地缓存

对于相同的配置,我们可以不用频繁地去请求数据库,而将其缓存到本地,这样后续的请求可以直接从缓存中读取配置,当然请记得设置缓存的过期时间。

我们目前用的是 Redis 缓存,它对于复杂的数据结构和操作支持的相当不错,而且操作简单,对于前期只是key-value存储的话,可以考虑使用 Redis

可操作界面

为了方便业务方修改配置,我们在内部平台上提供了一个简单的可操作界面,通过顶部的Tab栏切换查看不同应用下的配置记录。


点击 查看详情 可以看到当前版本的配置内容,目前展示的效果并不友好,后面会进行优化。

在需要修改配置的时候,我们可以通过 新增配置 给指定应用修改配置,新增时会自动在版本上 +1。

后续优化

目前有赞 App 已经实现了动态化配置中心整一套流程,但是在一些细节方面还需要不断的改进优化,比如:

  • 更加丰富的配置下发规则
  • 可操作界面配置的有效性的校验
  • 可操作界面的视觉优化
  • 等等

也欢迎大家提出自己的意见和建议,我们一起探讨学习!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容