歡迎光臨
每天分享高質量文章

丁靖–go工程效率實踐

 丁靖:2007年開始PHP, PECL開發,Swoole開發組成員;2015年開始Golang,從事儲存,圖像處理,高併發服務開發;目前是貝殼找房基礎服務負責人


前言  

       今天我分享的主題是go的工程效率實踐,做一個簡單的自我介紹,我叫丁靖,8年PHP、PECL開發,2015年開始接觸go,現在從事儲存和圖象處理以及高併發服務開發,目前是貝殼找房基礎服務負責人。

       先做一個鋪墊,分享一下我對技術團隊價值的理解,還有我們有哪些效率問題,以及我們是怎麼解決的。


技術價值

 技術團隊的價值

      首先看技術的價值,商業社會,在商言商,技術團隊扮演了什麼角色?我總結了三點:

      第一點,做業務流程的自動化,可以提升業務的效率,在很多公司IT方面都是這個作用;第二點,可以讓老闆的想法快速落地,有一個指標,從老闆一拍腦袋到專案上線的時間,時間越短表示我們的技術價值越大;第三,技術創新,改變世界。說的範圍有點大,但是我們周圍都在發生,比如說我們貝殼找房的VR看房。


技術從本質上講都是解決效率問題

      我覺得技術在本質上都是要解決效率問題,上面提到的業務流程提效、想法的快速落地以及技術創新,都是為了提高大家的工作效率和生活效率,比如在找房的過程中提升看房效率等等。


有哪些效率問題

       接下來我講一下開發過程中存在的效率問題,我總結了以下幾點,可能降低我們開發的效率:

       首先是我們怎麼去組織工程?golang很鬆散,很靈活,這可能會讓我們不知道怎麼做,或者看不懂別人的代碼;第二點是代碼復用,代碼復用率越高,開發效率越高;第三點問題定位,這個效率越高,故障恢復的效率越高;最後是接口設計,接口定義的通用性,權限設計的抽象性也會直接影響後期維護成本。    

      工程效率方面:介紹模塊化、生命周期管理 、依賴註入、系統分層、現在使用的目錄結構和包管理;代碼復用:封裝了資料庫、快取等基礎組建、解決配置和路由問題;問題定位:主要介紹我們怎麼做日誌和監控;接口設計:簡單介紹我們的接口抽象原則以及權限管理的方法。

工程組織


生命周期管理

      首先從工程組織開始,PHP大概也是這樣處理生命周期。不同的是,PHP只需要處理接收請求之後的邏輯,而Go還要關註怎麼接收請求。大致分這麼幾個階段:第一,行程啟動,加載配置,創建一些臨時檔案,記下行程PID以方便行程管理,這些做完之後才開始行程請求。請求開始,並行的多個請求都要開始,每個請求都要分配記憶體,申請一些資源,比如創建資料庫連接,做權限驗證,簽名校驗,之後才到我們後面真正的業務邏輯。這樣可以更好地組織工程。請求結束之後把申請的記憶體和資源釋放掉。行程結束時,刪除創建的臨時檔案等。

模塊化     

      我剛剛說到生命周期,現在開始介紹模塊化。基於這個生命周期,我們把很多建立的業務模型模塊化,並和生命周期對應上。比如說做權限驗證,有一種做法是在每個請求之前都調鑒權方法做權限驗證,有了生命周期,可以定義一個權限模塊,並定義請求開始時回呼函式來實現。這就實現了我們常說的高內聚低耦合,把權限驗證的邏輯內聚到模塊里,而與其他邏輯低耦合。

依賴註入   

      下麵一個問題,對於依賴註入可能有理解偏差和模糊的地方,這裡舉個例子,比如要組織一個工程,可能需要拆分成很多包,不同的包之間存在互相的呼叫。例如我現在有兩個模塊,互相之間存在呼叫關係,很多的做法是定義一個全域性的容器(container),把各個包實體化後放到這個容器裡面,運行過程中就可以呼叫到這些模塊了。依然註入能自動化完成這個工作。我們可以在程式啟動時把模塊一二三實體化後放到框架裡面,通過反射把實體註入到需要的模塊中。這裡還有個細節存在問題,如果模塊一先初始化,那模塊一初始化時模塊二和三沒有分配記憶體,無法在模塊一初始化時使用模塊二和三。因此依賴註入要分析他們之間的關係,模塊三應該最先初始化,然後是模塊二,最後是模塊一。我們實現的依賴註入需要先註冊,然後對所有註冊的模塊進行排序,利用反射知道模塊二依賴模塊三,模塊一也依賴模塊三,於是就排成這樣的序,先對模塊三分配記憶體,再把模塊三註入到模塊二裡面去。後面邏輯就可以直接呼叫了,省去了很多手工勞動,依賴註入就是解決這個問題。

系統分層  

       接下來是系統分層,我們最初的想法是結合模塊和生命周期,就可以比較好地組織代碼了,但是實際上發現還是不行,特別是不同的人可能習慣不一樣,比如有些人更習慣MVC,於是我們也支持了MVC的代碼組織方式。

包管理

      包管理,取用vendor屬性之後各種包管理都出現了,我們選擇了dep,理由是支持私有git倉庫、本地代碼快取、忽略_test檔案等等特性。忽略_test檔案這點做得比較細,因為依賴包裡面的這些測試檔案,對使用來說沒什麼意義,忽略它們可以更快地拉取。還有一點就是Dep專案一直在更新,一直在跟進修複問題。現在官方又出來vgo,但是沒有一些文件,所以我們沒有深入瞭解。

