從零開(kāi)始仿寫(xiě)一個(gè)抖音App——日志和埋點(diǎn)以及后端初步架構(gòu)

本文首發(fā)于微信公眾號(hào)——世界上有意思的事,搬運(yùn)轉(zhuǎn)載請(qǐng)注明出處,否則將追究版權(quán)責(zé)任。微信號(hào):a1018998632,交流qq群:859640274

本項(xiàng)目的 github 地址:MyTikTok

這兩周實(shí)在是太忙了,第一個(gè)需求即將上線加了一周的班,然后第二周又團(tuán)建了三天導(dǎo)致這次的文章滯后了兩周,估計(jì)大家都以為我要棄坑了。但是其實(shí)我在團(tuán)建的時(shí)候都在趕文章,讓大家久等了。本周的文章將會(huì)討論下面幾個(gè)問(wèn)題,大家可以按需跳章查看以節(jié)省寶貴的時(shí)間,本文預(yù)計(jì)閱讀時(shí)間10分鐘。

  • 1.討論——總結(jié)前兩周評(píng)論中有意義的討論并給予我的解答
  • 2.日志和埋點(diǎn)——討論一下日志和埋點(diǎn)如何設(shè)計(jì)以及實(shí)現(xiàn)方案
  • 3.后端架構(gòu)初步設(shè)想——討論一下未來(lái)的 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)為什么我要以抖音為例子,原因就是我的公司就是開(kāi)發(fā)短視頻的,技術(shù)上有類(lèi)似的地方,而本公司的產(chǎn)品是不可能作為例子開(kāi)發(fā)的,所以我就以抖音為例希望能過(guò)一遍大公司的項(xiàng)目開(kāi)發(fā)流程和架構(gòu),不僅僅是給讀者帶來(lái)好處,對(duì)我來(lái)說(shuō)也是一個(gè)很好的提升。
  • 2.當(dāng)然不可否認(rèn)的是抖音這個(gè) title 給我?guī)?lái)了一定的流量,也吸引了一部分人的眼球,但是我問(wèn)心無(wú)愧。因?yàn)槊恳黄恼碌膬?nèi)容都是我花費(fèi)兩周以上的業(yè)余時(shí)間撰寫(xiě)的,內(nèi)容的質(zhì)量上我敢說(shuō)比一般的文章要好上不少。
  • 3.有句話說(shuō)得好:人紅是非多,放在文章上也是一樣。我不希望打無(wú)謂的口水仗所以:以后如果文章中有與技術(shù)和文章無(wú)關(guān)的攻擊或者詆毀的評(píng)論我會(huì)直接刪除,并且不做回復(fù)。

二、日志和埋點(diǎn)

日志在一個(gè)項(xiàng)目中起著非常重要的輔助作用,它可以讓開(kāi)發(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 日志:用于開(kāi)發(fā)人員本地 debug
  • 2.aop debug 日志:用于開(kāi)發(fā)人員本地 debug, 使用了 aop 可以通過(guò)簡(jiǎn)單的注釋,對(duì)方法和類(lèi)進(jìn)行切片打日志。用于打一些需要統(tǒng)一執(zhí)行的日志。
  • 3.網(wǎng)絡(luò)請(qǐng)求 日志:用于開(kāi)發(fā)人員在本地對(duì)網(wǎng)絡(luò)請(qǐng)求 debug
  • 4.本地文件 日志:用于記錄在 app 上線之后出現(xiàn)的bug,將日志打到文件中,可以通過(guò)一個(gè)入口讓用戶手動(dòng)點(diǎn)擊上傳日志。
  • 5.埋點(diǎn) 日志:用于記錄用戶使用 app 的數(shù)據(jù)、app 性能等等的埋點(diǎn)日志,數(shù)據(jù)結(jié)構(gòu)由前后端協(xié)商定義,最后會(huì)存入后端的數(shù)據(jù)庫(kù)以便進(jìn)行一些數(shù)據(jù)分析。埋點(diǎn)的方式可以是手動(dòng)的,可以是自動(dòng)的。

1、debug日志

圖1:debug日志.png
  • 1.debug 日志比較簡(jiǎn)單,如圖一就是將 android 自身提供的 Log 類(lèi)進(jìn)行一些封裝,添加一些自己需要的特性和擴(kuò)展,這里就不多贅述了具體實(shí)現(xiàn)可以看項(xiàng)目中的代碼。。

2、aop日志

圖2:aop日志.png
  • 1.很多人在寫(xiě)一些重復(fù)性的日志的時(shí)候就會(huì)想到 aop,這種技術(shù)可以在注解的方法前后注入需要的模板代碼。我在上一篇文章中講到了這個(gè)技術(shù),有興趣的同學(xué)可以去看看,這里我就簡(jiǎn)單說(shuō)一下。
  • 2.首先我們得先定義一個(gè)注解類(lèi),其可以用于注解類(lèi)或者方法。注解類(lèi)中可以被填入一些信息,比如是否需要打印方法的初入?yún)⒌鹊取?/li>
  • 3.在注解類(lèi)使用了之后,我們需要用到 gradle transform。這種技術(shù)可以讓我們?cè)诰幾g期間掃描所有的類(lèi),從而找被注解類(lèi)所注解的方法和類(lèi)。
  • 4.最后我們可以用上javassist來(lái)給找到的方法前后注入我們需要的代碼。注意這里的日志可以是本地的 debug日志,也可以是本地文件日志,還可以是埋點(diǎn)日志??梢哉f(shuō) aop 日志只是一種對(duì)另外幾種日志的自動(dòng)化封裝。

