yinguangyao / blog

关于 JavaScript 前端开发、工作经验的一点点总结。
253 stars 12 forks source link

技术和方案,不能拆开看 #45

Open yinguangyao opened 3 years ago

yinguangyao commented 3 years ago

技术和方案,不能拆开看

前言

对于很多前端工程师来说,前端技术变化日新月异,很多人热衷于研究新技术。常常出现为了用上新技术,将其用在不适合的场景,导致事倍功半。 比如我们要做个管理系统,你偏要上服务端渲染,我们要做个 h5 项目,你偏要搞微前端,这就属于牛头不对马嘴。 今天,我就从最近遇到的一个例子来聊聊技术和方案的关系。

背景

在开发 APP 的时候,我们常常会遇到发版的问题。不仅需要通过应用商店的审核,还要考虑兼容老的版本。 尤其是对国际化的 APP 来说,翻译往往是很重要的一环。如果不小心翻译错误,那么想立马修复线上的问题就比较麻烦。所以动态化加载翻译文案就比较重要。 我们公司有一个 Transify 平台,这个平台是越南或者泰国团队的同事做翻译用的。我们每次提出需求,由他们来翻译成对应的泰文和越南文。 刚好隔壁组做了一个 ccms 配置中心的项目,允许我们在上面创建应用以及子业务,大概就是 Ant Design 这种树状结构。他们希望以后能推广到整个公司,将每个团队动态配置的东西放到上面,避免 hard code

image.png-58.6kB

客户端团队原本也是在应用启动的时候,直接去调 Transify 的接口,但这样不够灵活。他们也无法感知谁修改了某个 key,会不会有一些问题。 于是,产品给我们提需求,希望能够接入配置中心,将翻译文案做成动态配置的,可以按照版本进行差量更新。

在 ccms 上面我们可以创建一个大的应用,以及下面对应的子应用,在我们这边常常是按照 部门 -> 业务 -> 子应用 -> 地区 进行的分级,每个叶子节点存储每个子应用的配置信息。

PS:为什么不直接在 Transify 上做呢?因为 Transify 是新加坡团队做的,我们很难去推动新加坡那边帮我们做这些。

一期方案

为了做到差量更新,那么翻译 key 的粒度就一定要细,细化到每个 key 的维度,每次进行一次 diff,比较出来新老版本的差异,将差异部分返回给客户端。

于是,在 ccms 这一侧,安卓和 iOS 会创建不同的 branch,每个 branch 下面会根据地区区分创建 en、th 或者 en、vn 的子 branch,每个子 branch 下面的叶子节点就是我们的翻译 key,每个 key 的 value 就是翻译文案。

image.png-243.6kB

那么怎么把 Transify 的数据同步到 ccms 上面呢?定时调接口去拉?这样理论上也是可行的。但 ccms 团队希望他们能够做得更通用,以后是服务于整个公司的,不应该去接入业务层的东西。

那为什么不直接把翻译放到 ccms 上面呢?这样客户端只需要和 ccms 对接就行了。因为越南和泰国的同事都是使用 Transify 更新翻译的,他们根本不会用我们内部的 ccms 平台。

所以他们决定在我们的 Admin 系统新增一个页面,每次点击 Search 的时候就去拉 Transify 平台的翻译数据和 ccms 平台的翻译数据,diff 对比出 ccms 上面需要添加、更新、删除的 key 展示在页面上,然后允许手动 submit 到 ccms 上面。

image

数据存入 ccms 后,也需要在他们那边手动对有更新的 branch 发布一个新的版本,打上 version 的标签。

ccms 侧提供接口给客户端,在客户端启动的时候调用 ccms 的接口告诉他们本地的 branch 版本,然后 ccms 对两边的 version 进行对比。如果不一致,那就返回当前 branch 下面所有 key 的 version(只返回 version 有利于减少传输数据的体积)。

客户端拿到新的 version 后,和本地 key 的 version 进行对比。将需要更新的 key 再传给 ccms 接口来获取这些 key 的数据。

大致的流程如下:

image

缺陷

