如何玩转新技术 - 潘阳 | JTalk 第六期

956 阅读15分钟

编者按:本文系亚马逊的潘阳讲师,在掘金技术社区主办的《中美技术人才硅谷大讲堂 | JTalk 掘金线下活动第六期》 活动上的分享整理。掘金 JTalk 目前已举办6期,每期 JTalk 会邀请垂直行业的优秀工程师来分享优秀的实践经验,技巧方法。旨在为开发者提供线下技术交流互动机会,帮助开发者成长。

潘阳,目前在亚马逊 Alexa Mobile 部门工作,负责 Alexa app 的 React Native 及 iOS 架构与设计。其曾在 Apple HomeKit 团队工作,参与 HomePod 及其他多个 HomeKit 项目开发。此前他毕业于卡耐基梅隆大学并在Google实习。

相信大家能来这个讲座,想必平常也在开源技术社区活跃,对新技术的热情应该蛮高。那么在工作中遇到问题时,你会如何解决?假如有很多种解决方法,你会如何分析?其实这是一个亘古不变的话题,每个人会有每个人不同的方法去取舍自己使用的工具,下面我来分享一下我之前在我相关工作中的经历。

我们先从 HomeKit 讲起,HomeKit 出于隐私的考虑,不像 Alexa 和 Google Home 把所有的逻辑、数据放在服务器,你的手机端只是作为展示端。HomeKit 认为应该把所有的计算和逻辑都放在你的设备上,包括你的手机、手表、iPad、电视。那么实际中存在云端的东西只有一样,就是你的数据。这就导致了一个很经典的分布式问题,我有多少台设备,我同时在共享、读取、修改同一份数据,那么我们该如何解决多设备之间同步的冲突? 这个问题其实很难,在 HomeKit 最开始建立之初,我们并没有花太多的时间精力去尝试解决这个问题,因为在最开始我们的业务逻辑和任务相当简单。虽然理论上数据同步会有冲突,但遇到的情况不会太多,而且它的冲突在当时简单的业务逻辑下用户一般感知不到,所以我们一开始也没花费太多时间在这上面。但是最近这一两年,HomeKit 业务增长十分迅速,业务逻辑变得非常复杂。现在才是一个需要我们花时间和精力去解决问题的时机。

现在的分布式计算领域里有个解决方案叫做“CRDT”,用于解决多设备之间的数据共享冲突。但它现在没有非常成熟的商业解决方案。若要采取该方案,需自己从头开始解决实现。由于这套系统基本上是一个完善的分布式数据库,其整体实现并不简单。此外如果要用到 CRDT,HomeKit 的所有数据模型都要重写。不仅如此,我们还需要与 iCloud 团队进行合作。因为 HomeKit 是借用 iCloud 来存储云端数据的。有大公司工作经验的人都知道,跨部门协同工作非常恼人,不仅是技术层面,你还需要很多管理层层面的沟通与进度协调。所有这些问题加起来,导致这个解决方案的成本非常高。

既然解决成本这么高,上 CRDT 真的值得吗?我们来做一个收益-成本分析。成本我们刚才已经分析完了,下面来看收益。我们根据一些反馈数据得知,数据丢失、同步冲突等现象发生越来越频繁,数据同步时间也越来越长,整体用户体验越来越差。所以这个收益-成本图应该是这样。

幸运的是,Apple 是一个非常在意用户体验的公司。这么高的用户体验的收益使得我们愿意付出这么高的成本。最终管理层还是决定上马项目。从这个例子中我们学到的一点就是,在上马项目之前要做好收益-成本分析,我们才能做出最符合利益的决定。

但是呢,这其中会有一个潜在的问题,你最开始的收益分析并不一定是你最终能拿到的收益,这就是接下来我要讲的另外一个例子,即你的分析可能从头开始就是错的。

这就要讲到另外一个东西了,HomeKit 除了多设备之间的数据同步通讯,还有在一个设备内部的进程间的通讯。一旦涉及到进程间的通讯,就会有各个类的序列化和反序列化。那么最开始的时候很简单,我们用了 iOS 原生的 NSKeyedArchiver 和 NSKeyedUnarchiver。这两个东西非常经典、好用,但是它们需要一些资源和计算力。由于 iPhone 和 iPad 计算力足够强大我们一开始并没有意识到这个问题,直到我们后面推出了手表。如果有用过 Apple Watch 的人大概知道,第一代和第二代的 Apple Watch 硬件性能非常差,你如果用过的话,可能会感觉打开一个 App 会非常的久,所以导致体验非常糟糕。我们当时在想我们究竟能做些什么来提升,至少加速一下启动时间。我们做了一个集中在后端的内部分析,最后发现序列化和反序列化占了一个非常大的比重,将近有超过 50% 的时间花在了上面。在经过一些研究后我们发现,如果是用 ProtoBuf 来进行序列化和反序列化,资源占用可以减少 50% 以上,时间可以节约 70%。这显然是一组很可观的数字,但它的成本也非常高。