3、網(wǎng)絡(luò)請(qǐng)求日志

圖3:網(wǎng)絡(luò)請(qǐng)求日志.png
  • 1.我們?cè)谡{(diào)試網(wǎng)絡(luò)請(qǐng)求的時(shí)候,除了抓包還會(huì)打印出網(wǎng)絡(luò)請(qǐng)求。這個(gè)時(shí)候就如果有一種統(tǒng)一的形式來(lái)打印日志的話就會(huì)方便許多。
  • 2.現(xiàn)在絕大部分的廠商使用的網(wǎng)絡(luò)請(qǐng)求庫(kù)都是 okhttp ,所以我就直接在其上面進(jìn)行日志的定制就行了。因?yàn)轫?xiàng)目的 http 模塊還沒(méi)有進(jìn)行開(kāi)發(fā),所以還沒(méi)有實(shí)現(xiàn)代碼,這里就講一講大致方案。之后在開(kāi)發(fā) http 模塊的時(shí)候會(huì)順便講解具體實(shí)現(xiàn)。
  • 3.在講解方案之前我們需要知道,okhttp 的工作方式。如圖3中所示,在一個(gè) okhttp 請(qǐng)求的過(guò)程中會(huì)經(jīng)過(guò)一個(gè)個(gè)攔截器,從本地向網(wǎng)絡(luò)請(qǐng)求的時(shí)候會(huì)經(jīng)過(guò)一次,網(wǎng)絡(luò)請(qǐng)求回來(lái)的時(shí)候又會(huì)經(jīng)過(guò)一次。
  • 4.所以我們就可以添加一個(gè)日志攔截器在兩次經(jīng)過(guò)攔截器的時(shí)候打印請(qǐng)求的 head 和按需打印 請(qǐng)求的 body。注意,這里打印可以是向 debug 日志、本地文件日志、埋點(diǎn)日志這三個(gè)地方打印。分別用于本地 debug、線上 debug和網(wǎng)絡(luò)性能監(jiān)控。

4、本地文件日志

圖4:本地文件日志.png
  • 1.當(dāng)我們?cè)诰€上遇見(jiàn) bug 的時(shí)候咋辦呢?有些 crash 的日志可以通過(guò) bugly 這種平臺(tái)來(lái)進(jìn)行回?fù)?。但是有些奇葩?bug 只在某些機(jī)型甚至某些用戶的手機(jī)上發(fā)生。這個(gè)時(shí)候本地文件日志就派上用場(chǎng)了。
  • 2.我們可以在開(kāi)發(fā)的時(shí)候在一些關(guān)鍵的功能上手動(dòng)添加上本地文件日志。當(dāng)某個(gè)用戶報(bào)了 bug 之后我們就可以讓其通過(guò)一個(gè)入口將文件日志發(fā)送到后臺(tái),最后由開(kāi)發(fā)人員進(jìn)行日志分析找到問(wèn)題。
  • 3.接下來(lái)我就來(lái)通過(guò)代碼結(jié)合上面的圖4來(lái)講解本地文件日志的實(shí)現(xiàn)方式。
  • 4.我們先來(lái)看看圖4:
    • 1.LocalFileLogger負(fù)責(zé)提供本模塊對(duì)外的 api,主要功能有兩個(gè):
      • 1.初始化和綁定LocalFileLoggerService(這是一個(gè) service,可以通過(guò) binder 來(lái)與外部交互)
      • 2.通過(guò) binder 將外部的添加日志的請(qǐng)求交給LocalFileLoggerService
    • 2.LocalFileLoggerService中會(huì)初始化一個(gè) HandlerThread,本 Service 會(huì)通過(guò) Handler 向其不斷的拋入經(jīng)過(guò)高性能拼接的日志的添加請(qǐng)求。
    • 3.FileLogger是負(fù)責(zé)將日志寫(xiě)入本地的類(lèi),其也初始化了一個(gè) HandlerThread,并且自定義了一個(gè) LoggerHandler。這個(gè) Handler 會(huì)將 LocalFileLoggerService 拋過(guò)來(lái)的一條條日志進(jìn)行累積,當(dāng)積累到了一定量的時(shí)候。發(fā)出寫(xiě)入日志的請(qǐng)求交給 HandlerThread執(zhí)行。
