Go 項目 2 次架構演變,算是入了微服務的門吧!
作者:Ciusyan
https://juejin.cn/post/7203247258850312251
一、初見 Dousheng
(1)架構思路
因爲自己以前是一個Javer
,對傳統的MVC
三層架構還算比較熟悉,就巨石架構而言,使用MVC
的架構方式,模塊還算是比較清晰了。
因爲接觸了一門新的語言GoLang
,利用一些熟悉的事物過渡到不太熟悉的領域。是我們人性所擅長的。
所以在 Dousheng 的架構演變過程中,借鑑了 MVC 的思想,引入似MVC
的架構方式。
也就是:
-
控制層
(Handler)
:用於控制網絡請求。 -
業務層
(Service)
:用於處理具體業務,還有簡單的數據庫操作。 -
持久層
(Dao)
:用於進行數據庫的操作。
又因爲沒有類似 Spring 的框架來管理依賴(雖然我們的項目中引入了IoC,在後面介紹~)
,我們這裏並沒有嚴格的區分業務層和持久層。所以我們最初的架構是 "兩層半":Handler \-> Service + Dao
。
瞭解了初次架構的設計思路後,我們來看一些直觀的表達。
1、架構圖
簡單畫一幅圖來表示上面的思路就是:
這樣比較傳統的單體架構,較容易理解,就不多解釋了。直接來看看拆分後的目錄結構。
2、目錄結構
再來看看架構的目錄結構,一些輔助包,暫時不需要關注,可查看對應的文檔。現在只需要關注業務模塊的分層即可:apps包下面是業務模塊的
。
目錄結構概覽 [解讀]
DouSheng # 極簡版抖音 APP
├── apps # 所有服務模塊
│ ├── all # 統一管理所有模塊實例的註冊[驅動加載的方式]
│ ├── comment # ===評論模塊===
│ │ ├── api # 控制層(Handler)
│ │ ├── impl # 業務層(Service) + 持久層(Dao)
│ │ └── pb # interface 、model
│ ├── user # ===用戶模塊===
│ │ ├── api
│ │ ├── impl
│ │ └── pb
│ └── video # ===視頻模塊===
│ ├── api
│ ├── impl
│ └── pb
├── cmd # CLI
├── common.pb # 放置公共的protobuf文件[可抽離]
├── conf # 項目配置對象
├── docs # 項目相關文檔
├── etc # 項目具體配置
├── ioc # IoC容器[可抽離]
├── protocol # 提供協議
├── utils # 工具包
└── version # 版本信息
部分主要文件概覽 [解讀]
├── apps # 所有的業務模塊
│ ├── all # 驅動註冊所有的IOC容器實例
│ │ └── auto_register.go
│ ├── user # 以用戶模塊舉例
│ │ ├── api # 提供的 API 接口
│ │ │ ├── http.go # 使用 HTTP 的方式暴露 控制層邏輯
│ │ │ └── user.go # user服務模塊暴露的方法
│ │ ├── app.go # user模塊的結構體方法
│ │ ├── impl # user.ServerService 的實現
│ │ │ ├── dao.go # 可以看作是 持久層邏輯
│ │ │ ├── impl.go # 可以看作是 業務層邏輯
│ │ │ ├── user.go # user.ServerService 接口方法的實現
│ │ │ └── user_test.go # 此模塊測試用例【注:必寫,一般用於測試本模塊CURD的功能】
│ │ ├── pb # 此模塊的protobuf文件,裏面有(接口方法、請求model、響應model、本模塊model)
│ │ │ └── user.proto
│ │ ├── README.md # 本模塊說明
│ │ ├── user.pb.go # 利用 protoc 生成(結構體)
│ │ └── user_grpc.pb.go # 利用 protoc 生成(接口)
├── cmd # 用於啓動項目
│ ├── root.go
│ └── start.go # 啓動邏輯在這
├── common # 定義的公共的protobuf文件,可抽離
│ ├── common.pb.go
│ └── pb
│ └── common.proto
├── conf # 項目配置對象
│ ├── app.go # 此項目的配置
│ ├── config.go # 統一配置
│ ├── config_test.go
│ ├── load.go # 加載所有配置
│ ├── log.go # 日誌相關配置
│ └── mysql.go # mysql相關配置
├── etc
│ ├── dousheng.toml # 項目配置文件位置【可換成其他的,用其他庫解析】[禁止上傳github]
│ └── dousheng.toml.template # 配置文件模板[可上傳github]
├── ioc # IoC容器
│ ├── all.go # 統一所有容器
│ ├── gin.go # Gin HTTP 服務容器
│ ├── grpc.go # GRPC 服務容器
│ └── internal.go # 內部服務容器
├── Makefile # 利用Makefile管理項目[相當於一個腳手架]
├── utils # 放置一些通用的工具
│ └── md5.go
看完了apps
下的目錄結構,應該能很清晰的看出分了三個模塊(user、comment、video)
,並且每一個模塊都有自己完全獨立的 “兩層半” 架構。
既然還算清晰的對模塊進行了劃分。那爲什麼還要演變呢?
(2)遇到的問題
儘管也是分模塊開發,但是最終還是會打包並部署,還是爲單體應用。不是說不行,但是可會遇到一些問題:
-
其中最主要的問題就是,這個應用最終會太複雜,以至於任何單個開發者都可能搞不懂它。
-
應用無法擴展、可靠性低,一炸全炸,可能會出現嚴重的單點故障。
-
最終,想要實現應用的敏捷性開發和部署變得很難。
當業務體量不大的時候,單體架構可能會更受人們青睞,也不會引入更多額外的資源、技術複雜度...
但是當業務體量、用戶體量一旦增長了起來,單體架構很難穩定的抗住衝擊。再加上Go-To-Byte
也想學習一下微服務開發。
所以,我們進行了架構的第一次演變...
二、第一次演變
(1)架構思路
人類自古就有化繁爲簡、分而治之的思想,我們可以將一個複雜而龐大的業務,抽象成一個個簡單的服務,然後單獨的分開處理。我覺得這也是微服務的核心思路。
但是,在每一個單獨的服務中,我們還是保留了 MVC 的” 兩層半架構 “。再來看看一些直觀的表達:
1、架構圖
我們原先根據業務功能,對模塊進行了垂直劃分,然後在劃分出來的模塊中,進行了水平劃分,如下圖所示:
從圖中可以發現,拆分出來的每一個服務,我們都用不一樣的端口,不一樣的進程,運行了起來。對外部提供的服務,通過 HTTP 的方式暴露出去。而內部服務間的調用,就不再是通過文件路由引用了,而是通過 GRPC 協議暴露出去。
看完了架構圖,我們來看看大致的目錄結構。
2、目錄結構
總目錄結構概覽 [解讀]
還是以用戶中心、視頻服務、評論服務舉例。
DouSheng
├── dou_kit # ===簡單的分Kit公共包===
│ .....
├── user_center # ===用戶服務===
│ .....
└── video_service # ===視頻服務===
│ .....
└── comment_service # ===評論服務===
│ .....
詳細一些的結構概覽 [解讀]
這裏以用戶中心爲例,展開目錄結構:
DouSheng
├── dou_kit # ===簡單的分Kit公共包===
│ ├── conf # 配置文件
│ ├── constant # 常量
│ ├── docs.sql # 部分文檔
│ ├── exception # 統一error處理
│ └── ioc # IOC容器
├── user_center # ===用戶服務===
│ ├── apps # 包含的模塊
│ │ ├── token # token模塊
│ │ │ ├── impl
│ │ │ └── pb
│ │ ├── user # 用戶模塊
│ │ │ ├── api
│ │ │ ├── impl
│ │ │ └── pb
│ ├── client.rpc.middlerware # 用戶中心提供的客戶端
│ ├── cmd # 命令行工具
│ ├── common # 模塊內公共工具
│ │ ├── constant
│ │ └── utils
│ ├── docs # 模塊內文檔
│ │ ├── example
│ │ ├── sql
│ │ └── static.image
│ ├── etc # 用戶中心的配置文件
│ ├── protocol # 對外暴露的協議
│ └── version # 用於注入版本信息
└── video_service # ===視頻服務===
│ .....
└── comment_service # ===評論服務===
│ .....
看完了演進後的架構圖和目錄結構。其實這就是一個簡單的微服務拆分了。核心就是化繁爲簡,分而治之的思想。我們這裏僅對項目架構簡單說明,很多微服務的知識並未在這一節體現。
這樣進行簡單的拆分之後,分出了若干服務,並且服務間通過 rpc 調用,每個服務可以單獨部署、單獨編寫、本來已經解決了單體架構的很多問題了。而且是通過功能模塊劃分的,更容易理解了。那爲什麼還有一次架構演進呢?我們又遇到了什麼問題呢?
(2)遇到的問題
我們在這裏,首先遇到的問題就是:對外暴露的接口不統一,比如官方提供的測試 APP,需要配置後端接口的主機地址 + 端口。只能訪問一個進程內的接口。
而我們這樣的拆分方式,會同時啓動很多個對外暴露 HTTP 服務的進程。若想要完整的通過 APP 測試,是幾乎不可能的事情。
必行之事,何必問天。光是因爲上面所述的一個理由,我們的架構,就不得不再一次演變。還不談會遇到的其他問題。
那我們來看看是如何進行第二次架構演變的。
三、第二次演變
(1)架構思路
“沒有什麼是加一層解決不了的事情,如果有,那就兩層”。相信大家都聽過這句話。
是啊,我們遇到了上面的問題之後,嘗試加入了一層:Api Rooter
來解決這個問題。
解決了嗎?加入了這一層,我們對外暴露的 HTTP 接口,就可以統一在這一層做了。而由這一層,通過 GRPC 去調用內部服務實際的業務邏輯。
來看一些較爲直觀的表達,再繼續探討。
1、架構圖
主要呈現的是服務的拆分關係。
如圖所示,對外暴露的 HTTP 服務,全是經過Api Rooter
這一層出去的。在這一層,主要做兩件事情。
-
管理
Token
的認證 [提供 Gin 的認證中間件] -
組裝
Api
,對外提供 HTTP 服務
因爲 Token 相當於是用戶的身份憑證,以前是放在用戶中心的,現在是放在Api Rooter
的,因爲放在這裏,當有請求過來的時候,若需要校驗信息,直接調用方法即可。就不需要額外走 GRPC 去調用user_center
的方法了。
我們這裏其實並沒有太多組合 Api 的接口。我們的接口大多數是已經在內部服務組裝好的。然後在這一層直接暴露出去即可。相當於這是各個 HTTP 服務 Handler 的聚集地。在這裏聚集,然後統一暴露給外界。
值得一提的是,這一層,是通過 GRPC 去調用內部服務的,並不是通過 HTTP 協議去調用的。主要是因爲這是自定義的 Api 組合層,支持 GRPC 去調用自己的服務。
2、目錄結構
加入了 Api 這一層、把一些公共模塊更進一步的抽離出來後,現在的目錄結構是這樣的:
DouSheng
├── .github.workflows
├── api_rooter # ===簡易版網關===
│ ├── apps
│ │ ├── token # Token的 RPC Server
│ │ │ ├── impl
│ │ │ └── pb
│ │ ├── user.api # 用戶中心的HTTP接口
│ │ └── video.api # 視頻服務的HTTP接口
│ ├── client.rpc # Token的RPC Client
│ ├── common
│ │ ├── all
│ │ └── utils
│ ├── docs
│ ├── etc
│ └── protocol
├── dou_kit # ===封裝的公共庫===
│ ├── client
│ ├── cmd
│ ├── conf
│ ├── constant
│ ├── docs
│ │ ├── sql
│ │ └── static
│ ├── exception.custom
│ ├── ioc
│ ├── protocol
│ └── version
├── guidance.docs # ===項目文檔===
├── user_center # ===用戶中心===
│ ├── apps.user
│ │ ├── impl
│ │ └── pb
│ ├── client.rpc
│ ├── common
│ │ ├── all
│ │ └── utils
│ ├── docs
│ │ ├── example
│ │ ├── sql
│ │ └── static.image
│ └── etc
└── video_service # ===視頻服務===
├── apps.video
│ ├── impl
│ └── pb
├── client.rpc
├── common
│ ├── all
│ ├── pb
│ └── utils
├── docs.sql
├── etc
└── store.aliyun
在加入這一層後,對外暴露接口的方式、樣式、和端口,都統一了。這下就完事了嘛?未來真的不會出問題了嗎?
(2)可能會遇到的問題
我們現在是通過Api Rooter
來統一暴露接口的。其中最致命的就是整個 App Rooter
屬於 single point of failure
,若在這一層出現嚴重的代碼缺陷,或者流量洪峯,可能會引發集羣宕機,出現單點故障。這個故障並不是說某一個服務宕機了,而是對外提供的 HTTP 接口會崩掉。
但是由於一些原因:如項目進度、未學習的知識、技術成本.... 等問題。目前還沒有辦法再次演進。所以 Dousheng 最終的架構,暫定爲這樣了。
四、未來的設想
未來架構演進思路
既然每一個 API 服務太龐大了,那我們繼續利用大禹治水,分而治之的思想。將其拆分成多個服務獨立的網關小組。這樣就算某一服務提供的 API 宕機了,也不會導致所有服務宕機。也就是解決了單體故障的問題。
在引入一層真正的網關技術(API Geteway)
,來處理轉發用戶的請求。而且將一些橫切面的邏輯放置到這一層。比如日誌監控、安全認證等等
大致畫一幅圖,也就是這個樣子的:
至此,我們通過兩次架構的演進,相信你已經基本瞭解了 Dousheng 的架構思路。也算是入了微服務的門了~
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/WxnjyZre8MMMyOszMRNjnA