我们要把底层涉及跨进程通讯的类全部针对 ProtoBuf 进行修改。首先需要针对每个类设计 proto 文件。然后需要把底层的数据模型的各种操作都做相应修改。因为 HomeKit 里涉及进程间通讯的类非常多,而且相互之间的关系错综复杂,这工程量其实不小。所以目前看来,这个收益-成本图跟前一个 CRDT 基本类似,也是一个高成本高收益的项目。

但是我们当时花了好几个月把它写了出来,到最后快收尾的阶段我们做端对端的测试的时候才发现一个很严重的问题:端对端的测试体验提升并没有很明显,总耗时提升甚至小于 30%。为什么现在端到端测试的结果跟最初我们分析后端的结果相差这么多?答案并不难发现。在整个 Home App 冷启动过程中,其实是分为前端 UI 渲染和后端数据处理两部分,而 UI 渲染占了其中的大头。实际在后台传输和后台数据处理即序列化和反序列化这边,大概也就只占了不到 30% 的时间。所以即便我们把整个后端数据处理的时间提升了 70%,也不过是减少了 30% 中的 70%。这对整体用户感受提升可以说是微乎其微,毕竟如果你已经等了五秒,你不会再介意多等一秒的。

图上蓝点是真实收益,红点是理想收益。显然我们最初早期的分析中并没有分析全面,所以有了一个非常理想的收益。然而实际分析之后才发现真实的收益其实非常低,完全不值得这么高的成本。所以在做收益-成本分析的时候,尽量从系统全局的角度去做分析。仅局限于自己的范围,很多时候会误判成本或者收益。

上述两个例子都还好,都有最佳的解决方案,你的选择无非是要还是不要。但工作中很多时候你会遇到一个问题:你手头可能有很多的工具,你究竟要用哪一个?下面讲得这个例子就是类似的。

HomeKit 作为一个智能家居平台需要知道你的家在哪,这样才能给你提供更好的功能。比方说等你到家的时候帮你把灯打开、当你离开家的时候帮你把门锁上等类似的功能,这前提便是需要知道你家的位置。由于一些隐私的问题,我们不能让用户手动输入,所以我们不得不通过代码来判断你家在哪。这个解决方案相当得“naive”,就是当你连上你家的 Wi-Fi,和本地的设备连上以后,我们就判断你已经回家了,我们会向 CoreLocation 请求一个当前的位置,把这个位置当成你的家的位置。这看起来非常无害、非常简单有效的一个方案,但其实有个问题,在于:

  1. 你家的 Wi-Fi 范围可能非常广,在你距离家门口还有十几米的地方就连上了 Wi-Fi;
  2. 如果你单独请求一次位置数据的话,有时候得到的位置精度比较低,偏移比较严重。

这两个因素加起来,可能会导致我们得到你家的位置实际离你好几个街区。这个影响非常严重,总不能离我我家好几个街区的时候就自动把我家门打开了。最后怎么解决这个问题呢?当你连上你家 Wi-Fi 以后,我们一直连续获取你的当前位置,直到一定时间以后,这样我就会有你的一系列的位置。现在的问题变成了我有一堆你的数据叠,要怎么样才能够知道你家在哪。这其实是一组有很明显特征的模块数据,你很可能是离了你家一大段的时候你就径直走向了家门口,或者你开车绕了一大段再走回你家,但不管怎样它就是一串数据点点向你家,并且停在了你家,这是一个很明显的数据特征。

那么对于对模式识别,或是对机器学习有所研究的人来讲,会很明显意识到我们可以训练出一个模型。训练出来后,把你当前的数据点扔进去,根据往期的模式识别出来。这是一个很彻底的解决方案,而且也很精确,但是成本尚待考量。如果我们需要引入模式识别这一套很复杂的算法,或者一套更高级的机器学习算法,我们需要把它拉进我们的代码库里,我们还需要训练和维护它的数据,我们还需要把它的模型建立出来,这会消耗掉好几个全职工程师的几个月时间。模式识别的收益非常高,因为它能一直准确推算出你的家位置在哪,但是有没有更好的方法?就是可以没有那么准确但成本大幅度下降。