圖5:LocalFileLogger代碼1.png

圖6:LocalFileLoggerService代碼1.png

圖7:FileLogger代碼1.png

圖8:LoggerHandler代碼1.png
  • 5.再來(lái)看看代碼,我們跟著代碼走一遍流程:
    • 1.首先在圖5中我們可以看見(jiàn)在 addLog 中經(jīng)過(guò)一系列的調(diào)用,最終交給了 sLogInterface.log 這個(gè)對(duì)象是一個(gè) Binder 對(duì)象,用于操作 LocalFileLoggerService 。
    • 2.進(jìn)入到圖6,可以看見(jiàn) Service 初始化了一個(gè) HandlerThread 然后定義了一個(gè) Handler 用于向其中拋送請(qǐng)求。然后在看 mBinder 的實(shí)現(xiàn)就是通過(guò) Handler 向 HandlerThread 中拋送 FileLogger.addLog 的執(zhí)行請(qǐng)求。
    • 3.進(jìn)入到圖7,可以看見(jiàn)在 FileLogger 初始化的時(shí)候也初始化了一個(gè) HandlerThread ,然后定義了一個(gè) LoggerHandler 來(lái)向其中拋日志寫(xiě)入請(qǐng)求。FileLogger.addLog 方法中是直接發(fā)送一個(gè)請(qǐng)求。
    • 4.再看圖8,LoggerHandler.add 中并不會(huì)立即向本地寫(xiě)入日志,而是會(huì)有一 LOG_CACHE_COUNT 閾值,只有超過(guò)了這個(gè)閾值才會(huì)向文件系統(tǒng)中寫(xiě)入日志。

5、埋點(diǎn)日志

圖9:埋點(diǎn)日志.png
  • 1.埋點(diǎn)日志其實(shí)和文件日志類(lèi)似,我這里就結(jié)合圖9簡(jiǎn)單說(shuō)一下,具體的代碼大家可以去翻看項(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í)行,最終 通過(guò) 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ù)庫(kù)模塊都沒(méi)有開(kāi)始寫(xiě),所以 UploadLogStorage 和 LogSender 都還只是接口,但是并不影響代碼邏輯。

三、后端架構(gòu)的初步設(shè)想

雖然本項(xiàng)目的著重點(diǎn)是仿抖音 android 端 app 的開(kāi)發(fā),但是后臺(tái)方面也會(huì)有所涉及。接下來(lái)筆者會(huì)介紹一下本項(xiàng)目在后端方面的目標(biāo)和預(yù)期達(dá)到的效果。

1、RPC

可能會(huì)有客戶端的同學(xué)對(duì) RPC(遠(yuǎn)程過(guò)程調(diào)用) 這個(gè)詞不怎么了解,我這里就先簡(jiǎn)單介紹一下。

拿 Java 來(lái)說(shuō):比如我們有兩個(gè)服務(wù) A、B 在兩個(gè)服務(wù)器上,此時(shí)我們要在 A 上調(diào)用 B 的服務(wù)獲取其上的數(shù)據(jù) Foo。那么在 A 中可以寫(xiě)成 Foo f = b.XXXService();。在這里 Foo 是 A、B 兩個(gè)服務(wù)所定義的數(shù)據(jù)傳輸結(jié)構(gòu),b 是 B 服務(wù)所抽象出來(lái)的對(duì)象,其內(nèi)部實(shí)現(xiàn)可以是各種網(wǎng)絡(luò)數(shù)據(jù)交互協(xié)議,比如說(shuō) http 協(xié)議。簡(jiǎn)單來(lái)說(shuō):RPC就是要像調(diào)用本地的函數(shù)一樣去調(diào)遠(yuǎn)程函數(shù)。

