分佈式系統常見問題總結(二)html
參考:前端
微信公衆號:架構師之路node
上圖是一個典型的互聯網分層架構:nginx
客戶端層:典型調用方是browser或者APPweb
站點應用層:實現核心業務邏輯,從下游獲取數據,對上游返回html或者json數據庫
數據-緩存層:加速訪問存儲編程
數據-數據庫層:固化數據存儲json
若是實施了服務化,這個分層架構圖多是這樣:後端
中間多了一個服務層。瀏覽器
同一個層次的內部,例如端上的APP,以及web-server,也都有進行MVC分層:
view層:展示
control層:邏輯
model層:數據
能夠看到,每一個工程師骨子裏,都潛移默化的實施着分層架構。
那麼,互聯網分層架構的本質到底是什麼呢?
若是咱們仔細思考會發現,無論是跨進程的分層架構,仍是進程內的MVC分層,都是一個「數據移動」,而後「被處理」和「被呈現」的過程,歸根結底一句話:互聯網分層架構,是一個數據移動,處理,呈現的過程,其中數據移動是整個過程的核心。
如上圖所示:
數據處理和呈現要CPU計算,CPU是固定不動的:
db/service/web-server都部署在固定的集羣上
端上,無論是browser仍是APP,也有固定的CPU處理
數據是移動的:
跨進程移動:數據從數據庫和緩存裏,轉移到service層,到web-server層,到client層
同進程移動:數據從model層,轉移到control層,轉移到view層
數據要移動,因此有兩個東西很重要:
數據傳輸的格式
數據在各層次的形態
先看數據傳輸的格式,即協議很重要:
service與db/cache之間,二進制協議/文本協議是數據傳輸的載體
web-server與service之間,RPC的二進制協議是數據傳輸的載體
client和web-server之間,http協議是數據傳輸的載體
再看數據在各層次的形態,以用戶數據爲例:
db層,數據是以「行」爲單位存在的row(uid, name, age)
cache層,數據是以kv的形式存在的kv(uid -> User)
service層,會把row或者kv轉化爲對程序友好的User對象
web-server層,會把對程序友好的User對象轉化爲對http友好的json對象
client層:最終端上拿到的是json對象
結論:互聯網分層架構的本質,是數據的移動。
爲何要說這個,這將會引出「分層架構演進」的核心原則與方法:
讓上游更高效的獲取與處理數據,複用
讓下游能屏蔽數據的獲取細節,封裝
弄清楚這個原則與方法,再加上一些經驗積累,就能回答網友常常在評論中提出的這些問題了:
是否須要引入DAO層,什麼時機引入
是否須要服務化,什麼時機服務化
是否須要抽取通用中臺業務,什麼時機抽取
是否須要先後端分離,什麼時機分離
(網友們的這些提問,其實很難回答。在不瞭解業務發展階段,業務規模,數據量併發量的狀況下,妄下YES或NO的結論,自己就是不負責任的。)
更具體的分層架構演進細節,下一篇和你們細究。
總結
互聯網分層架構的本質,是數據的移動
互聯網分層架構中,數據的傳輸格式(協議)與數據在各層次的形態很重要
互聯網分層架構演進的核心原則與方法:封裝與複用
互聯網分層架構的本質,是數據的移動。
互聯網分層架構演進的核心原則:
讓上游更高效的獲取與處理數據,複用
讓下游能屏蔽數據的獲取細節,封裝
這些在上一篇《互聯網分層架構的本質》中有詳盡的描述,在實際系統架構演進過程當中,如何利用這兩個原則,對系統逐步進行分層抽象呢?我們先從後端系統開始講解。
本文主要解答兩個問題:
後端架構,何時進行DAO層的抽象
後端架構,何時進行數據服務層的抽象
一個業務系統最初的後端結構如上:
web-server層從db層獲取數據並進行加工處理
db層存儲數據
此時,web-server層如何獲取底層的數據呢?
web-server層獲取數據的一段僞代碼如上,不用糾結代碼的細節,也不用糾結不一樣編程語言與不一樣數據庫驅動的差別,其獲取數據的過程大體爲:
建立一個與數據庫的鏈接,初始化資源
根據業務拼裝一個SQL語句
經過鏈接執行SQL語句,並得到結果集
經過遊標遍歷結果集,取出每行數據,亦可從每行數據中取出屬性數據
關閉數據庫鏈接,回收資源
若是業務不復雜,這段代碼寫1次2次還能夠,但若是業務愈來愈複雜,每次都這麼獲取數據,就略顯低效了,有大量冗餘、重複、每次必寫的代碼。
如何讓數據的獲取更加高效快捷呢?
經過技術手段實現:
表與類的映射
屬性與成員的映射
SQL與函數的映射
絕大部分公司正在用的ORM,DAO等技術,就是一種分層抽象,能夠提升數據獲取的效率,屏蔽鏈接,遊標,結果集這些複雜性。
結論
當手寫代碼從DB中獲取數據,成爲通用痛點的時候,就應該抽象出DAO層,簡化數據獲取過程,提升數據獲取效率,向上遊屏蔽底層的複雜性。
抽象出DAO層以後,系統架構並不會一成不變:
隨着業務愈來愈複雜,業務系統會不斷進行垂直拆分
隨着數據量愈來愈大,數據庫會進行水平切分
隨着讀併發的愈來愈大,會增長緩存下降數據庫的壓力
因而系統架構變成了這個樣子:
業務系統垂直拆分,數據庫水平切分,緩存這些都是常見的架構優化手段。
此時,web-server層如何獲取底層的數據呢?
根據樓主的經驗,以用戶數據爲例,流程通常是這樣的:
先查緩存:先用uid嘗試從緩存獲取數據,若是cache hit,數據獲取成功,返回User實體,流程結束
肯定路由:若是cache miss,先查詢路由配置,肯定uid落在哪一個數據庫實例的哪一個庫上
查詢DB:經過DAO從對應庫獲取uid對應的數據實體User
插入緩存:將kv(uid, User)放入緩存,以便下次緩存查詢數據可以命中緩存
若是業務不復雜,這段代碼寫1次2次還能夠,但若是業務愈來愈複雜,每次都這麼獲取數據,就略顯低效了,有大量冗餘、重複、每次必寫的代碼。
特別的,業務垂直拆分紅很是多的子系統以後:
一旦底層有稍許變化,全部上游的系統都須要升級修改
子系統之間極可能出現代碼拷貝
一旦拷貝代碼,出現一個bug,多個子系統都須要升級修改
不相信業務會垂直拆分紅多個子系統?舉兩個例子:
58同城有招聘、房產、二手、二手車、黃頁等5大頭部業務,都須要訪問用戶數據
58到家有月嫂、保姆、麗人、速運、平臺等多個業務,也都須要訪問用戶數據
若是每一個子系統都須要關注緩存,分庫,讀寫分離的複雜性,調用層會瘋掉的。
如何讓數據的獲取更加高效快捷呢?
服務化,數據服務層的抽象勢在必行。
經過抽象數據服務層:
web-server層能夠經過RPC接口,像調用本地函數同樣調用遠端的數據
數據服務層,只有這一處須要關注緩存,分庫,讀寫分離這些複雜性
服務化這裏就不展開,更詳細的可參考《互聯網架構爲何要作服務化?》。
結論
當業務愈來愈複雜,垂直拆分的系統愈來愈多,數據庫實施了水平切分,數據層實施了緩存加速以後,底層數據獲取複雜性成爲通用痛點的時候,就應該抽象出數據服務層,簡化數據獲取過程,提升數據獲取效率,向上遊屏蔽底層的複雜性。
互聯網分層架構是一個頗有意思的問題,服務化的引入,並非越早越好:
請求處理時間可能會增長
運維可能會更加複雜
定位問題可能會更加麻煩
千萬別魯莽的在「微服務」大流之下,草率的進行微服務改造,看似「高大上架構」的背後,隱藏着更多並未接觸過的「大坑」。仍是那句話,架構和業務的特色和階段有關:一切脫離業務的架構設計,都是耍流氓。
這一篇先到這裏,分層架構,還有不少內容要和你們聊:
後端架構,是否須要抽取中臺業務,什麼時機抽取
後端架構,是否須要先後端分離,什麼時機分離
前端架構,如何進行分層實踐
末了,再次強調下,互聯網分層架構的本質,是數據的移動。
互聯網分層架構演進的核心原則,是讓上游更高效的獲取與處理數據,讓下游能屏蔽掉數據的複雜性獲取細節。
近期參加一些業界的技術大會,「微服務架構」的話題很是之火,也在一些場合聊過服務化架構實踐,最近幾期文章指望用通俗易懂的語言聊聊了我的對服務化以及微服務架構的理解,但願能給大夥一些啓示。若是有遺漏,也歡迎你們補充。
【服務化以前高可用架構】
在服務化以前,互聯網的高可用架構大體是這樣一個架構:
(1)用戶端是瀏覽器browser,APP客戶端
(2)後端入口是高可用的nginx集羣,用於作反向代理
(3)中間核心是高可用的web-server集羣,研發工程師主要編碼工做就是在這一層
(4)後端存儲是高可用的db集羣,數據存儲在這一層
更典型的,web-server層是經過DAO/ORM等技術來訪問數據庫的。
能夠看到,最初都是沒有服務層的,此時架構會碰到一些什麼痛點呢?
【架構痛點一:代碼處處拷貝】
舉一個最多見的業務的例子->用戶數據的訪問,絕大部分公司都有一個數據庫存儲用戶數據,各個業務都有訪問用戶數據的需求:
在有用戶服務以前,各個業務線都是本身經過DAO寫SQL訪問user庫來存取用戶數據,這無形中就致使了代碼的拷貝。
【架構痛點二:複雜性擴散】
隨着併發量的愈來愈高,用戶數據的訪問數據庫成了瓶頸,須要加入緩存來下降數據庫的讀壓力,因而架構中引入了緩存,因爲沒有統一的服務層,各個業務線都須要關注緩存的引入致使的複雜性:
對於用戶數據的寫請求,全部業務線都要升級代碼:
(1)先淘汰cache
(2)再寫數據
對於用戶數據的讀請求,全部業務線也都要升級代碼:
(1)先讀cache,命中則返回
(2)沒命中則讀數據庫
(3)再把數據放入cache
這個複雜性是典型的「業務無關」的複雜性,業務方須要被迫升級。
隨着數據量的愈來愈大,數據庫須要進行水平拆分,因而架構中又引入了分庫分表,因爲沒有統一的服務層,各個業務線都須要關注分庫分表的引入致使的複雜性:
這個複雜性也是典型的「業務無關」的複雜性,業務方須要被迫升級。
包括bug的修改,發現一個bug,多個地方都須要修改。
【架構痛點三:庫的複用與耦合】
服務化並非惟一的解決上述兩痛點的方法,抽象出統一的「庫」是最早容易想到的解決:
(1)代碼拷貝
(2)複雜性擴散
的方法。抽象出一個user.so,負責整個用戶數據的存取,從而避免代碼的拷貝。至於複雜性,也只有user.so這一個地方須要關注了。
解決了舊的問題,會引入新的問題,庫的版本維護與業務線之間代碼的耦合:
業務線A將user.so由版本1升級至版本2,若是不兼容業務線B的代碼,會致使B業務出現問題;
業務線A若是通知了業務線B升級,則是的業務線B會無端作一些「自身業務無關」的升級,很是鬱悶。固然,若是各個業務線都是拷貝了一份代碼則不存在這個問題。
【架構痛點四:SQL質量得不到保障,業務相互影響】
業務線經過DAO訪問數據庫:
本質上SQL語句仍是各個業務線拼裝的,資深的工程師寫出高質量的SQL沒啥問題,經驗沒有這麼豐富的工程師可能會寫出一些低效的SQL,假如業務線A寫了一個全表掃描的SQL,致使數據庫的CPU100%,影響的不僅是一個業務線,而是全部的業務線都會受影響。
【架構痛點五:瘋狂的DB耦合】
業務線不至訪問user數據,還會結合本身的業務訪問本身的數據:
典型的,經過join數據表來實現各自業務線的一些業務邏輯。
這樣的話,業務線A的table-user與table-A耦合在了一塊兒,業務線B的table-user與table-B耦合在了一塊兒,業務線C的table-user與table-C耦合在了一塊兒,結果就是:table-user,table-A,table-B,table-C都耦合在了一塊兒。
隨着數據量的愈來愈大,業務線ABC的數據庫是沒法垂直拆分開的,必須使用一個大庫(瘋了,一個大庫300多個業務表 =_=)。
【架構痛點六:…】
爲了解決上面的諸多問題,互聯網高可用分層架構演進的過程當中,引入了「服務層」。
以上文中的用戶業務爲例,引入了user-service,對業務線響應所用用戶數據的存取。引入服務層有什麼好處,解決什麼問題呢?
【好處一:調用方爽】
有服務層以前:業務方訪問用戶數據,須要經過DAO拼裝SQL訪問
有服務層以後:業務方經過RPC訪問用戶數據,就像調用一個本地函數同樣,很是之爽
User = UserService::GetUserById(uid);
傳入一個uid,獲得一個User實體,就像調用本地函數同樣,不須要關心序列化,網絡傳輸,後端執行,網絡傳輸,範序列化等複雜性。
【好處二:複用性,防止代碼拷貝】
這個不展開敘述,全部user數據的存取,都經過user-service來進行,代碼只此一份,不存在拷貝。
升級一處升級,bug修改一處修改。
【好處三:專一性,屏蔽底層複雜度】
在沒有服務層以前,全部業務線都須要關注緩存、分庫分表這些細節。
在有了服務層以後,只有服務層須要專一關注底層的複雜性了,向上遊屏蔽了細節。
【好處四:SQL質量獲得保障】
原來是業務向上遊直接拼接SQL訪問數據庫。
有了服務層以後,全部的SQL都是服務層提供的,業務線不能再隨心所欲了。底層服務對於穩定性的要求更好的話,能夠由更資深的工程師維護,而不是像原來SQL難以收口,難以控制。
【好處五:數據庫解耦】
原來各個業務的數據庫都混在一個大庫裏,相互join,難以拆分。
服務化以後,底層的數據庫被隔離開了,能夠很方便的拆分出來,進行擴容。
【好處六:提供有限接口,無限性能】
在服務化以前,各業務線上遊想怎麼操縱數據庫都行,遇到了性能瓶頸,各業務線容易扯皮,相互推諉。
服務化以後,服務只提供有限的通用接口,理論上服務集羣可以提供無限性能,性能出現瓶頸,服務層一處集中優化。
《互聯網分層架構的本質》簡述了兩個觀點:
互聯網分層架構的本質,是數據的移動
互聯網分層架構演進的核心原則:是讓上游更高效的獲取與處理數據,讓下游能屏蔽數據的獲取細節
《分層架構:何時抽象DAO層,何時抽象數據服務層》中的觀點是:
當手寫代碼從DB中獲取數據,成爲通用痛點的時候,就應該抽象出DAO層,簡化數據獲取過程,提升數據獲取效率,向上遊屏蔽底層的複雜性
當業務愈來愈複雜,垂直拆分的系統愈來愈多,數據庫實施了水平切分,數據層實施了緩存加速以後,底層數據獲取複雜性成爲通用痛點的時候,就應該抽象出數據服務層,簡化數據獲取過程,提升數據獲取效率,向上遊屏蔽底層的複雜性
文本將要解答的問題是:
基礎數據的訪問須要服務化,業務層是否須要服務化
若是須要服務化,何時服務化
基礎數據的訪問服務化以後,一個業務系統的後端架構如上:
web-server經過RPC接口,從基礎數據service獲取數據
基礎數據service經過DAO,從db/cache獲取數據
db/cache存儲數據
隨着時間的推移,系統架構並不會一成不變:
隨着業務愈來愈複雜,業務會不斷進行垂直拆分
隨着數據愈來愈複雜,基礎數據service也會愈來愈多
因而系統架構變成了上圖這個樣子,業務垂直拆分,有若干個基礎數據服務:
垂直業務要經過多個RPC接口訪問不一樣的基礎數據service,service共有是服務化的特徵
每一個基礎數據service訪問本身的數據存儲,數據私有也是服務化的特徵
這個架構圖中的依賴關係是否是看上去很彆扭?
基礎數據service與存儲層以前鏈接關係很清晰
業務web-server層與基礎數據service層之間的鏈接關係錯綜複雜,變成了蜘蛛網
再舉一個更具體的例子,58同城列表頁web-server如何獲取底層的數據?
首先調用商業基礎service,獲取商業廣告帖子數據,用於頂部置頂/精準的廣告帖子展現
再調用搜索基礎service,獲取天然搜索帖子數據,用於中部天然搜索帖子展現
再調用推薦基礎service,獲取推薦帖子數據,用於底部推薦帖子展現
再調用用戶基礎service,獲取用戶數據,用於右側用戶信息展現
…
若是隻有一個列表頁這麼寫還行,但若是有招聘、房產、二手、二手車、黃頁…等多個大部分是共性數據,少部分是個性數據的列表頁,每次都這麼獲取數據,就略顯低效了,有大量冗餘、重複、每次必寫的代碼。
特別的,不一樣業務上游列表頁都依賴於底層若干相同服務:
一旦一個服務RPC接口有稍許變化,全部上游的系統都須要升級修改
子系統之間極可能出現代碼拷貝
一旦拷貝代碼,出現一個bug,多個子系統都須要升級修改
如何讓數據的獲取更加高效快捷呢?
業務服務化,通用業務服務層的抽象勢在必行。
經過抽象通用業務服務層,例如58同城「通用列表服務」:
web-server層,能夠經過RPC接口,像調用本地函數同樣,調用通用業務service,一次性獲取全部通用數據
通用業務service,也能夠經過屢次調用基礎數據service提供的RPC接口,分別獲取數據,底層數據獲取的複雜性,全都屏蔽在了此處
是否是鏈接關係也看起來更清晰?
這樣的好處是:
複雜的從基礎服務獲取數據代碼,只有在通用業務service處寫了一次,沒有代碼拷貝
底層基礎數據service接口發生變化,只有通用業務service一處須要升級修改
若是有bug,無論是底層基礎數據service的bug,仍是通用業務service的bug,都只有一處須要升級修改
業務web-server獲取數據更便捷,獲取全部數據,只需一個RPC接口調用
結論:
當業務愈來愈複雜,垂直拆分的系統愈來愈多,基礎數據服務愈來愈多,底層數據獲取複雜性成爲通用痛點的時候,就應該抽象出通用業務服務,簡化數據獲取過程,提升數據獲取效率,向上遊屏蔽底層的複雜性。
最後再強調兩點:
是否須要抽象通用業務服務,和業務複雜性,以及業務發展階段有關,不可一律而論
須要抽象什麼通用業務服務,和具體業務相關
任何脫離業務的架構設計,都是耍流氓。
通用業務服務化以後,系統的典型後端結構如上:
web-server經過RPC接口,從通用業務服務獲取數據
biz-service經過RPC接口,從多個基礎數據service獲取數據
基礎數據service經過DAO,從獨立db/cache獲取數據
db/cache存儲數據
隨着時間的推移,系統架構並不會一成不變,業務愈來愈複雜,改版愈來愈多,此時web-server層雖然使用了MVC架構,但如下諸多痛點是否似曾相識?
產品追求絢麗的效果,並對設備兼容性要求高,這些需求不斷折磨着使用MVC的Java工程師們(本文以Java舉例)
無論是PC,仍是手機H5,仍是APP,應用前端展示的變化頻率遠遠大於後端邏輯的變化頻率(感謝那些喜歡作改版的產品經理),改velocity模版並非Java工程師喜歡和擅長的工做
此時,爲了緩解這些問題,通常會成立單獨的前端FE部門,來負責交互與展示的研發,其職責與後端Java工程師分離開,但痛點依然沒有徹底解決:
一點點展示的改動,須要Java工程師們從新編譯,打包,上線,重啓tomcat,效率極低
原先Java工程師負責全部MVC的研發工做,如今分爲Java和FE兩塊,須要等前端和後端都完成研發,才能一塊兒調試總體效果,不只增長了溝通成本,任何一塊出問題,均可能致使項目延期
更具體的,看一個這樣的例子,最開始產品只有PC版本,此時其系統分層架構以下:
客戶端,web-server,service,很是清晰。
隨着業務的發展,產品須要新增Mobile版本,Mobile版本和PC版本大部分業務邏輯都同樣,惟一的區別是屏幕比較小:
信息展示的條數會比較少,即調用service服務時,傳入的參數會不同
產品功能會比較少,大部分service的調用同樣,少數service不須要調用
展示,交互會有所區別
因爲工期較緊,Mobile版本的web-server通常怎麼來呢?
沒錯,把PC版本的工程拷貝一份,而後再作小量的修改:
service調用的參數有些變化
大部分service的調用同樣,少數service的調用去掉
修改展示,交互相關的代碼
業務繼續發展,產品又須要新增APP版本,APP版本和Mobile版本業務邏輯徹底相同,惟一的區別是:
Mobile版本返回html格式的數據,APP版本返回json格式的數據,而後進行本地渲染
因爲工期較緊,APP版本的web-server通常怎麼來呢?
沒錯,把Mobile版本的工程拷貝一份,而後再作小量的修改:
把拼裝html數據的代碼,修改成拼裝json數據
這麼迭代,演化,發展,架構會變成這個樣子:
端,是PC,Mobile,APP
web-server接入,是PC站,M站,APP站
服務層,通用的業務服務,以及基礎數據服務
這個架構圖中的依賴關係是否是看上去很彆扭?
端到web-server之間鏈接關係很清晰
web-server與service之間的鏈接關係變成了蜘蛛網
PC/H5/APP的web-server層大部分業務是相同的,只有少數的邏輯/展示/交互不同:
一旦一個服務RPC接口有稍許變化,全部web-server系統都須要升級修改
web-server之間存在大量代碼拷貝
一旦拷貝代碼,出現一個bug,多個子系統都須要升級修改
如何讓數據的獲取更加高效快捷,如何讓數據生產與數據展示解耦分離呢?
先後端分離的分層抽象勢在必行。
經過先後端分離分層抽象:
站點展現層,node.js,負責數據的展示與交互,由FE維護
站點數據層,web-server,負責業務邏輯與json數據接口的提供,由Java工程師維護
這樣的好處是:
複雜的業務邏輯與數據生成,只有在站點數據層處寫了一次,沒有代碼拷貝
底層service接口發生變化,只有站點數據層一處須要升級修改
底層service若是有bug,只有站點數據層一處須要升級修改
站點展示層能夠根據產品的不一樣形態,傳入不一樣的參數,調用不一樣的站點數據層接口
除此以外:
產品追求絢麗的效果,並對設備兼容性要求高,再也不困擾Java工程師,由更專業的FE對接
一點點展示的改動,再也不須要Java工程師們從新編譯,打包,上線,重啓tomcat
約定好json接口後,Java和FE分開開發,FE能夠用mock的接口自測,再也不等待一塊兒聯調
結論:
當業務愈來愈複雜,端上的產品愈來愈多,展示層的變化愈來愈快愈來愈多,站點層存在大量代碼拷貝,數據獲取複雜性成爲通用痛點的時候,就應該進行先後端分離分層抽象,簡化數據獲取過程,提升數據獲取效率,向上遊屏蔽底層的複雜性。
最後再強調兩點:
是否須要先後端分離,和業務複雜性,以及業務發展階段有關,不可一律而論
本文強調的先後端分離的思路,實際狀況下有多種實現方式,文章並無透徹展開實現細節
任何脫離業務的架構設計,都是耍流氓。
思路比細節重要。
閱讀前序文章,「分層架構設計」的背景與前因後果更加清晰: