從零開始仿寫一個抖音App——日誌和埋點以及後端初步架構

何時夕發表於2018-09-16

本文首發於微信公眾號——世界上有意思的事,搬運轉載請註明出處,否則將追究版權責任。微訊號:a1018998632,交流qq群:859640274

連載文章

本專案的 github 地址:MyTikTok

這兩週實在是太忙了,第一個需求即將上線加了一週的班,然後第二週又團建了三天導致這次的文章滯後了兩週,估計大家都以為我要棄坑了。但是其實我在團建的時候都在趕文章,讓大家久等了。本週的文章將會討論下面幾個問題,大家可以按需跳章檢視以節省寶貴的時間,本文預計閱讀時間10分鐘

  • 1.討論——總結前兩週評論中有意義的討論並給予我的解答
  • 2.日誌和埋點——討論一下日誌和埋點如何設計以及實現方案
  • 3.後端架構初步設想——討論一下未來的 app 的後端需要怎麼架構以及如何實現
  • 4.ubuntu環境初始化——將雲上的環境初始化成我所熟悉的 mac 環境(讀者如果是 windows 也可以瞭解一下,到後期的話文章會涉及到比較多的 linux 下的操作)

一、討論

討論1:專案會不會使用 kotlin?

  • 1.目前我的計劃是在基礎模組上面使用 java ,在業務模組中看情況選擇幾個模組使用 kotlin。

討論2:本系列文章是標題黨,蹭抖音的熱度

  • 1.首先明確一點為什麼我要以抖音為例子,原因就是我的公司就是開發短視訊的,技術上有類似的地方,而本公司的產品是不可能作為例子開發的,所以我就以抖音為例希望能過一遍大公司的專案開發流程和架構,不僅僅是給讀者帶來好處,對我來說也是一個很好的提升。
  • 2.當然不可否認的是抖音這個 title 給我帶來了一定的流量,也吸引了一部分人的眼球,但是我問心無愧。因為每一篇文章的內容都是我花費兩週以上的業餘時間撰寫的,內容的質量上我敢說比一般的文章要好上不少。
  • 3.有句話說得好:人紅是非多,放在文章上也是一樣。我不希望打無謂的口水仗所以:以後如果文章中有與技術和文章無關的攻擊或者詆譭的評論我會直接刪除,並且不做回覆。

二、日誌和埋點

日誌在一個專案中起著非常重要的輔助作用,它可以讓開發人員方便的定位 bug。它可以在系統上線之後讓後臺監控 app 的效能以及穩定性。他還可以收集使用者的行為資料以方便對使用者的需求進行分析。在這一節中我會分析5種不同的日誌,並講解其中幾種日誌的實現方式。

首先我先列舉一下五種不同的 log 吧。

  • 1.debug 日誌:用於開發人員本地 debug
  • 2.aop debug 日誌:用於開發人員本地 debug, 使用了 aop 可以通過簡單的註釋,對方法和類進行切片打日誌。用於打一些需要統一執行的日誌。
  • 3.網路請求 日誌:用於開發人員在本地對網路請求 debug
  • 4.本地檔案 日誌:用於記錄在 app 上線之後出現的bug,將日誌打到檔案中,可以通過一個入口讓使用者手動點選上傳日誌。
  • 5.埋點 日誌:用於記錄使用者使用 app 的資料、app 效能等等的埋點日誌,資料結構由前後端協商定義,最後會存入後端的資料庫以便進行一些資料分析。埋點的方式可以是手動的,可以是自動的。

1、debug日誌

圖1:debug日誌.png

  • 1.debug 日誌比較簡單,如圖一就是將 android 自身提供的 Log 類進行一些封裝,新增一些自己需要的特性和擴充套件,這裡就不多贅述了具體實現可以看專案中的程式碼。

2、aop日誌

圖2:aop日誌.png

  • 1.很多人在寫一些重複性的日誌的時候就會想到 aop,這種技術可以在註解的方法前後注入需要的模板程式碼。我在上一篇文章中講到了這個技術,有興趣的同學可以去看看,這裡我就簡單說一下。
  • 2.首先我們得先定義一個註解類,其可以用於註解類或者方法。註解類中可以被填入一些資訊,比如是否需要列印方法的初入參等等。
  • 3.在註解類使用了之後,我們需要用到 gradle transform。這種技術可以讓我們在編譯期間掃描所有的類,從而找被註解類所註解的方法和類。
  • 4.最後我們可以用上javassist來給找到的方法前後注入我們需要的程式碼。注意這裡的日誌可以是本地的 debug日誌,也可以是本地檔案日誌,還可以是埋點日誌。可以說 aop 日誌只是一種對另外幾種日誌的自動化封裝。

