這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
前言
工作這些年,先後經曆過兩家公司,分別參與過php語言架構的設計和主導過golang技術棧的落地工作,在此過程中有一些感悟和總結。我想以之前我主導的golang技術棧為線索,來陳述當時遇到的一些問題,以及分析問題和解決問題的思路。主要目的是想陳述golang技術體系在我們團隊中落地過程,分析我們在各個階段中,遇到的一些問題,並將分析問題的思路和解決問題的方法記錄下來,以便讓後來的同學瞭解golang在團隊的演化過程,吸取相關的經驗,以便在今後的系統設計和開發上少走彎路。
在系統不斷演化的過程中,有時候對架構的選型很隨意,認為能滿足現在功能就行,沒有對其擴充性和效能進行考量,是否能夠持續的支撐業務的發展——走可持續化發展路線,導致隨著業務的發展,發現當時選型有誤,但想轉又很難。那麼現在,我就來談談,我們是如何抉擇這些事情的。
我們為什麼要由php轉向golang
最初,大約是在2015年時,平台內所有的業務系統均是由php語言構成的,上線沒多久,平台的流量開始爆發性增長,並發量越來越大,當時的最快最有效最佳化手段無外乎加機器和增加php-fpm的數量,但是,受限於php本身的網路模型,終究不適合這種高並發,大流量的情境。面對這種棘手的問題,再加上當時人手有限,業務任務重等因素,於是找其它部門借了一批寫lua的外援,幫忙把一些邏輯簡單且訪問量大的介面,換成了lua,暫時扛過了晚高峰,因此,有相當長的一段時間,大部分項目是php+lua共存的一個狀態。但由於lua本身的一些局限性,不太適合做一些複雜的商務邏輯,所以最終,在業務有強烈的需求的前提下,同時伴隨著技術發展的潮流,在2015年底,我們開始選擇轉向golang。
我們怎樣由php轉向golang
由於之前團隊全部都是php棧,在golang方面的積累並不多,所以在php轉向golang的過程中,面臨了在轉型過程中都會面臨的問題:
1 用什麼架構;
2 在業務任務重,人員極其匱乏的情況下如何將php項目重構成golang。
用什麼架構
之前團隊有人仿造內部php架構開發過一個golang架構,有人提議將其直接拿過來用,有人說找個開源的如beego,gin,martini等這類流行的架構。由於之前用內部的php架構做開發,遇到過不少問題,所以我個人當時還是比較排斥使用自研的架構,主要有以下幾點原因:1 文檔少,漏洞多;2 需要投入人力去開發和維護,在當時人力極其緊缺的情況下是不現實的。另外,當時社區流行的架構也比較多,但是最終也沒有選擇那些流行的架構,主要是出於以下考慮:時間短,任務重,沒有精力去辨別各個架構的優劣,適用情境以及效能如何。萬一冒然使用一個還沒有深入瞭解的架構,線上出問題咋辦!尤其在當時系統頻繁出問題,頂著各種壓力的情況下。
雖說,我無法在短時間內選一個合適的架構,但是我還是比較確定,我們的需求是什嗎?
1 只做高效能的HTTP 介面;
2 需要完整的單元測試體系;
3 可擴充,組件化;
基於以上三點,可以發現,golang內建的特性就可以滿足這些需求。於是,我們開始決定用golang裸寫。
裸寫不是亂寫
裸寫不是亂寫。眾說周知,用架構的其中一個好處就是保證團隊代碼風格的一致性,當然,目前市面上除了beego外的大多數架構,在代碼風格上也並沒有做約束。為了保證團隊golang代碼的規範性和一致性,按照經典的分層架構和過往的經驗,我們制定了一套golang編程模版,由上向下:Router層,Service層,Dao層,還有貫穿這三層的Entity層,架構圖1所示。其中,Router層負責處理與http handler邏輯,請求參數以及response格式相關的處理工作,Service層處理商務邏輯,Dao層處理資料訪問邏輯,Entity層負責實體定義相關的邏輯,貫穿Router,Service,Dao這三層。層與層之間不直接進行耦合,高層模組不直接依賴與低層模組,它們都依賴於所定義的抽象介面。Booch曾經說過:“所有結構良好的物件導向構架都具有清晰的層次定義,每個層次通過一個定義良好的,受控的介面向外提供了一組內聚的服務”。除此之外,我們還維護了一套常用的公用組件庫,如:日誌庫,各種資料庫driver等。
圖1 分層構架
如何重構
當我們制定好編程模版後,我們就開始進行項目重構工作。由於,業務任務重,人手少,所以,重構的基本方向就是:根據業務需求,結合介面重要性進行重構。只有這樣,才能保證在業務需求不停的情況下,進行系統重構。所以,在此期間,有相當長的一段時間是處於php+golang混合編程,混合部署的狀態,這種狀態一直持續了一年。採取混合編程的思路在重構初期,可能會遇到同一份代碼,需要用golang寫一遍用php寫一遍,無疑增加了一定的工作量,當然這也是避免不了的。
最終,我們為什麼要引入go-kit
隨著業務的發展,請求量越來越大,為了應對更大的挑戰,團隊有了向grpc,thrift方向發展的趨勢,另外我們還是需要標準化一些中介軟體的使用,來保障系統的穩定性。於是,我又開始陷入了深思,不過現在團隊兵強馬壯,業務穩定,給我留下了充分思考和調研時間。這次的思考,還是像最初架構抉擇的思路一樣,首先,要弄清楚我們的需求是什嗎?
1 需要一個同時支援http,grpc,thrift等協議,具有良好擴充性的架構;
2 架構本身和業務代碼保持一種低耦合的狀態;
3 需要一套通用的middleware,使之與http,grpc等傳輸協議無關。
目前市面上流行的架構都是圍繞著http協議而展開的,包括gin,beego等。經調研,我發現go-kit能夠滿足我們的需求。 go-kit本身不是一個架構,而是一套微服務工具集。其設計思想跟我們初期golang模版制定的思想也算是不謀而合——分層設計,組件化,可擴充。go-kit的架構2所示,分為三層結構:Transport層,Endpoint層,Service層。Transport層主要負責與傳輸協議Http,Gprc,Thrift等相關的邏輯,Endpoint層主要負責request/response格式的轉換,以及公用攔截器相關的邏輯;Service層則專註於商務邏輯。go-kit除了經典的分層架構外,還在endpoint層提供了很多公用的攔截器,如log,metric,tracing,circuitbreaker,rate-limiter等,來保障業務系統的可用性。它們在設計上有一個共同特點:都是同傳輸協議無關的。在之前的一些http架構中,這些攔截器同傳輸協議是緊緊耦合在一起的。因此,藉助gokit這套工具集,我們就能很好的對transport協議,middleware進行擴充,且不會影響到業務本身的設計。
圖2 go-kit架構圖
我們怎樣將go-kit整合到我們現有的業務系統中
我們找到了心儀的開源工具後,那麼我們怎樣以較低的成本將其引入到我們業務系統中呢?之前我們有提到,我們的golang模版是分為三層:router,service和dao。而go-kit也分為三層,我們可以根據每層職責的不同進行重新組合,從上到下依次為:transport層,endpoint層,service層,dao層。這樣就能很輕易的將go-kit整合進來,當然你如果哪天因為某種原因,不想再繼續使用go-kit這套東西,直接將endpoint層和Transport層移除即可。在整合的過程中,需要注意一點:之前的代碼中router層不能包含任何商務邏輯,否則就無法整合。
圖3 架構的演化
總結
不論是我之前的一篇文章《淺談互連網業務系統設計》所講的系統設計,還是這篇文章所陳述的架構選型。我們首先需要明確的一點就是:需求是什嗎?如何在滿足需求的同時,讓架構和系統具有一定的彈性。無外乎使用經典的五大設計原則:單一職責原則,開放封閉原則,依賴倒置原則,介面隔離原則,為你的設計提供堅實的理論基礎和方向指引。另外,在做選型的時候不要盲從,別人口中好的東西,不一定適合你,只有明確自身需求後,找到適合自己的才是最好的!
參考書籍
《實現領域驅動設計》
《敏捷式軟體開發 (Agile Software Development) 原則 模式與實踐》