本文首發(fā)于微信公眾號(hào)——世界上有意思的事,搬運(yùn)轉(zhuǎn)載請(qǐng)注明出處,否則將追究版權(quán)責(zé)任。微信號(hào):a1018998632,交流qq群:859640274
- 1.從零開始仿寫一個(gè)抖音app——開始
- 5.從零開始仿寫一個(gè)抖音App——app架構(gòu)更新與網(wǎng)絡(luò)層定制
- 6.從零開始仿寫一個(gè)抖音App——音視頻開篇
- 7.從零開始仿寫一個(gè)抖音App——基于FFmpeg的極簡(jiǎn)視頻播放器
- 8.從零開始仿寫一個(gè)抖音App——跨平臺(tái)視頻編輯SDK項(xiàng)目搭建
- 9.從零開始仿寫一個(gè)抖音App——Android繪制機(jī)制以及Surface家族源碼全解析
本項(xiàng)目的 github 地址:MyTikTok
這兩周實(shí)在是太忙了,第一個(gè)需求即將上線加了一周的班,然后第二周又團(tuán)建了三天導(dǎo)致這次的文章滯后了兩周,估計(jì)大家都以為我要棄坑了。但是其實(shí)我在團(tuán)建的時(shí)候都在趕文章,讓大家久等了。本周的文章將會(huì)討論下面幾個(gè)問題,大家可以按需跳章查看以節(jié)省寶貴的時(shí)間,本文預(yù)計(jì)閱讀時(shí)間10分鐘。
- 1.討論——總結(jié)前兩周評(píng)論中有意義的討論并給予我的解答
- 2.日志和埋點(diǎn)——討論一下日志和埋點(diǎn)如何設(shè)計(jì)以及實(shí)現(xiàn)方案
- 3.后端架構(gòu)初步設(shè)想——討論一下未來的 app 的后端需要怎么架構(gòu)以及如何實(shí)現(xiàn)
- 4.ubuntu環(huán)境初始化——將云上的環(huán)境初始化成我所熟悉的 mac 環(huán)境(讀者如果是 windows 也可以了解一下,到后期的話文章會(huì)涉及到比較多的 linux 下的操作)
一、討論
討論1:項(xiàng)目會(huì)不會(huì)使用 kotlin?
- 1.目前我的計(jì)劃是在基礎(chǔ)模塊上面使用 java ,在業(yè)務(wù)模塊中看情況選擇幾個(gè)模塊使用 kotlin。
討論2:本系列文章是標(biāo)題黨,蹭抖音的熱度
- 1.首先明確一點(diǎn)為什么我要以抖音為例子,原因就是我的公司就是開發(fā)短視頻的,技術(shù)上有類似的地方,而本公司的產(chǎn)品是不可能作為例子開發(fā)的,所以我就以抖音為例希望能過一遍大公司的項(xiàng)目開發(fā)流程和架構(gòu),不僅僅是給讀者帶來好處,對(duì)我來說也是一個(gè)很好的提升。
- 2.當(dāng)然不可否認(rèn)的是抖音這個(gè) title 給我?guī)砹艘欢ǖ牧髁浚参艘徊糠秩说难矍颍俏覇栃臒o愧。因?yàn)槊恳黄恼碌膬?nèi)容都是我花費(fèi)兩周以上的業(yè)余時(shí)間撰寫的,內(nèi)容的質(zhì)量上我敢說比一般的文章要好上不少。
- 3.有句話說得好:人紅是非多,放在文章上也是一樣。我不希望打無謂的口水仗所以:以后如果文章中有與技術(shù)和文章無關(guān)的攻擊或者詆毀的評(píng)論我會(huì)直接刪除,并且不做回復(fù)。
二、日志和埋點(diǎn)
日志在一個(gè)項(xiàng)目中起著非常重要的輔助作用,它可以讓開發(fā)人員方便的定位 bug。它可以在系統(tǒng)上線之后讓后臺(tái)監(jiān)控 app 的性能以及穩(wěn)定性。他還可以收集用戶的行為數(shù)據(jù)以方便對(duì)用戶的需求進(jìn)行分析。在這一節(jié)中我會(huì)分析5種不同的日志,并講解其中幾種日志的實(shí)現(xiàn)方式。
首先我先列舉一下五種不同的 log 吧。
- 1.debug 日志:用于開發(fā)人員本地 debug
- 2.aop debug 日志:用于開發(fā)人員本地 debug, 使用了 aop 可以通過簡(jiǎn)單的注釋,對(duì)方法和類進(jìn)行切片打日志。用于打一些需要統(tǒng)一執(zhí)行的日志。
- 3.網(wǎng)絡(luò)請(qǐng)求 日志:用于開發(fā)人員在本地對(duì)網(wǎng)絡(luò)請(qǐng)求 debug
- 4.本地文件 日志:用于記錄在 app 上線之后出現(xiàn)的bug,將日志打到文件中,可以通過一個(gè)入口讓用戶手動(dòng)點(diǎn)擊上傳日志。
- 5.埋點(diǎn) 日志:用于記錄用戶使用 app 的數(shù)據(jù)、app 性能等等的埋點(diǎn)日志,數(shù)據(jù)結(jié)構(gòu)由前后端協(xié)商定義,最后會(huì)存入后端的數(shù)據(jù)庫以便進(jìn)行一些數(shù)據(jù)分析。埋點(diǎn)的方式可以是手動(dòng)的,可以是自動(dòng)的。
1、debug日志
- 1.debug 日志比較簡(jiǎn)單,如圖一就是將 android 自身提供的 Log 類進(jìn)行一些封裝,添加一些自己需要的特性和擴(kuò)展,這里就不多贅述了具體實(shí)現(xiàn)可以看項(xiàng)目中的代碼。。
2、aop日志
- 1.很多人在寫一些重復(fù)性的日志的時(shí)候就會(huì)想到 aop,這種技術(shù)可以在注解的方法前后注入需要的模板代碼。我在上一篇文章中講到了這個(gè)技術(shù),有興趣的同學(xué)可以去看看,這里我就簡(jiǎn)單說一下。
- 2.首先我們得先定義一個(gè)注解類,其可以用于注解類或者方法。注解類中可以被填入一些信息,比如是否需要打印方法的初入?yún)⒌鹊取?/li>
- 3.在注解類使用了之后,我們需要用到 gradle transform。這種技術(shù)可以讓我們?cè)诰幾g期間掃描所有的類,從而找被注解類所注解的方法和類。
- 4.最后我們可以用上javassist來給找到的方法前后注入我們需要的代碼。注意這里的日志可以是本地的 debug日志,也可以是本地文件日志,還可以是埋點(diǎn)日志。可以說 aop 日志只是一種對(duì)另外幾種日志的自動(dòng)化封裝。
3、網(wǎng)絡(luò)請(qǐng)求日志
- 1.我們?cè)谡{(diào)試網(wǎng)絡(luò)請(qǐng)求的時(shí)候,除了抓包還會(huì)打印出網(wǎng)絡(luò)請(qǐng)求。這個(gè)時(shí)候就如果有一種統(tǒng)一的形式來打印日志的話就會(huì)方便許多。
- 2.現(xiàn)在絕大部分的廠商使用的網(wǎng)絡(luò)請(qǐng)求庫都是 okhttp ,所以我就直接在其上面進(jìn)行日志的定制就行了。因?yàn)轫?xiàng)目的 http 模塊還沒有進(jìn)行開發(fā),所以還沒有實(shí)現(xiàn)代碼,這里就講一講大致方案。之后在開發(fā) http 模塊的時(shí)候會(huì)順便講解具體實(shí)現(xiàn)。
- 3.在講解方案之前我們需要知道,okhttp 的工作方式。如圖3中所示,在一個(gè) okhttp 請(qǐng)求的過程中會(huì)經(jīng)過一個(gè)個(gè)攔截器,從本地向網(wǎng)絡(luò)請(qǐng)求的時(shí)候會(huì)經(jīng)過一次,網(wǎng)絡(luò)請(qǐng)求回來的時(shí)候又會(huì)經(jīng)過一次。
- 4.所以我們就可以添加一個(gè)日志攔截器在兩次經(jīng)過攔截器的時(shí)候打印請(qǐng)求的 head 和按需打印 請(qǐng)求的 body。注意,這里打印可以是向 debug 日志、本地文件日志、埋點(diǎn)日志這三個(gè)地方打印。分別用于本地 debug、線上 debug和網(wǎng)絡(luò)性能監(jiān)控。
4、本地文件日志
- 1.當(dāng)我們?cè)诰€上遇見 bug 的時(shí)候咋辦呢?有些 crash 的日志可以通過 bugly 這種平臺(tái)來進(jìn)行回?fù)啤5怯行┢孑獾?bug 只在某些機(jī)型甚至某些用戶的手機(jī)上發(fā)生。這個(gè)時(shí)候本地文件日志就派上用場(chǎng)了。
- 2.我們可以在開發(fā)的時(shí)候在一些關(guān)鍵的功能上手動(dòng)添加上本地文件日志。當(dāng)某個(gè)用戶報(bào)了 bug 之后我們就可以讓其通過一個(gè)入口將文件日志發(fā)送到后臺(tái),最后由開發(fā)人員進(jìn)行日志分析找到問題。
- 3.接下來我就來通過代碼結(jié)合上面的圖4來講解本地文件日志的實(shí)現(xiàn)方式。
- 4.我們先來看看圖4:
- 1.LocalFileLogger負(fù)責(zé)提供本模塊對(duì)外的 api,主要功能有兩個(gè):
- 1.初始化和綁定LocalFileLoggerService(這是一個(gè) service,可以通過 binder 來與外部交互)
- 2.通過 binder 將外部的添加日志的請(qǐng)求交給LocalFileLoggerService
- 2.LocalFileLoggerService中會(huì)初始化一個(gè) HandlerThread,本 Service 會(huì)通過 Handler 向其不斷的拋入經(jīng)過高性能拼接的日志的添加請(qǐng)求。
- 3.FileLogger是負(fù)責(zé)將日志寫入本地的類,其也初始化了一個(gè) HandlerThread,并且自定義了一個(gè) LoggerHandler。這個(gè) Handler 會(huì)將 LocalFileLoggerService 拋過來的一條條日志進(jìn)行累積,當(dāng)積累到了一定量的時(shí)候。發(fā)出寫入日志的請(qǐng)求交給 HandlerThread執(zhí)行。
- 1.LocalFileLogger負(fù)責(zé)提供本模塊對(duì)外的 api,主要功能有兩個(gè):
- 5.再來看看代碼,我們跟著代碼走一遍流程:
- 1.首先在圖5中我們可以看見在 addLog 中經(jīng)過一系列的調(diào)用,最終交給了 sLogInterface.log 這個(gè)對(duì)象是一個(gè) Binder 對(duì)象,用于操作 LocalFileLoggerService 。
- 2.進(jìn)入到圖6,可以看見 Service 初始化了一個(gè) HandlerThread 然后定義了一個(gè) Handler 用于向其中拋送請(qǐng)求。然后在看 mBinder 的實(shí)現(xiàn)就是通過 Handler 向 HandlerThread 中拋送 FileLogger.addLog 的執(zhí)行請(qǐng)求。
- 3.進(jìn)入到圖7,可以看見在 FileLogger 初始化的時(shí)候也初始化了一個(gè) HandlerThread ,然后定義了一個(gè) LoggerHandler 來向其中拋日志寫入請(qǐng)求。FileLogger.addLog 方法中是直接發(fā)送一個(gè)請(qǐng)求。
- 4.再看圖8,LoggerHandler.add 中并不會(huì)立即向本地寫入日志,而是會(huì)有一 LOG_CACHE_COUNT 閾值,只有超過了這個(gè)閾值才會(huì)向文件系統(tǒng)中寫入日志。
5、埋點(diǎn)日志
- 1.埋點(diǎn)日志其實(shí)和文件日志類似,我這里就結(jié)合圖9簡(jiǎn)單說一下,具體的代碼大家可以去翻看項(xiàng)目
- 2.首先還是有一個(gè) UploadLogManager 用于給外部提供 api 以及初始化 LocalFileLoggerService。這里比文件系統(tǒng)復(fù)雜的地方就在于多了一個(gè) UploadLogConfiguration 用于裝配一些設(shè)置。
- 3.有了 LocalFileLoggerService 之后這里分兩個(gè)不同的埋點(diǎn)日志添加方式。
- 1.實(shí)時(shí)埋點(diǎn)日志添加:外部需要立即將當(dāng)前的埋點(diǎn)日志上報(bào),此時(shí)就直接將請(qǐng)求發(fā)送給 UploadLogHandler 然后交給 HandlerThread 執(zhí)行,最終 通過 LogSender執(zhí)行網(wǎng)絡(luò)上報(bào)。
- 2.非實(shí)時(shí)埋點(diǎn)日志添加:這種方式是每隔一定的時(shí)間,LocalFileLoggerService 會(huì)從 UploadLogStorage 中取出一定量的日志,合并之后再像1中一樣上報(bào)埋點(diǎn)。
- 4.目前因?yàn)?Http 模塊和 數(shù)據(jù)庫模塊都沒有開始寫,所以 UploadLogStorage 和 LogSender 都還只是接口,但是并不影響代碼邏輯。
三、后端架構(gòu)的初步設(shè)想
雖然本項(xiàng)目的著重點(diǎn)是仿抖音 android 端 app 的開發(fā),但是后臺(tái)方面也會(huì)有所涉及。接下來筆者會(huì)介紹一下本項(xiàng)目在后端方面的目標(biāo)和預(yù)期達(dá)到的效果。
1、RPC
可能會(huì)有客戶端的同學(xué)對(duì) RPC(遠(yuǎn)程過程調(diào)用) 這個(gè)詞不怎么了解,我這里就先簡(jiǎn)單介紹一下。
拿 Java 來說:比如我們有兩個(gè)服務(wù) A、B 在兩個(gè)服務(wù)器上,此時(shí)我們要在 A 上調(diào)用 B 的服務(wù)獲取其上的數(shù)據(jù) Foo。那么在 A 中可以寫成 Foo f = b.XXXService();。在這里 Foo 是 A、B 兩個(gè)服務(wù)所定義的數(shù)據(jù)傳輸結(jié)構(gòu),b 是 B 服務(wù)所抽象出來的對(duì)象,其內(nèi)部實(shí)現(xiàn)可以是各種網(wǎng)絡(luò)數(shù)據(jù)交互協(xié)議,比如說 http 協(xié)議。簡(jiǎn)單來說:RPC就是要像調(diào)用本地的函數(shù)一樣去調(diào)遠(yuǎn)程函數(shù)。
現(xiàn)存的 RPC 框架有很多,各個(gè)大廠也都開源了自己框架,我這里就介紹和比較一下幾個(gè)框架,最后結(jié)合本項(xiàng)目的需求選擇適合的框架。
- 1.Dubbo:這個(gè)是阿里開源的一個(gè)框架,后來阿里因?yàn)榉N種原因把他廢棄了,最后被當(dāng)當(dāng)網(wǎng)維護(hù)擴(kuò)展出了一個(gè) Dubbox。這里就講一講他的優(yōu)劣勢(shì)吧:
- 1.優(yōu)勢(shì):
- 1.Dubbo 是用 java 寫的,對(duì)于 android 客戶端的開發(fā)者來說比較友好。
- 2.Dubbo 的生態(tài)目前來說還是比較好的,筆者去年在有贊實(shí)習(xí) java 開發(fā)的時(shí)候,用過半年的 Dubbo,感覺各種坑都有人踩過,各種庫也都比較完善。
- 3.對(duì)于服務(wù)治理支持的比較到位。
- 2.劣勢(shì):
- 1.跨平臺(tái)能力差,原生的 Dubbo 基本上沒有跨平臺(tái)能力,后面的話集成了 thrift 作為擴(kuò)展的話就有了,不過我總感覺集成之后用起來不方便。
- 2.以 java 作為主開發(fā)語言的話,不能快速迭代。我們項(xiàng)目的時(shí)間主要是要向 android 客戶端傾斜,所以需要一個(gè)能快速迭代的語言。
- 3.序列化和反序列化的速度與其他 RPC 框架相比都不是很拔尖。
- 4.性能較其他幾個(gè)框架差。
- 1.優(yōu)勢(shì):
- 2.Thrift:這個(gè)是 FaceBook 開源的一個(gè)框架,2007年由facebook貢獻(xiàn)到apache基金,是apache下的頂級(jí)項(xiàng)目。
- 1.優(yōu)勢(shì):
- 1.跨平臺(tái)能力強(qiáng),支持幾乎所有的主流語言。
- 2.性能比較好
- 2.劣勢(shì):
- 1.跨平臺(tái)的語言協(xié)議寫起來比較麻煩。
- 2.不支持服務(wù)治理
- 1.優(yōu)勢(shì):
- 3.Grpc:由 Google 開源的框架,我司目前后端也在使用這個(gè)框架
- 1.優(yōu)勢(shì):
- 1.跨平臺(tái)能力強(qiáng)、支持大部分主流開發(fā)語言
- 2.跨平臺(tái)語言協(xié)議用的是 ProtoBuf,與我們客戶端的技術(shù)棧一致。
- 3.性能比較好
- 4.有我司的技術(shù)支持,當(dāng)然不是官方的,不過我可以了解我司在這方面的技術(shù),最后反哺到我們的項(xiàng)目中。
- 2.劣勢(shì):
- 1.不支持服務(wù)治理
- 1.優(yōu)勢(shì):
看了上面的比較我想大家心里已經(jīng)有了答案,沒錯(cuò)我決定使用 grpc 做為本項(xiàng)目后端的 rpc 框架。然后開發(fā)的語言是 python 為主,java 為輔助,后面如果有時(shí)間的話可能會(huì)用 go 實(shí)現(xiàn)一個(gè)小的服務(wù)也說不定。使用這些語言的原因有下面幾點(diǎn):
- 1.首先 python 目前后臺(tái)的生態(tài)也比較成熟,用起來也比較方便快速。
- 2.其次我們到了后面會(huì)使用 tensorflow 來訓(xùn)練各種深度學(xué)習(xí)的模型,這樣的話熟練使用 python 是必須的。
- 3.有人會(huì)問你為什么要用幾種不同的語言來實(shí)現(xiàn)后端的服務(wù)呢?這不是多此一舉嗎。的確,從正常開發(fā)的角度來講是挺多余的,但是多語言的環(huán)境在大一些的廠來講是再正常不過的事情,我的一部分目的也是為了模擬這種場(chǎng)景。除此之外,這種多語言的環(huán)境在我看來還是比較有意思的,想試試玩玩看。
2.微服務(wù)與服務(wù)治理
其實(shí)本來在這里我有很多東西想說的,但是發(fā)現(xiàn)自己現(xiàn)在的能力并不能完全說好這兩個(gè)東西,怕最后會(huì)誤導(dǎo)大家,所以我這里就列一下最后本項(xiàng)目需要完成的與這兩個(gè)目標(biāo)相關(guān)的東西。
- 1.在未來筆者預(yù)期的是會(huì)有10臺(tái)服務(wù)機(jī)器,兩臺(tái)為一組提供一類服務(wù),一共會(huì)有五個(gè)大類的服務(wù)。
- 2.所以第一個(gè)要實(shí)現(xiàn)的功能就是:服務(wù)發(fā)現(xiàn)注冊(cè)功能。這個(gè)功能主要是和注冊(cè)中心進(jìn)行交互。
- 1.服務(wù)提供者啟動(dòng),向注冊(cè)中心注冊(cè)自己提供的服務(wù)
- 2.消費(fèi)者啟動(dòng),向注冊(cè)中心訂閱自己需要的服務(wù)
- 3.注冊(cè)中心返回服務(wù)提供者的列表給消費(fèi)者
- 4.消費(fèi)者從服務(wù)提供者列表中,按照軟負(fù)載均衡算法,選擇一臺(tái)發(fā)起請(qǐng)求
- 3.為了了解和監(jiān)控各個(gè)服務(wù)的情況,第二個(gè)要實(shí)現(xiàn)的功能就是:服務(wù)監(jiān)控,即累計(jì)計(jì)算隨著時(shí)間推移各個(gè)服務(wù)被調(diào)用的次數(shù)。
- 4.為了區(qū)分內(nèi)外網(wǎng),以及統(tǒng)一鑒權(quán)。需要實(shí)現(xiàn)的第三個(gè)功能就是:服務(wù)網(wǎng)關(guān),所有外部請(qǐng)求都會(huì)經(jīng)過這個(gè)網(wǎng)關(guān),網(wǎng)關(guān)會(huì)將請(qǐng)求分發(fā)給內(nèi)部的機(jī)器,內(nèi)部機(jī)器調(diào)用完成之后會(huì)將結(jié)果通過網(wǎng)關(guān)返回給外部。
四、ubuntu環(huán)境初始化
不知道在我的讀者中有多少個(gè)人用的是 mac。因?yàn)槲冶救司褪?mac 和 win 的雙系統(tǒng)用戶所以我深知。mac 在開發(fā)方面的好處。這一節(jié)就輕松一點(diǎn),我演示一下如何將本地 mac 命令行環(huán)境初始化到云上的 ubuntu 中。
1、oh my zsh
- 1.首先需要在XX云中買一個(gè)機(jī)器。我買的是阿里云,最開始的系統(tǒng)模板選擇 ubuntu16,然后什么都不要裝。然后在本地用 ssh 登錄云主機(jī)。
- 2.在本地電腦上 clone 一下我的這個(gè)庫,接下來要用到里面的兩個(gè)腳本文件。ubuntu初始化
- 3.用 scp 命令將2中的兩個(gè)文件上傳到服務(wù)器上分別是:ubuntu_init.sh 和 ubuntu_init_oh-my-zsh.sh。例如:scp a.jpg root@47.106.145.211:/root/a.jpg,將本地本目錄的 a.jpg 文件上傳為云服務(wù)器上的/root/a.jpg文件。
- 4.運(yùn)行ubuntu_init.sh,中間會(huì)讓你輸入密碼,最后會(huì)重啟服務(wù)器。
- 5.等4中重啟服務(wù)器之后,登錄服務(wù)器然后運(yùn)行ubuntu_init_oh-my-zsh.sh。如此就大功告成了。最終效果如圖10,這個(gè)終端比 ubuntu 原生的好用多了,而且還支持各種定制的插件。
- 6.忘了說了這個(gè)命令行是一個(gè)開源項(xiàng)目:oh my zsh,英語比較好的同學(xué)可以看原項(xiàng)目,來拓展自己的配置。
2、vim 配置
- 1.接下來就是 vim 的配置,其實(shí)我到現(xiàn)在也沒完全成功的把配置完全成功的把配置完成成功的轉(zhuǎn)移到 ubuntu 上面,所以大家看看就好。
- 2.ubuntu初始化,這個(gè)倉庫里 .vimrc 是 vim 的配置文件。vim 插件管理,這個(gè)倉庫里是 vim 插件庫。
- 3.這里其實(shí)就是為了 show 一下我的成果,對(duì)于初學(xué)者來說能學(xué)習(xí)的方面不多,對(duì)于老鳥來說也看不上我的配置。
3、docker配置
這兩周我也抽空學(xué)習(xí)了一下 docker,我的理解上 docker 就是一個(gè)方便打包重用超輕量虛擬機(jī)。所以我們后端也會(huì)用上這個(gè)技術(shù)以方便運(yùn)維。我也是剛學(xué)這東西,所以我就貼幾個(gè)我學(xué)習(xí)的網(wǎng)址吧!
五、尾巴
本篇文章是從零開始仿寫一個(gè)抖音App系列文章的第四篇,篇幅比較長(zhǎng)能看到這里的同學(xué)非常感謝你們對(duì)我的認(rèn)可。從決定寫這個(gè)系列的文章開始到現(xiàn)在已經(jīng)兩個(gè)多月了,我發(fā)現(xiàn)這兩個(gè)月我的成長(zhǎng)是非常迅速的,所以接下來我還會(huì)堅(jiān)持這樣寫下去。
不販賣焦慮,也不標(biāo)題黨。分享一些這個(gè)世界上有意思的事情。題材包括且不限于:科幻、科學(xué)、科技、互聯(lián)網(wǎng)、程序員、計(jì)算機(jī)編程。下面是我的微信公眾號(hào):世界上有意思的事,干貨多多等你來看。