3、網路請求日誌

圖3:網路請求日誌.png

  • 1.我們在除錯網路請求的時候,除了抓包還會列印出網路請求。這個時候就如果有一種統一的形式來列印日誌的話就會方便許多。
  • 2.現在絕大部分的廠商使用的網路請求庫都是 okhttp ,所以我就直接在其上面進行日誌的定製就行了。因為專案的 http 模組還沒有進行開發,所以還沒有實現程式碼,這裡就講一講大致方案。之後在開發 http 模組的時候會順便講解具體實現。
  • 3.在講解方案之前我們需要知道,okhttp 的工作方式。如圖3中所示,在一個 okhttp 請求的過程中會經過一個個攔截器,從本地向網路請求的時候會經過一次,網路請求回來的時候又會經過一次。
  • 4.所以我們就可以新增一個日誌攔截器在兩次經過攔截器的時候列印請求的 head 和按需列印 請求的 body。注意,這裡列印可以是向 debug 日誌、本地檔案日誌、埋點日誌這三個地方列印。分別用於本地 debug、線上 debug和網路效能監控。

4、本地檔案日誌

圖4:本地檔案日誌.png

  • 1.當我們線上上遇見 bug 的時候咋辦呢?有些 crash 的日誌可以通過 bugly 這種平臺來進行回撈。但是有些奇葩的 bug 只在某些機型甚至某些使用者的手機上發生。這個時候本地檔案日誌就派上用場了。
  • 2.我們可以在開發的時候在一些關鍵的功能上手動新增上本地檔案日誌。當某個使用者報了 bug 之後我們就可以讓其通過一個入口將檔案日誌傳送到後臺,最後由開發人員進行日誌分析找到問題。
  • 3.接下來我就來通過程式碼結合上面的圖4來講解本地檔案日誌的實現方式。
  • 4.我們先來看看圖4:
    • 1.LocalFileLogger負責提供本模組對外的 api,主要功能有兩個:
      • 1.初始化和繫結LocalFileLoggerService(這是一個 service,可以通過 binder 來與外部互動)
      • 2.通過 binder 將外部的新增日誌的請求交給LocalFileLoggerService
    • 2.LocalFileLoggerService中會初始化一個 HandlerThread,本 Service 會通過 Handler 向其不斷的拋入經過高效能拼接的日誌的新增請求。
    • 3.FileLogger是負責將日誌寫入本地的類,其也初始化了一個 HandlerThread,並且自定義了一個 LoggerHandler。這個 Handler 會將 LocalFileLoggerService 拋過來的一條條日誌進行累積,當積累到了一定量的時候。發出寫入日誌的請求交給 HandlerThread執行。

圖5:LocalFileLogger程式碼1.png
圖6:LocalFileLoggerService程式碼1.png
圖7:FileLogger程式碼1.png
圖8:LoggerHandler程式碼1.png

  • 5.再來看看程式碼,我們跟著程式碼走一遍流程:
    • 1.首先在圖5中我們可以看見在 addLog 中經過一系列的呼叫,最終交給了 sLogInterface.log 這個物件是一個 Binder 物件,用於操作 LocalFileLoggerService 。
    • 2.進入到圖6,可以看見 Service 初始化了一個 HandlerThread 然後定義了一個 Handler 用於向其中拋送請求。然後在看 mBinder 的實現就是通過 Handler 向 HandlerThread 中拋送 FileLogger.addLog 的執行請求。
    • 3.進入到圖7,可以看見在 FileLogger 初始化的時候也初始化了一個 HandlerThread ,然後定義了一個 LoggerHandler 來向其中拋日誌寫入請求。FileLogger.addLog 方法中是直接傳送一個請求。
    • 4.再看圖8,LoggerHandler.add 中並不會立即向本地寫入日誌,而是會有一 LOG_CACHE_COUNT 閾值,只有超過了這個閾值才會向檔案系統中寫入日誌。

5、埋點日誌

