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

丁靖–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)

分享創造快樂