第一期按照这个方案做了出来,每次点击搜索的时候,Admin 侧去从 Transify 和 ccms 两边获取数据,然后进行一次 diff 的对比,对比出来 ccms 上面的哪些 key 是需要添加、更新、删除的,然后再调用接口存入 ccms 的数据库里面。

问题就在于从两边拉数据这步,每次点击搜索之后,页面加载肉眼可见的慢。分析了一下,diff 耗时 100 ms 左右,Transify 平台上面接口耗时 4-5s,而 ccms 上面达到了惊人的 10s。即使我们把 ccms 接口耗时优化到了 1s,但性能还是受限于 Transify 平台的 4-5s,毕竟我们很难推动新加坡团队帮我们优化。

那为什么耗时会这么多呢?主要原因是请求的数据量过大,每次都是传输了 8000 个 key-value 的 json 对象,大小差不多有 1m 多。

除此之外,由于翻译 key 过多,导致在 ccms 上面创建了上万个叶子结点,这已经违反了 ccms 平台创建的初衷。他们不应该为了兼容一些特殊的业务,把业务逻辑也接入进来。

技术固然重要,可再新再好的技术在这种场景下也无能为力,方案的重要性就体现出来了。

PS:为什么一期的方案设计这么糟糕呢?其实在做一期的时候,原本我们没有想过做翻译 key 的差量更新,只是打算在 Admin 上每次把从 Transify 获取到的翻译 key 全部存入 ccms 里面,ccms 也每次下发给客户端全量的翻译 key,但客户端觉得性能太差,不同意这种做法。最终我们三方达成了妥协让步,使用了一期的方案,却也没料到性能那么差。

二期优化

在一期的基础上,我们针对这两个痛点提出了几个优化方向。

  1. ccms 不再存储翻译 key,所有的翻译应当存在 cdn 文件里面,ccms 只存 cdn 的地址。这样上万的叶子节点就可以优化成一个。在 Admin 提交发布的时候,会把差量数据写入到 cdn 文件里面。而客户端消费的时候,会使用 bs diff 算法进行二进制文件的差分,比较出新老两个文件的差异,将差量 patch 进去。
  2. Admin 侧不再等 Search 的时候实时进行 diff,而是增加一个 NodeJS 服务,每10分钟拉一下 Transify 和 ccms 的数据,进行 diff 操作,然后将 diff 后的结果保存起来。当在 Admin 点击 Search 的时候,实际上获取到的是已经 diff 后的数据,大大减少了用户的等待时间。
  3. 如果线上已经出问题了,等待10分钟就会变得很严重。为了避免每10分钟拉一次不够实时,在 Admin 上提供 Sync 按钮,支持实时拉取两端数据做 diff。

image

当然,这个二期优化在技术实现上没有任何难度,但确实完美解决了这个问题,每次 Search 的时候速度降低到了 1s,可以说提升了9倍之多。

除此之外,使用 bs diff 做二进制文件的差分,避免了 ccms 和客户端繁琐的交互,降低了风险,也保持了 ccms 的通用性。

实际上,这个也是我们这边的 RN 团队做增量更新、H5 游戏团队做离线包优化都是通过 bs diff 进行二进制差分实现的,技术原理都是一致的,但能不能用对场景就显得尤为重要。

总结

刚工作的时候,我们尚且年轻,对于很多问题的看法比较片面,很难看清每个方案背后隐藏的坑,也不知道怎么去不断优化,寻找最优解。 技术应该服务于业务,可我们常常陷入技术的深坑中,执着于技术栈的选型。在我们部门这边,大家并不怎么 care 你使用的技术,只要你可以 hold 住,用啥技术都行。 当然,我们这边也会有用错技术栈的时候,比如管理系统用了 Nuxt 服务端渲染,导致构建速度特别慢,使得开发体验大打折扣。也会有一些让人眼前一亮的 idea,比如使用 monorepo 来管理一些分散的 H5 页面,大大提高了开发效率。

L1atte commented 1 year ago

想问下博主,每次梳理业务都会画像上面一样严谨的时序图吗,还是列一个初步的 order list

yinguangyao commented 1 year ago

@L1atte 不会,暂时只是一个 order list