現(xiàn)存的 RPC 框架有很多,各個(gè)大廠也都開(kāi)源了自己框架,我這里就介紹和比較一下幾個(gè)框架,最后結(jié)合本項(xiàng)目的需求選擇適合的框架。

  • 1.Dubbo:這個(gè)是阿里開(kāi)源的一個(gè)框架,后來(lái)阿里因?yàn)榉N種原因把他廢棄了,最后被當(dāng)當(dāng)網(wǎng)維護(hù)擴(kuò)展出了一個(gè) Dubbox。這里就講一講他的優(yōu)劣勢(shì)吧:
    • 1.優(yōu)勢(shì):
      • 1.Dubbo 是用 java 寫(xiě)的,對(duì)于 android 客戶端的開(kāi)發(fā)者來(lái)說(shuō)比較友好。
      • 2.Dubbo 的生態(tài)目前來(lái)說(shuō)還是比較好的,筆者去年在有贊實(shí)習(xí) java 開(kāi)發(fā)的時(shí)候,用過(guò)半年的 Dubbo,感覺(jué)各種坑都有人踩過(guò),各種庫(kù)也都比較完善。
      • 3.對(duì)于服務(wù)治理支持的比較到位。
    • 2.劣勢(shì):
      • 1.跨平臺(tái)能力差,原生的 Dubbo 基本上沒(méi)有跨平臺(tái)能力,后面的話集成了 thrift 作為擴(kuò)展的話就有了,不過(guò)我總感覺(jué)集成之后用起來(lái)不方便。
      • 2.以 java 作為主開(kāi)發(fā)語(yǔ)言的話,不能快速迭代。我們項(xiàng)目的時(shí)間主要是要向 android 客戶端傾斜,所以需要一個(gè)能快速迭代的語(yǔ)言。
      • 3.序列化和反序列化的速度與其他 RPC 框架相比都不是很拔尖。
      • 4.性能較其他幾個(gè)框架差。
  • 2.Thrift:這個(gè)是 FaceBook 開(kāi)源的一個(gè)框架,2007年由facebook貢獻(xiàn)到apache基金,是apache下的頂級(jí)項(xiàng)目。
    • 1.優(yōu)勢(shì):
      • 1.跨平臺(tái)能力強(qiáng),支持幾乎所有的主流語(yǔ)言。
      • 2.性能比較好
    • 2.劣勢(shì):
      • 1.跨平臺(tái)的語(yǔ)言協(xié)議寫(xiě)起來(lái)比較麻煩。
      • 2.不支持服務(wù)治理
  • 3.Grpc:由 Google 開(kāi)源的框架,我司目前后端也在使用這個(gè)框架
    • 1.優(yōu)勢(shì):
      • 1.跨平臺(tái)能力強(qiáng)、支持大部分主流開(kāi)發(fā)語(yǔ)言
      • 2.跨平臺(tái)語(yǔ)言協(xié)議用的是 ProtoBuf,與我們客戶端的技術(shù)棧一致。
      • 3.性能比較好
      • 4.有我司的技術(shù)支持,當(dāng)然不是官方的,不過(guò)我可以了解我司在這方面的技術(shù),最后反哺到我們的項(xiàng)目中。
    • 2.劣勢(shì):
      • 1.不支持服務(wù)治理

看了上面的比較我想大家心里已經(jīng)有了答案,沒(méi)錯(cuò)我決定使用 grpc 做為本項(xiàng)目后端的 rpc 框架。然后開(kāi)發(fā)的語(yǔ)言是 python 為主,java 為輔助,后面如果有時(shí)間的話可能會(huì)用 go 實(shí)現(xiàn)一個(gè)小的服務(wù)也說(shuō)不定。使用這些語(yǔ)言的原因有下面幾點(diǎn):

  • 1.首先 python 目前后臺(tái)的生態(tài)也比較成熟,用起來(lái)也比較方便快速。
  • 2.其次我們到了后面會(huì)使用 tensorflow 來(lái)訓(xùn)練各種深度學(xué)習(xí)的模型,這樣的話熟練使用 python 是必須的。
  • 3.有人會(huì)問(wèn)你為什么要用幾種不同的語(yǔ)言來(lái)實(shí)現(xiàn)后端的服務(wù)呢?這不是多此一舉嗎。的確,從正常開(kāi)發(fā)的角度來(lái)講是挺多余的,但是多語(yǔ)言的環(huán)境在大一些的廠來(lái)講是再正常不過(guò)的事情,我的一部分目的也是為了模擬這種場(chǎng)景。除此之外,這種多語(yǔ)言的環(huán)境在我看來(lái)還是比較有意思的,想試試玩玩看。