如果你上过跟机器学习相关课程的话,你会知道一个非常经典且简单的算法,叫做KMeans 算法。它是用来做聚类的,它就很适合解决这个问题。首先,我们的数据具有很明显的模式,即一系列的点连向了你家并在你家门口停下来了,所以这个假设便是你家的点是最多的。那么我们用 KMeans 算法把这些位置点做一个聚类,把它聚成几类,然后把聚类最多的那个点作为你家终点的位置,这是一个很不错的解决方案。经过我们测试,虽然它的效果不能达到 100% 准确,但是在 99% 的情况下都能够判断出你家的位置。再加上这个位置在使用时是作为 Geofence,会有一个最小 20 米的半径,略微的偏差几乎没有影响。同时,它收集数据的时间大概只有那么几分钟,收集到的数据点其实非常少,我们大概只需要迭代不到10次左右我的数据就能收敛,我甚至连提前判断退出的收敛算法都不需要,我大概只需要强制迭代几次就能得到一个很好的结果了。所以可以看到它的成本其实非常低,实在没有必要去上模式识别。

假如你平常不知道这些东西,那你可能无法做这些取舍,对于个人开发者的技术成长来说,可以多关注像掘金这样的技术社区,多去了解新兴的技术,你不必对每样新技术都很熟悉,只需要清楚它大概能解决什么问题,这样才能让你在遇到问题时想起来手上还有这样的工具,届时真需要它的时候再进行深入的了解。

最后跟大家分享一个我个人的小例子:最近也快要入夏了,我便萌生了减脂的想法,那么有一套减肥方案叫”生酮饮食“,感兴趣的可以回去了解一下。它的原理是减少碳水化合物摄入,增加蛋白质和脂肪摄入。那么一旦开始这套饮食之后,你买食物的时候会开始关注它的营养标签表,看看它到底含了多少的碳水、脂肪和蛋白质。

国内的营养标签都是统一地以”每100克“作为一份的单位,但美国这边商家比较奸诈,明明有个碳水化合物很高的食物,商家把它每份的重量降得特别小,比如只有10克,那我这一份的碳水化合物在数值上可能也就只有2克。如果你不看每份的重量,只看碳水化合物在每份当中的分量的话,你会觉得它很低,但实际上它很高。

上图是我在几个产品上截取下来的食物营养表,可以看到左边这个一份是31克,它含了3克的碳水化合物;中间这个是114克,它含了42克的碳水化合物。所以呢这两份的计数单位是不一样的。那么有了这个需求之后,我在想我能否做一个小工具,把这两个格式工整、字体统一的营养表扫下来,让小工具做一个转换,最后在我的手机上显示出来。

既然现在的需求是这样,我如何解决这个问题?显然我首先需要一个图像识别的库,把营养表中的数字和文字识别出来,我才能做下一步的处理。当时我在 GitHub 上找了一番,找到一个还不错的库。正准备写这个工具的时候,我的一位朋友提醒我这些食物营养表可能网上都有。一番搜索后,果不其然我在美国农业部的官网找到了这些公开的数据。你要做的只是把食物的标签代码输入进去,它就会返回一个网页。有了网页之后,我们要做的事情就非常简单了,直接扒一下网页的内容把需要的数据找出来就可以了,根本不需要刚才的那个库。 现在有两套工具,但是这就看你个人的喜好了。比方说我对网络方面不太了解,可能就更倾向于 OCR 识别标签。要是我比较熟悉 Python 或者 Swift,我比较喜欢处理网络请求,那可能更倾向于使用美国农业部网站。

所以你要做的就是善用搜索引擎,来了解你手上的工具,这两套工具一个走的是图像识别,另一个走的是网络请求,虽然解决的方法不同,但是最后解决的问题是同一个。平常要多阅读别的领域文章,掘金上的就很不错,多了解新的技术趋势

最后总结一下,今天主要分享的有三点

  1. 做成本的收益分析;
  2. 分析要全面;
  3. 接受不完美但是够用的解决方案;
  4. 广泛了解各项技术以备不时之需。

以上就是这些,谢谢大家。

《中美技术人才硅谷大讲堂 | JTalk 掘金线下活动第六期》 分享整理合集

Android P 新特性大起底 - 李寄超 | JTalk 第六期

阿里巴巴黑科技调度系统揭秘 - 何剑 | JTalk 第六期

如何玩转新技术 - 潘阳 | JTalk 第六期