編者按:本文系亞馬遜的潘陽講師,在掘金技術社群主辦的《中美技術人才矽谷大講堂 | 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 請求一個當前的位置,把這個位置當成你的家的位置。這看起來非常無害、非常簡單有效的一個方案,但其實有個問題,在於:
- 你家的 Wi-Fi 範圍可能非常廣,在你距離家門口還有十幾米的地方就連上了 Wi-Fi;
- 如果你單獨請求一次位置資料的話,有時候得到的位置精度比較低,偏移比較嚴重。
這兩個因素加起來,可能會導致我們得到你家的位置實際離你好幾個街區。這個影響非常嚴重,總不能離我我家好幾個街區的時候就自動把我家門開啟了。最後怎麼解決這個問題呢?當你連上你家 Wi-Fi 以後,我們一直連續獲取你的當前位置,直到一定時間以後,這樣我就會有你的一系列的位置。現在的問題變成了我有一堆你的資料疊,要怎麼樣才能夠知道你家在哪。這其實是一組有很明顯特徵的模組資料,你很可能是離了你家一大段的時候你就徑直走向了家門口,或者你開車繞了一大段再走回你家,但不管怎樣它就是一串資料點點向你家,並且停在了你家,這是一個很明顯的資料特徵。
那麼對於對模式識別,或是對機器學習有所研究的人來講,會很明顯意識到我們可以訓練出一個模型。訓練出來後,把你當前的資料點扔進去,根據往期的模式識別出來。這是一個很徹底的解決方案,而且也很精確,但是成本尚待考量。如果我們需要引入模式識別這一套很複雜的演算法,或者一套更高階的機器學習演算法,我們需要把它拉進我們的程式碼庫裡,我們還需要訓練和維護它的資料,我們還需要把它的模型建立出來,這會消耗掉好幾個全職工程師的幾個月時間。模式識別的收益非常高,因為它能一直準確推算出你的家位置在哪,但是有沒有更好的方法?就是可以沒有那麼準確但成本大幅度下降。
如果你上過跟機器學習相關課程的話,你會知道一個非常經典且簡單的演算法,叫做KMeans 演算法。它是用來做聚類的,它就很適合解決這個問題。首先,我們的資料具有很明顯的模式,即一系列的點連向了你家並在你家門口停下來了,所以這個假設便是你家的點是最多的。那麼我們用 KMeans 演算法把這些位置點做一個聚類,把它聚成幾類,然後把聚類最多的那個點作為你家終點的位置,這是一個很不錯的解決方案。經過我們測試,雖然它的效果不能達到 100% 準確,但是在 99% 的情況下都能夠判斷出你家的位置。再加上這個位置在使用時是作為 Geofence,會有一個最小 20 米的半徑,略微的偏差幾乎沒有影響。同時,它收集資料的時間大概只有那麼幾分鐘,收集到的資料點其實非常少,我們大概只需要迭代不到10次左右我的資料就能收斂,我甚至連提前判斷退出的收斂演算法都不需要,我大概只需要強制迭代幾次就能得到一個很好的結果了。所以可以看到它的成本其實非常低,實在沒有必要去上模式識別。
假如你平常不知道這些東西,那你可能無法做這些取捨,對於個人開發者的技術成長來說,可以多關注像掘金這樣的技術社群,多去了解新興的技術,你不必對每樣新技術都很熟悉,只需要清楚它大概能解決什麼問題,這樣才能讓你在遇到問題時想起來手上還有這樣的工具,屆時真需要它的時候再進行深入的瞭解。
最後跟大家分享一個我個人的小例子:最近也快要入夏了,我便萌生了減脂的想法,那麼有一套減肥方案叫”生酮飲食“,感興趣的可以回去瞭解一下。它的原理是減少碳水化合物攝入,增加蛋白質和脂肪攝入。那麼一旦開始這套飲食之後,你買食物的時候會開始關注它的營養標籤表,看看它到底含了多少的碳水、脂肪和蛋白質。
國內的營養標籤都是統一地以”每100克“作為一份的單位,但美國這邊商家比較奸詐,明明有個碳水化合物很高的食物,商家把它每份的重量降得特別小,比如只有10克,那我這一份的碳水化合物在數值上可能也就只有2克。如果你不看每份的重量,只看碳水化合物在每份當中的分量的話,你會覺得它很低,但實際上它很高。
上圖是我在幾個產品上擷取下來的食物營養表,可以看到左邊這個一份是31克,它含了3克的碳水化合物;中間這個是114克,它含了42克的碳水化合物。所以呢這兩份的計數單位是不一樣的。那麼有了這個需求之後,我在想我能否做一個小工具,把這兩個格式工整、字型統一的營養表掃下來,讓小工具做一個轉換,最後在我的手機上顯示出來。
既然現在的需求是這樣,我如何解決這個問題?顯然我首先需要一個影像識別的庫,把營養表中的數字和文字識別出來,我才能做下一步的處理。當時我在 GitHub 上找了一番,找到一個還不錯的庫。正準備寫這個工具的時候,我的一位朋友提醒我這些食物營養表可能網上都有。一番搜尋後,果不其然我在美國農業部的官網找到了這些公開的資料。你要做的只是把食物的標籤程式碼輸入進去,它就會返回一個網頁。有了網頁之後,我們要做的事情就非常簡單了,直接扒一下網頁的內容把需要的資料找出來就可以了,根本不需要剛才的那個庫。 現在有兩套工具,但是這就看你個人的喜好了。比方說我對網路方面不太瞭解,可能就更傾向於 OCR 識別標籤。要是我比較熟悉 Python 或者 Swift,我比較喜歡處理網路請求,那可能更傾向於使用美國農業部網站。
所以你要做的就是善用搜尋引擎,來了解你手上的工具,這兩套工具一個走的是影像識別,另一個走的是網路請求,雖然解決的方法不同,但是最後解決的問題是同一個。平常要多閱讀別的領域文章,掘金上的就很不錯,多瞭解新的技術趨勢。
最後總結一下,今天主要分享的有三點
- 做成本的收益分析;
- 分析要全面;
- 接受不完美但是夠用的解決方案;
- 廣泛瞭解各項技術以備不時之需。
以上就是這些,謝謝大家。
《中美技術人才矽谷大講堂 | JTalk 掘金線下活動第六期》 分享整理合集
Android P 新特性大起底 - 李寄超 | JTalk 第六期