工程目錄  

      工程目錄,vendor還在實驗階段的時期,我們把專案目錄作為GOPATH,go1.7之後改為了vendor 方式管理代碼倉庫。編譯統一使用Makefile進行,一個專案中可能存在多個service,比如這裡就可以使用make app-service來編譯app-service,編譯用到了 go build。這裡面有個坑,編譯時加-race引數可以做動態併發檢查,但是對併發有限制,當併發達到域值就會自動退出。所以我們做了區分,DEBUG環境編譯時才加這個引數。make test執行測試。這樣基本上go編譯的幾個環節可以比較完好的映射到 make 命令上。

代碼復用


基礎組建    

      代碼復用,首先挑選了一些優秀的開源專案放到框架里,比如 gorm、redigo 等等。但我們並不是按照操作資源來封裝,而是面向功能封裝。這樣封裝是為了通過配置改變底層操作的資源,通過接口抽象出各種操作而不直接呼叫原生的,以便擴展。我們封裝了資料庫、佇列、快取、會話等功能。總結一下這樣做主要的目:1. 為了方便擴展 2. 接口更穩定

配置       

      接下來介紹配置,因為使用了go語言,可以做更深層次優化,現在我們配置的邏輯是這樣的:三個資料源:環境變數、配置檔案、配置服務,最終都快取到記憶體中,同時監聽配置服務h和配置檔案的變化,發現變化就拉到記憶體里快取起來。應用層只從記憶體獲取配置,配置又分業務配置和系統配置,業務配置為什麼要分開呢?因為業務配置的資料結構更複雜,有樹狀結構和網絡結構,但系統配置相對固定,基本都是key-value的結構。

請求路由  

      路由:可分請求路由和命令列路由。命令列路由有什麼用呢?比如服務啟動時候,運維工具有更多引數,不同引數有不同作用。請求路由,我們在原生路由的基礎上做了擴展,原生路由是指定一個回呼函式(handler),我們可以把 handler 拆成更多小handler,可以把一個過程拆分成很多小 handler,有的 handler 只負責記錄訪問日誌,有的handler負責加 recover 把做異常恢復,捕獲異常後打一條日誌,還有可以加權限驗證的 handler,每個 handler 一旦失敗就會走終止執行 handler 。再結合剛剛介紹的模塊化,可以幫助我們更好的組織代碼,構建可擴展易維護的專案。

問題定位


日誌   

 

      問題定位:首先分享一下我對日誌的觀點,總結下來有這幾個日誌:訪問日誌、錯誤日誌、系統錯誤日誌、除錯日誌。訪問日誌,可以搜集很多信息,可以參考常見nginx訪問日誌格式,能有效的幫助我們分析系統運行狀態;錯誤日誌,正常的請求響應,沒有錯誤日誌,主要目的是快速定位異常請求的問題;系統錯誤日誌,在系統崩潰(panic)recover時會打,方便追溯問題,這屬於比較嚴重的錯誤,所以獨立出來沒有和錯誤日誌放在一起。同時所有日誌都可以輸出到不同的儲存引擎里,比如在Kafka、檔案、Hdfs。

日誌格式

      日誌格式,首先介紹一個日誌組件,有些同學習慣把錯誤日誌分開打印,這樣不太容易檢索,比較浪費儲存空間。我們的做法是當發生錯誤時,把呼叫棧拼接起來打印一條日誌。所以一個請求會產生一條訪問日誌,如果出錯則只會產生一條錯誤日誌,這條錯誤日誌需要包含能幫助你快速定位錯誤的信息。另外在處理邏輯中可以多打一些除錯日誌,提高在開發階段的除錯效率,在線上把除錯日誌大打印開關關閉。這是我們定的日誌格式,通過request_id能夠很快過濾出某個請求執行過程中的所有日誌。另外我們把所有日誌都統一輸出到一個檔案里,不管錯誤日誌還是訪問日誌都以相對統一的格式輸出,這樣能方便我們做日誌切分。日誌裡面需包含幾個關鍵信息:時間、錯誤級別、來源請求ID、請求ID、日誌信息、背景關係。通過請求ID可以在服務間做鏈路追蹤。

監控

      後面的是監控,前面的日誌搞定了,通過切割和分析,最後輸出到儲存裡面,當然就很容易了,方法很多,這裡就不展開講了。

接口設計


接口抽象      

      最後介紹一下我們的接口設計。接口抽象,將所有操作都能夠抽象到一個資源上,如果能把大量操作都能抽象到增刪改查上,那這個建模就比較成功了。但是不可避免在某些前端界面上不能按照這個方法抽象,比如說登錄等等,所以前端的我們管不了了,希望我們的後端接口是穩定的。這就是Restful的設計思想,1.資源 2. 表現層 3. 狀態轉換。這裡面映射到接口裡面,資源對應 URI,表現層對應接口傳回內容,狀態轉換對應我們的更新和刪除、獲取,操作資源使它的狀態變換。

如何確保身份合法

      設計接口用不用Restful有什麼區別?我們不光要把接口實現,還要控制權限。Restful接口做權限控制就非常簡單了,要做權限首先要獲取呼叫接口的呼叫者身份,身份如何標識?我們參照AWS IAM的設計通過AK來標識。那麼如何保證AK合法呢?這就涉及到簽名,也是接口設計的一部分。簽名通過,下一步才是鑒權。

權限配置

       同樣參考AWS IAM,通過Policy來設置權限。一個接口能否被訪問到,是應用在該呼叫者身上的很多條Policy綜合作用的結果,每個Policy配置包含能訪問什麼資源,能做什麼操作,這個操作是允許還是不允許。把所有規則遍歷一遍,就根據這個流程圖知道這個資源到底能不能被執行。實現起來非常簡單,僅需要幾十行代碼。剩餘工作只要給每個呼叫者添加Policy,但是前提是接口能通過資源來標識。 

赞(0)

分享創造快樂