圖9:埋點日誌.png

  • 1.埋點日誌其實和檔案日誌類似,我這裡就結合圖9簡單說一下,具體的程式碼大家可以去翻看專案
  • 2.首先還是有一個 UploadLogManager 用於給外部提供 api 以及初始化 LocalFileLoggerService。這裡比檔案系統複雜的地方就在於多了一個 UploadLogConfiguration 用於裝配一些設定。
  • 3.有了 LocalFileLoggerService 之後這裡分兩個不同的埋點日誌新增方式。
    • 1.實時埋點日誌新增:外部需要立即將當前的埋點日誌上報,此時就直接將請求傳送給 UploadLogHandler 然後交給 HandlerThread 執行,最終 通過 LogSender執行網路上報。
    • 2.非實時埋點日誌新增:這種方式是每隔一定的時間,LocalFileLoggerService 會從 UploadLogStorage 中取出一定量的日誌,合併之後再像1中一樣上報埋點。
  • 4.目前因為 Http 模組和 資料庫模組都沒有開始寫,所以 UploadLogStorage 和 LogSender 都還只是介面,但是並不影響程式碼邏輯。

三、後端架構的初步設想

雖然本專案的著重點是仿抖音 android 端 app 的開發,但是後臺方面也會有所涉及。接下來筆者會介紹一下本專案在後端方面的目標和預期達到的效果。

1、RPC

可能會有客戶端的同學對 RPC(遠端過程呼叫) 這個詞不怎麼了解,我這裡就先簡單介紹一下。

拿 Java 來說:比如我們有兩個服務 A、B 在兩個伺服器上,此時我們要在 A 上呼叫 B 的服務獲取其上的資料 Foo。那麼在 A 中可以寫成 Foo f = b.XXXService();。在這裡 Foo 是 A、B 兩個服務所定義的資料傳輸結構,b 是 B 服務所抽象出來的物件,其內部實現可以是各種網路資料互動協議,比如說 http 協議。簡單來說:RPC就是要像呼叫本地的函式一樣去調遠端函式。

現存的 RPC 框架有很多,各個大廠也都開源了自己框架,我這裡就介紹和比較一下幾個框架,最後結合本專案的需求選擇適合的框架。

  • 1.Dubbo:這個是阿里開源的一個框架,後來阿里因為種種原因把他廢棄了,最後被噹噹網維護擴充套件出了一個 Dubbox。這裡就講一講他的優劣勢吧:
    • 1.優勢:
      • 1.Dubbo 是用 java 寫的,對於 android 客戶端的開發者來說比較友好。
      • 2.Dubbo 的生態目前來說還是比較好的,筆者去年在有贊實習 java 開發的時候,用過半年的 Dubbo,感覺各種坑都有人踩過,各種庫也都比較完善。
      • 3.對於服務治理支援的比較到位。
    • 2.劣勢:
      • 1.跨平臺能力差,原生的 Dubbo 基本上沒有跨平臺能力,後面的話整合了 thrift 作為擴充套件的話就有了,不過我總感覺整合之後用起來不方便。
      • 2.以 java 作為主開發語言的話,不能快速迭代。我們專案的時間主要是要向 android 客戶端傾斜,所以需要一個能快速迭代的語言。
      • 3.序列化和反序列化的速度與其他 RPC 框架相比都不是很拔尖。
      • 4.效能較其他幾個框架差。
  • 2.Thrift:這個是 FaceBook 開源的一個框架,2007年由facebook貢獻到apache基金,是apache下的頂級專案。
    • 1.優勢:
      • 1.跨平臺能力強,支援幾乎所有的主流語言。
      • 2.效能比較好
    • 2.劣勢:
      • 1.跨平臺的語言協議寫起來比較麻煩。
      • 2.不支援服務治理
  • 3.Grpc:由 Google 開源的框架,我司目前後端也在使用這個框架
    • 1.優勢:
      • 1.跨平臺能力強、支援大部分主流開發語言
      • 2.跨平臺語言協議用的是 ProtoBuf,與我們客戶端的技術棧一致。
      • 3.效能比較好
      • 4.有我司的技術支援,當然不是官方的,不過我可以瞭解我司在這方面的技術,最後反哺到我們的專案中。
    • 2.劣勢:
      • 1.不支援服務治理

看了上面的比較我想大家心裡已經有了答案,沒錯我決定使用 grpc 做為本專案後端的 rpc 框架。然後開發的語言是 python 為主,java 為輔助,後面如果有時間的話可能會用 go 實現一個小的服務也說不定。使用這些語言的原因有下面幾點:

  • 1.首先 python 目前後臺的生態也比較成熟,用起來也比較方便快速。
  • 2.其次我們到了後面會使用 tensorflow 來訓練各種深度學習的模型,這樣的話熟練使用 python 是必須的。
  • 3.有人會問你為什麼要用幾種不同的語言來實現後端的服務呢?這不是多此一舉嗎。的確,從正常開發的角度來講是挺多餘的,但是多語言的環境在大一些的廠來講是再正常不過的事情,我的一部分目的也是為了模擬這種場景。除此之外,這種多語言的環境在我看來還是比較有意思的,想試試玩玩看。