2.微服務(wù)與服務(wù)治理

其實(shí)本來(lái)在這里我有很多東西想說(shuō)的,但是發(fā)現(xiàn)自己現(xiàn)在的能力并不能完全說(shuō)好這兩個(gè)東西,怕最后會(huì)誤導(dǎo)大家,所以我這里就列一下最后本項(xiàng)目需要完成的與這兩個(gè)目標(biāo)相關(guān)的東西。

  • 1.在未來(lái)筆者預(yù)期的是會(huì)有10臺(tái)服務(wù)機(jī)器,兩臺(tái)為一組提供一類(lèi)服務(wù),一共會(huì)有五個(gè)大類(lèi)的服務(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)過(guò)這個(gè)網(wǎng)關(guān),網(wǎng)關(guān)會(huì)將請(qǐng)求分發(fā)給內(nèi)部的機(jī)器,內(nèi)部機(jī)器調(diào)用完成之后會(huì)將結(jié)果通過(guò)網(wǎng)關(guān)返回給外部。

四、ubuntu環(huán)境初始化

不知道在我的讀者中有多少個(gè)人用的是 mac。因?yàn)槲冶救司褪?mac 和 win 的雙系統(tǒng)用戶所以我深知。mac 在開(kāi)發(fā)方面的好處。這一節(jié)就輕松一點(diǎn),我演示一下如何將本地 mac 命令行環(huán)境初始化到云上的 ubuntu 中。

1、oh my zsh

圖10:oh my zsh.png
  • 1.首先需要在XX云中買(mǎi)一個(gè)機(jī)器。我買(mǎi)的是阿里云,最開(kāi)始的系統(tǒng)模板選擇 ubuntu16,然后什么都不要裝。然后在本地用 ssh 登錄云主機(jī)。
  • 2.在本地電腦上 clone 一下我的這個(gè)庫(kù),接下來(lái)要用到里面的兩個(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.忘了說(shuō)了這個(gè)命令行是一個(gè)開(kāi)源項(xiàng)目:oh my zsh,英語(yǔ)比較好的同學(xué)可以看原項(xiàng)目,來(lái)拓展自己的配置。

2、vim 配置

圖11:vim.png
  • 1.接下來(lái)就是 vim 的配置,其實(shí)我到現(xiàn)在也沒(méi)完全成功的把配置完全成功的把配置完成成功的轉(zhuǎn)移到 ubuntu 上面,所以大家看看就好。
  • 2.ubuntu初始化,這個(gè)倉(cāng)庫(kù)里 .vimrc 是 vim 的配置文件。vim 插件管理,這個(gè)倉(cāng)庫(kù)里是 vim 插件庫(kù)。
  • 3.這里其實(shí)就是為了 show 一下我的成果,對(duì)于初學(xué)者來(lái)說(shuō)能學(xué)習(xí)的方面不多,對(duì)于老鳥(niǎo)來(lái)說(shuō)也看不上我的配置。

3、docker配置

這兩周我也抽空學(xué)習(xí)了一下 docker,我的理解上 docker 就是一個(gè)方便打包重用超輕量虛擬機(jī)。所以我們后端也會(huì)用上這個(gè)技術(shù)以方便運(yùn)維。我也是剛學(xué)這東西,所以我就貼幾個(gè)我學(xué)習(xí)的網(wǎng)址吧!

五、尾巴

本篇文章是從零開(kāi)始仿寫(xiě)一個(gè)抖音App系列文章的第四篇,篇幅比較長(zhǎng)能看到這里的同學(xué)非常感謝你們對(duì)我的認(rèn)可。從決定寫(xiě)這個(gè)系列的文章開(kāi)始到現(xiàn)在已經(jīng)兩個(gè)多月了,我發(fā)現(xiàn)這兩個(gè)月我的成長(zhǎng)是非常迅速的,所以接下來(lái)我還會(huì)堅(jiān)持這樣寫(xiě)下去。

不販賣(mài)焦慮,也不標(biāo)題黨。分享一些這個(gè)世界上有意思的事情。題材包括且不限于:科幻、科學(xué)、科技、互聯(lián)網(wǎng)、程序員、計(jì)算機(jī)編程。下面是我的微信公眾號(hào):世界上有意思的事,干貨多多等你來(lái)看。

世界上有意思的事

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容