秒殺系統架構優化思路
一、秒殺業務爲什麼難做
1)im系統,例如qq或者微博,每個人都讀自己的數據(好友列表、羣列表、個人信息);
2)微博系統,每個人讀你關注的人的數據,一個人讀多個人的數據;
3)秒殺系統,庫存只有一份,所有人會在集中的時間讀和寫這些數據,多個人讀一個數據。
例如:小米手機每週二的秒殺,可能手機只有1萬部,但瞬時進入的流量可能是幾百幾千萬。
又例如:12306搶票,票是有限的,庫存一份,瞬時流量非常多,都讀相同的庫存。讀寫衝突,鎖非常嚴重,這是秒殺業務難的地方。那我們怎麼優化秒殺業務的架構呢?
二、優化方向
優化方向有兩個(今天就講這兩個點):
(1)將請求儘量攔截在系統上游(不要讓鎖衝突落到數據庫上去)。傳統秒殺系統之所以掛,請求都壓倒了後端數據層,數據讀寫鎖衝突嚴重,併發高響應慢,幾乎所有請求都超時,流量雖大,下單成功的有效流量甚小。以12306爲例,一趟火車其實只有2000張票,200w個人來買,基本沒有人能買成功,請求有效率爲0。
(2)充分利用緩存,秒殺買票,這是一個典型的讀多些少的應用場景,大部分請求是車次查詢,票查詢,下單和支付纔是寫請求。一趟火車其實只有2000張票,200w個人來買,最多2000個人下單成功,其他人都是查詢庫存,寫比例只有0.1%,讀比例佔99.9%,非常適合使用緩存來優化。好,後續講講怎麼個“將請求儘量攔截在系統上游”法,以及怎麼個“緩存”法,講講細節。
三、常見秒殺架構
常見的站點架構基本是這樣的(絕對不畫忽悠類的架構圖)
(1)瀏覽器端,最上層,會執行到一些JS代碼
(2)站點層,這一層會訪問後端數據,拼html頁面返回給瀏覽器
(3)服務層,向上遊屏蔽底層數據細節,提供數據訪問
(4)數據層,最終的庫存是存在這裏的,mysql是一個典型(當然還有會緩存)
這個圖雖然簡單,但能形象的說明大流量高併發的秒殺業務架構,大家要記得這一張圖。
後面細細解析各個層級怎麼優化。
四、各層次優化細節
第一層,客戶端怎麼優化(瀏覽器層,APP層)
問大家一個問題,大家都玩過微信的搖一搖搶紅包對吧,每次搖一搖,就會往後端發送請求麼?回顧我們下單搶票的場景,點擊了“查詢”按鈕之後,系統那個卡呀,進度條漲的慢呀,作爲用戶,我會不自覺的再去點擊“查詢”,對麼?繼續點,繼續點,點點點。。。有用麼?平白無故的增加了系統負載,一個用戶點5次,80%的請求是這麼多出來的,怎麼整?
(a)產品層面,用戶點擊“查詢”或者“購票”後,按鈕置灰,禁止用戶重複提交請求;
(b)JS層面,限制用戶在x秒之內只能提交一次請求;
APP層面,可以做類似的事情,雖然你瘋狂的在搖微信,其實x秒才向後端發起一次請求。這就是所謂的“將請求儘量攔截在系統上游”,越上游越好,瀏覽器層,APP層就給攔住,這樣就能擋住80%+的請求,這種辦法只能攔住普通用戶(但99%的用戶是普通用戶)對於羣內的高端程序員是攔不住的。firebug一抓包,http長啥樣都知道,js是萬萬攔不住程序員寫for循環,調用http接口的,這部分請求怎麼處理?
第二層,站點層面的請求攔截
怎麼攔截?怎麼防止程序員寫for循環調用,有去重依據麼?ip?cookie-id?…想複雜了,這類業務都需要登錄,用uid即可。在站點層面,對uid進行請求計數和去重,甚至不需要統一存儲計數,直接站點層內存存儲(這樣計數會不準,但最簡單)。一個uid,5秒只准透過1個請求,這樣又能攔住99%的for循環請求。
5s只透過一個請求,其餘的請求怎麼辦?緩存,頁面緩存,同一個uid,限制訪問頻度,做頁面緩存,x秒內到達站點層的請求,均返回同一頁面。同一個item的查詢,例如車次,做頁面緩存,x秒內到達站點層的請求,均返回同一頁面。如此限流,既能保證用戶有良好的用戶體驗(沒有返回404)又能保證系統的健壯性(利用頁面緩存,把請求攔截在站點層了)。
頁面緩存不一定要保證所有站點返回一致的頁面,直接放在每個站點的內存也是可以的。優點是簡單,壞處是http請求落到不同的站點,返回的車票數據可能不一樣,這是站點層的請求攔截與緩存優化。
好,這個方式攔住了寫for循環發http請求的程序員,有些高端程序員(黑客)控制了10w個肉雞,手裏有10w個uid,同時發請求(先不考慮實名制的問題,小米搶手機不需要實名制),這下怎麼辦,站點層按照uid限流攔不住了。
第三層 服務層來攔截(反正就是不要讓請求落到數據庫上去)
服務層怎麼攔截?大哥,我是服務層,我清楚的知道小米只有1萬部手機,我清楚的知道一列火車只有2000張車票,我透10w個請求去數據庫有什麼意義呢?沒錯,請求隊列!
對於寫請求,做請求隊列,每次只透有限的寫請求去數據層(下訂單,支付這樣的寫業務)
1w部手機,只透1w個下單請求去db
3k張火車票,只透3k個下單請求去db
如果均成功再放下一批,如果庫存不夠則隊列裏的寫請求全部返回“已售完”。
對於讀請求,怎麼優化?cache抗,不管是memcached還是redis,單機抗個每秒10w應該都是沒什麼問題的。如此限流,只有非常少的寫請求,和非常少的讀緩存mis的請求會透到數據層去,又有99.9%的請求被攔住了。
當然,還有業務規則上的一些優化。回想12306所做的,分時分段售票,原來統一10點賣票,現在8點,8點半,9點,...每隔半個小時放出一批:將流量攤勻。
其次,數據粒度的優化:你去購票,對於餘票查詢這個業務,票剩了58張,還是26張,你真的關注麼,其實我們只關心有票和無票?流量大的時候,做一個粗粒度的“有票”“無票”緩存即可。
第三,一些業務邏輯的異步:例如下單業務與 支付業務的分離。這些優化都是結合 業務 來的,我之前分享過一個觀點“一切脫離業務的架構設計都是耍流氓”架構的優化也要針對業務。
好了,最後是數據庫層
瀏覽器攔截了80%,站點層攔截了99.9%並做了頁面緩存,服務層又做了寫請求隊列與數據緩存,每次透到數據庫層的請求都是可控的。db基本就沒什麼壓力了,閒庭信步,單機也能扛得住,還是那句話,庫存是有限的,小米的產能有限,透這麼多請求來數據庫沒有意義。
全部透到數據庫,100w個下單,0個成功,請求有效率0%。透3k個到數據,全部成功,請求有效率100%。
五、總結
上文應該描述的非常清楚了,沒什麼總結了,對於秒殺系統,再次重複下我個人經驗的兩個架構優化思路:
(1)儘量將請求攔截在系統上游(越上游越好);
(2)讀多寫少的常用多使用緩存(緩存抗讀壓力);
瀏覽器和APP:做限速
站點層:按照uid做限速,做頁面緩存
服務層:按照業務做寫請求隊列控制流量,做數據緩存
數據層:閒庭信步
並且:結合業務做優化
六、Q&A
問題1、按你的架構,其實壓力最大的反而是站點層,假設真實有效的請求數有1000萬,不太可能限制請求連接數吧,那麼這部分的壓力怎麼處理?
答:每秒鐘的併發可能沒有1kw,假設有1kw,解決方案2個:
(1)站點層是可以通過加機器擴容的,最不濟1k臺機器來唄。
(2)如果機器不夠,拋棄請求,拋棄50%(50%直接返回稍後再試),原則是要保護系統,不能讓所有用戶都失敗。
問題2、“控制了10w個肉雞,手裏有10w個uid,同時發請求” 這個問題怎麼解決哈?
答:上面說了,服務層寫請求隊列控制
問題3:限制訪問頻次的緩存,是否也可以用於搜索?例如A用戶搜索了“手機”,B用戶搜索“手機”,優先使用A搜索後生成的緩存頁面?
答:這個是可以的,這個方法也經常用在“動態”運營活動頁,例如短時間推送4kw用戶app-push運營活動,做頁面緩存。
問題4:如果隊列處理失敗,如何處理?肉雞把隊列被撐爆了怎麼辦?
答:處理失敗返回下單失敗,讓用戶再試。隊列成本很低,爆了很難吧。最壞的情況下,緩存了若干請求之後,後續請求都直接返回“無票”(隊列裏已經有100w請求了,都等着,再接受請求也沒有意義了)
問題5:站點層過濾的話,是把uid請求數單獨保存到各個站點的內存中麼?如果是這樣的話,怎麼處理多臺服務器集羣經過負載均衡器將相同用戶的響應分佈到不同服務器的情況呢?還是說將站點層的過濾放到負載均衡前?
答:可以放在內存,這樣的話看似一臺服務器限制了5s一個請求,全局來說(假設有10臺機器),其實是限制了5s 10個請求,解決辦法:
1)加大限制(這是建議的方案,最簡單)
2)在nginx層做7層均衡,讓一個uid的請求儘量落到同一個機器上
問題6:服務層過濾的話,隊列是服務層統一的一個隊列?還是每個提供服務的服務器各一個隊列?如果是統一的一個隊列的話,需不需要在各個服務器提交的請求入隊列前進行鎖控制?
答:可以不用統一一個隊列,這樣的話每個服務透過更少量的請求(總票數/服務個數),這樣簡單。統一一個隊列又複雜了。
問題7:秒殺之後的支付完成,以及未支付取消佔位,如何對剩餘庫存做及時的控制更新?
答:數據庫裏一個狀態,未支付。如果超過時間,例如45分鐘,庫存會重新會恢復(大家熟知的“回倉”),給我們搶票的啓示是,開動秒殺後,45分鐘之後再試試看,說不定又有票喲~
問題8:不同的用戶瀏覽同一個商品 落在不同的緩存實例顯示的庫存完全不一樣 請問老師怎麼做緩存數據一致或者是允許髒讀?
答:目前的架構設計,請求落到不同的站點上,數據可能不一致(頁面緩存不一樣),這個業務場景能接受。但數據庫層面真實數據是沒問題的。
問題9:就算處於業務把優化考慮“3k張火車票,只透3k個下單請求去db”那這3K個訂單就不會發生擁堵了嗎?
答:(1)數據庫抗3k個寫請求還是ok的;(2)可以數據拆分;(3)如果3k扛不住,服務層可以控制透過去的併發數量,根據壓測情況來吧,3k只是舉例;
問題10;如果在站點層或者服務層處理後臺失敗的話,需不需要考慮對這批處理失敗的請求做重放?還是就直接丟棄?
答:別重放了,返回用戶查詢失敗或者下單失敗吧,架構設計原則之一是“fail fast”。
問題11.對於大型系統的秒殺,比如12306,同時進行的秒殺活動很多,如何分流?
答:垂直拆分
問題12、額外又想到一個問題。這套流程做成同步還是異步的?如果是同步的話,應該還存在會有響應反饋慢的情況。但如果是異步的話,如何控制能夠將響應結果返回正確的請求方?
答:用戶層面肯定是同步的(用戶的http請求是夯住的),服務層面可以同步可以異步。
問題13、秒殺羣提問:減庫存是在那個階段減呢?如果是下單鎖庫存的話,大量惡意用戶下單鎖庫存而不支付如何處理呢?
答:數據庫層面寫請求量很低,還好,下單不支付,等時間過完再“回倉”,之前提過了。
細聊分佈式ID生成方法
一、需求緣起
幾乎所有的業務系統,都有生成一個記錄標識的需求,例如:
(1)消息標識:message-id
(2)訂單標識:order-id
(3)帖子標識:tiezi-id
這個記錄標識往往就是數據庫中的唯一主鍵,數據庫上會建立聚集索引(cluster index),即在物理存儲上以這個字段排序。
這個記錄標識上的查詢,往往又有分頁或者排序的業務需求,例如:
(1)拉取最新的一頁消息:selectmessage-id/ order by time/ limit 100
(2)拉取最新的一頁訂單:selectorder-id/ order by time/ limit 100
(3)拉取最新的一頁帖子:selecttiezi-id/ order by time/ limit 100
所以往往要有一個time字段,並且在time字段上建立普通索引(non-cluster index)。
我們都知道普通索引存儲的是實際記錄的指針,其訪問效率會比聚集索引慢,如果記錄標識在生成時能夠基本按照時間有序,則可以省去這個time字段的索引查詢:
select message-id/ (order by message-id)/limit 100
再次強調,能這麼做的前提是,message-id的生成基本是趨勢時間遞增的。
這就引出了記錄標識生成(也就是上文提到的三個XXX-id)的兩大核心需求:
(1)全局唯一
(2)趨勢有序
這也是本文要討論的核心問題:如何高效生成趨勢有序的全局唯一ID。
二、常見方法、不足與優化
【常見方法一:使用數據庫的 auto_increment 來生成全局唯一遞增ID】
優點:
(1)簡單,使用數據庫已有的功能
(2)能夠保證唯一性
(3)能夠保證遞增性
(4)步長固定
缺點:
(1)可用性難以保證:數據庫常見架構是一主多從+讀寫分離,生成自增ID是寫請求,主庫掛了就玩不轉了
(2)擴展性差,性能有上限:因爲寫入是單點,數據庫主庫的寫性能決定ID的生成性能上限,並且難以擴展
改進方法:
(1)增加主庫,避免寫入單點
(2)數據水平切分,保證各主庫生成的ID不重複
如上圖所述,由1個寫庫變成3個寫庫,每個寫庫設置不同的auto_increment初始值,以及相同的增長步長,以保證每個數據庫生成的ID是不同的(上圖中庫0生成0,3,6,9…,庫1生成1,4,7,10,庫2生成2,5,8,11…)
改進後的架構保證了可用性,但缺點是:
(1)喪失了ID生成的“絕對遞增性”:先訪問庫0生成0,3,再訪問庫1生成1,可能導致在非常短的時間內,ID生成不是絕對遞增的(這個問題不大,我們的目標是趨勢遞增,不是絕對遞增)
(2)數據庫的寫壓力依然很大,每次生成ID都要訪問數據庫
爲了解決上述兩個問題,引出了第二個常見的方案
【常見方法二:單點批量ID生成服務】
分佈式系統之所以難,很重要的原因之一是“沒有一個全局時鐘,難以保證絕對的時序”,要想保證絕對的時序,還是隻能使用單點服務,用本地時鐘保證“絕對時序”。數據庫寫壓力大,是因爲每次生成ID都訪問了數據庫,可以使用批量的方式降低數據庫寫壓力。
如上圖所述,數據庫使用雙master保證可用性,數據庫中只存儲當前ID的最大值,例如0。ID生成服務假設每次批量拉取6個ID,服務訪問數據庫,將當前ID的最大值修改爲5,這樣應用訪問ID生成服務索要ID,ID生成服務不需要每次訪問數據庫,就能依次派發0,1,2,3,4,5這些ID了,當ID發完後,再將ID的最大值修改爲11,就能再次派發6,7,8,9,10,11這些ID了,於是數據庫的壓力就降低到原來的1/6了。
優點:
(1)保證了ID生成的絕對遞增有序
(2)大大的降低了數據庫的壓力,ID生成可以做到每秒生成幾萬幾十萬個
缺點:
(1)服務仍然是單點
(2)如果服務掛了,服務重啓起來之後,繼續生成ID可能會不連續,中間出現空洞(服務內存是保存着0,1,2,3,4,5,數據庫中max-id是5,分配到3時,服務重啓了,下次會從6開始分配,4和5就成了空洞,不過這個問題也不大)
(3)雖然每秒可以生成幾萬幾十萬個ID,但畢竟還是有性能上限,無法進行水平擴展
改進方法:
單點服務的常用高可用優化方案是“備用服務”,也叫“影子服務”,所以我們能用以下方法優化上述缺點(1):
如上圖,對外提供的服務是主服務,有一個影子服務時刻處於備用狀態,當主服務掛了的時候影子服務頂上。這個切換的過程對調用方是透明的,可以自動完成,常用的技術是vip+keepalived,具體就不在這裏展開。
【常見方法三:uuid】
上述方案來生成ID,雖然性能大增,但由於是單點系統,總還是存在性能上限的。同時,上述兩種方案,不管是數據庫還是服務來生成ID,業務方Application都需要進行一次遠程調用,比較耗時。有沒有一種本地生成ID的方法,即高性能,又時延低呢?
uuid是一種常見的方案:string ID =GenUUID();
優點:
(1)本地生成ID,不需要進行遠程調用,時延低
(2)擴展性好,基本可以認爲沒有性能上限
缺點:
(1)無法保證趨勢遞增
(2)uuid過長,往往用字符串表示,作爲主鍵建立索引查詢效率低,常見優化方案爲“轉化爲兩個uint64整數存儲”或者“折半存儲”(折半後不能保證唯一性)
【常見方法四:取當前毫秒數】
uuid是一個本地算法,生成性能高,但無法保證趨勢遞增,且作爲字符串ID檢索效率低,有沒有一種能保證遞增的本地算法呢?
取當前毫秒數是一種常見方案:uint64 ID = GenTimeMS();
優點:
(1)本地生成ID,不需要進行遠程調用,時延低
(2)生成的ID趨勢遞增
(3)生成的ID是整數,建立索引後查詢效率高
缺點:
(1)如果併發量超過1000,會生成重複的ID
我去,這個缺點要了命了,不能保證ID的唯一性。當然,使用微秒可以降低衝突概率,但每秒最多隻能生成1000000個ID,再多的話就一定會衝突了,所以使用微秒並不從根本上解決問題。
【常見方法五:類snowflake算法】
snowflake是twitter開源的分佈式ID生成算法,其核心思想是:一個long型的ID,使用其中41bit作爲毫秒數,10bit作爲機器編號,12bit作爲毫秒內序列號。這個算法單機每秒內理論上最多可以生成1000*(2^12),也就是400W的ID,完全能滿足業務的需求。
借鑑snowflake的思想,結合各公司的業務邏輯和併發量,可以實現自己的分佈式ID生成算法。
舉例,假設某公司ID生成器服務的需求如下:
(1)單機高峯併發量小於1W,預計未來5年單機高峯併發量小於10W
(2)有2個機房,預計未來5年機房數量小於4個
(3)每個機房機器數小於100臺
(4)目前有5個業務線有ID生成需求,預計未來業務線數量小於10個
(5)…
分析過程如下:
(1)高位取從2016年1月1日到現在的毫秒數(假設系統ID生成器服務在這個時間之後上線),假設系統至少運行10年,那至少需要10年*365天*24小時*3600秒*1000毫秒=320*10^9,差不多預留39bit給毫秒數
(2)每秒的單機高峯併發量小於10W,即平均每毫秒的單機高峯併發量小於100,差不多預留7bit給每毫秒內序列號
(3)5年內機房數小於4個,預留2bit給機房標識
(4)每個機房小於100臺機器,預留7bit給每個機房內的服務器標識
(5)業務線小於10個,預留4bit給業務線標識
這樣設計的64bit標識,可以保證:
(1)每個業務線、每個機房、每個機器生成的ID都是不同的
(2)同一個機器,每個毫秒內生成的ID都是不同的
(3)同一個機器,同一個毫秒內,以序列號區區分保證生成的ID是不同的
(4)將毫秒數放在最高位,保證生成的ID是趨勢遞增的
缺點:
(1)由於“沒有一個全局時鐘”,每臺服務器分配的ID是絕對遞增的,但從全局看,生成的ID只是趨勢遞增的(有些服務器的時間早,有些服務器的時間晚)
最後一個容易忽略的問題:
生成的ID,例如message-id/ order-id/ tiezi-id,在數據量大時往往需要分庫分表,這些ID經常作爲取模分庫分表的依據,爲了分庫分表後數據均勻,ID生成往往有“取模隨機性”的需求,所以我們通常把每秒內的序列號放在ID的最末位,保證生成的ID是隨機的。
又如果,我們在跨毫秒時,序列號總是歸0,會使得序列號爲0的ID比較多,導致生成的ID取模後不均勻。解決方法是,序列號不是每次都歸0,而是歸一個0到9的隨機數,這個地方。
一分鐘瞭解互聯網動靜分離架構
一、靜態頁面
靜態頁面,是指互聯網架構中,幾乎不變的頁面(或者變化頻率很低),例如:
-
首頁等html頁面
-
js/css等樣式文件
-
jpg/apk等資源文件
靜態頁面,有與之匹配的技術架構來加速,例如:
-
CDN
-
nginx
-
squid/varnish
二、動態頁面
動態頁面,是指互聯網架構中,不同用戶不同場景訪問,都不一樣的頁面,例如:
-
百度搜索結果頁
-
淘寶商品列表頁
-
速運個人訂單中心頁
這些頁面,不同用戶,不同場景訪問,大都會動態生成不同的頁面。
動態頁面,有與之匹配的技術架構,例如:
-
分層架構
-
服務化架構
-
數據庫,緩存架構
三、互聯網動靜分離架構
動靜分離是指,靜態頁面與動態頁面分開不同系統訪問的架構設計方法。
一般來說:
-
靜態頁面訪問路徑短,訪問速度快,幾毫秒
-
動態頁面訪問路徑長,訪問速度相對較慢(數據庫的訪問,網絡傳輸,業務邏輯計算),幾十毫秒甚至幾百毫秒,對架構擴展性的要求更高
-
靜態頁面與動態頁面以不同域名區分
四、頁面靜態化
既然靜態頁面訪問快,動態頁面生成慢,有沒有可能,將原本需要動態生成的站點提前生成好,使用靜態頁面加速技術來訪問呢?
這就是互聯網架構中的“頁面靜態化”優化技術。
舉例,如下圖,58同城的帖子詳情頁,原本是需要動態生成的:
-
瀏覽器發起http請求,訪問/detail/12348888x.shtml 詳情頁
-
web-server層從RESTful接口中,解析出帖子id是12348888
-
service層通過DAO層拼裝SQL語句,訪問數據庫
-
最終獲取數據,拼裝html返回瀏覽器
而“頁面靜態化”是指,將帖子ID爲12348888的帖子12348888x.shtml提前生成好,由靜態頁面相關加速技術來加速:
這樣的話,將極大提升訪問速度,減少訪問時間,提高用戶體驗。
五、頁面靜態化的適用場景
頁面靜態化優化後速度會加快,那能不能所有的場景都使用這個優化呢?哪些業務場景適合使用這個架構優化方案呢?
一切脫離業務的架構設計都是耍流氓,頁面靜態化,適用於:總數據量不大,生成靜態頁面數量不多的業務。例如:
-
58速運的城市頁只有幾百個,就可以用這個優化,只需提前生成幾百個城市的“靜態化頁面”即可
-
一些二手車業務,只有幾萬量二手車庫存,也可以提前生成這幾萬量二手車的靜態頁面
-
像58同城這樣的信息模式業務,有幾十億的帖子量,就不太適合於靜態化(碎片文件多,反而訪問慢)
六、總結
“頁面靜態化”是一種將原本需要動態生成的站點提前生成靜態站點的優化技術。
總數據量不大,生成靜態頁面數量不多的業務,非常適合於“頁面靜態化”優化。
數據庫讀寫分離架構,爲什麼我不喜歡
RD:單庫數據量太大,數據庫扛不住了,我要申請一個數據庫從庫,讀寫分離。
DBA:數據量多少?
RD:5000w左右。
DBA:讀寫吞吐量呢?
RD:讀QPS約200,寫QPS約30左右。
對於互聯網某些業務場景,並不是很喜歡數據庫讀寫分離架構
一、讀寫分離
什麼是數據庫讀寫分離?
答:一主多從,讀寫分離,主動同步,是一種常見的數據庫架構,一般來說:
-
主庫,提供數據庫寫服務
-
從庫,提供數據庫讀服務
-
主從之間,通過某種機制同步數據,例如mysql的binlog
一個組從同步集羣通常稱爲一個“分組”。
分組架構究竟解決什麼問題?
答:大部分互聯網業務讀多寫少,數據庫的讀往往最先成爲性能瓶頸,如果希望:
-
線性提升數據庫讀性能
-
通過消除讀寫鎖衝突提升數據庫寫性能
此時可以使用分組架構。
一句話,分組主要解決“數據庫讀性能瓶頸”問題,在數據庫扛不住讀的時候,通常讀寫分離,通過增加從庫線性提升系統讀性能。
二、水平切分
什麼是數據庫水平切分?
答:水平切分,也是一種常見的數據庫架構,一般來說:
-
每個數據庫之間沒有數據重合,沒有類似binlog同步的關聯
-
所有數據並集,組成全部數據
-
會用算法,來完成數據分割,例如“取模”
一個水平切分集羣中的每一個數據庫,通常稱爲一個“分片”。
水平切分架構究竟解決什麼問題?
答:大部分互聯網業務數據量很大,單庫容量容易成爲瓶頸,如果希望:
-
線性降低單庫數據容量
-
線性提升數據庫寫性能
此時可以使用水平切分架構。
一句話總結,水平切分主要解決“數據庫數據量大”問題,在數據庫容量扛不住的時候,通常水平切分。
三、爲什麼不喜歡讀寫分離
對於互聯網大數據量,高併發量,高可用要求高,一致性要求高,前端面向用戶的業務場景,如果數據庫讀寫分離:
-
數據庫連接池需要區分:讀連接池,寫連接池
-
如果要保證讀高可用,讀連接池要實現故障自動轉移
-
有潛在的主庫從庫一致性問題
-
如果面臨的是“讀性能瓶頸”問題,增加緩存可能來得更直接,更容易一點
-
關於成本,從庫的成本比緩存高不少
-
對於雲上的架構,以阿里云爲例,主庫提供高可用服務,從庫不提供高可用服務
所以,上述業務場景下,樓主建議使用緩存架構來加強系統讀性能,替代數據庫主從分離架構。
當然,使用緩存架構的潛在問題:如果緩存掛了,流量全部壓到數據庫上,數據庫會雪崩。不過幸好,雲上的緩存一般都提供高可用的服務。
四、總結
-
讀寫分離,解決“數據庫讀性能瓶頸”問題
-
水平切分,解決“數據庫數據量大”問題
-
對於互聯網大數據量,高併發量,高可用要求高,一致性要求高,前端面向用戶的業務場景,微服務緩存架構,可能比數據庫讀寫分離架構更合適
互聯網分層架構的本質
上圖是一個典型的互聯網分層架構:
-
客戶端層:典型調用方是browser或者APP
-
站點應用層:實現核心業務邏輯,從下游獲取數據,對上游返回html或者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與服務化
互聯網分層架構的本質,是數據的移動。
互聯網分層架構演進的核心原則:
-
讓上游更高效的獲取與處理數據,複用
-
讓下游能屏蔽數據的獲取細節,封裝
本文主要解答兩個問題:
-
後端架構,什麼時候進行DAO層的抽象
-
後端架構,什麼時候進行數據服務層的抽象
核心問題一:什麼時候進行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,難以拆分。
服務化之後,底層的數據庫被隔離開了,可以很方便的拆分出來,進行擴容。
【好處六:提供有限接口,無限性能】
在服務化之前,各業務線上遊想怎麼操縱數據庫都行,遇到了性能瓶頸,各業務線容易扯皮,相互推諉。
服務化之後,服務只提供有限的通用接口,理論上服務集羣能夠提供無限性能,性能出現瓶頸,服務層一處集中優化。
業務層是否也需要服務化?
觀點:
-
互聯網分層架構的本質,是數據的移動
-
互聯網分層架構演進的核心原則:是讓上游更高效的獲取與處理數據,讓下游能屏蔽數據的獲取細節
-
當手寫代碼從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的接口自測,不再等待一起聯調
結論:
當業務越來越複雜,端上的產品越來越多,展現層的變化越來越快越來越多,站點層存在大量代碼拷貝,數據獲取複雜性成爲通用痛點的時候,就應該進行前後端分離分層抽象,簡化數據獲取過程,提高數據獲取效率,向上遊屏蔽底層的複雜性。
最後再強調兩點:
-
是否需要前後端分離,和業務複雜性,以及業務發展階段有關,不可一概而論
-
本文強調的前後端分離的思路,實際情況下有多種實現方式,文章並沒有透徹展開實現細節