2.微服務與服務治理

其實本來在這裡我有很多東西想說的,但是發現自己現在的能力並不能完全說好這兩個東西,怕最後會誤導大家,所以我這裡就列一下最後本專案需要完成的與這兩個目標相關的東西。

  • 1.在未來筆者預期的是會有10臺服務機器,兩臺為一組提供一類服務,一共會有五個大類的服務。
  • 2.所以第一個要實現的功能就是:服務發現註冊功能。這個功能主要是和註冊中心進行互動。
    • 1.服務提供者啟動,向註冊中心註冊自己提供的服務
    • 2.消費者啟動,向註冊中心訂閱自己需要的服務
    • 3.註冊中心返回服務提供者的列表給消費者
    • 4.消費者從服務提供者列表中,按照軟負載均衡演算法,選擇一臺發起請求
  • 3.為了瞭解和監控各個服務的情況,第二個要實現的功能就是:服務監控,即累計計算隨著時間推移各個服務被呼叫的次數。
  • 4.為了區分內外網,以及統一鑑權。需要實現的第三個功能就是:服務閘道器,所有外部請求都會經過這個閘道器,閘道器會將請求分發給內部的機器,內部機器呼叫完成之後會將結果通過閘道器返回給外部。

四、ubuntu環境初始化

不知道在我的讀者中有多少個人用的是 mac。因為我本人就是 mac 和 win 的雙系統使用者所以我深知。mac 在開發方面的好處。這一節就輕鬆一點,我演示一下如何將本地 mac 命令列環境初始化到雲上的 ubuntu 中。

1、oh my zsh

圖10:oh my zsh.png

  • 1.首先需要在XX雲中買一個機器。我買的是阿里雲,最開始的系統模板選擇 ubuntu16,然後什麼都不要裝。然後在本地用 ssh 登入雲主機。
  • 2.在本地電腦上 clone 一下我的這個庫,接下來要用到裡面的兩個指令碼檔案。ubuntu初始化
  • 3.用 scp 命令將2中的兩個檔案上傳到伺服器上分別是:ubuntu_init.sh 和 ubuntu_init_oh-my-zsh.sh。例如:scp a.jpg root@47.106.145.211:/root/a.jpg,將本地本目錄的 a.jpg 檔案上傳為雲伺服器上的/root/a.jpg檔案。
  • 4.執行ubuntu_init.sh,中間會讓你輸入密碼,最後會重啟伺服器。
  • 5.等4中重啟伺服器之後,登入伺服器然後執行ubuntu_init_oh-my-zsh.sh。如此就大功告成了。最終效果如圖10,這個終端比 ubuntu 原生的好用多了,而且還支援各種定製的外掛。
  • 6.忘了說了這個命令列是一個開源專案:oh my zsh,英語比較好的同學可以看原專案,來擴充自己的配置。

2、vim 配置

圖11:vim.png

  • 1.接下來就是 vim 的配置,其實我到現在也沒完全成功的把配置完全成功的把配置完成成功的轉移到 ubuntu 上面,所以大家看看就好。
  • 2.ubuntu初始化,這個倉庫裡 .vimrc 是 vim 的配置檔案。vim 外掛管理,這個倉庫裡是 vim 外掛庫。
  • 3.這裡其實就是為了 show 一下我的成果,對於初學者來說能學習的方面不多,對於老鳥來說也看不上我的配置。

3、docker配置

這兩週我也抽空學習了一下 docker,我的理解上 docker 就是一個方便打包重用超輕量虛擬機器。所以我們後端也會用上這個技術以方便運維。我也是剛學這東西,所以我就貼幾個我學習的網址吧!

五、尾巴

本篇文章是從零開始仿寫一個抖音App系列文章的第四篇,篇幅比較長能看到這裡的同學非常感謝你們對我的認可。從決定寫這個系列的文章開始到現在已經兩個多月了,我發現這兩個月我的成長是非常迅速的,所以接下來我還會堅持這樣寫下去。

不販賣焦慮,也不標題黨。分享一些這個世界上有意思的事情。題材包括且不限於:科幻、科學、科技、網際網路、程式設計師、計算機程式設計。下面是我的微信公眾號:世界上有意思的事,乾貨多多等你來看。

世界上有意思的事

相關文章