一篇長文,快速掌握8種系統架構技術模式
本文作者:京東金融技術研發部 劉洋
「摘要」時間如梭,作為一個老碼農,已奉獻十年有餘,經歷的系統架構設計不少,在這裡來,我把工作中使用到的一些架構方面的設計模式分享給大家,希望大家少走彎路,早日走上人生巔峰。總體而言,架構設計模式共有八種:
- 單庫單應用模式:最簡單的,最常見的,可能大家都見過,都用過
- 內容分發模式:目前用的比較多,大廠都在用
- 查詢分離模式:應對大並發的查詢、業務處理
- 微服務模式:適用於非常複雜的業務拆解
- 多級緩存模式:可以把各種緩存玩的非常好
- 分庫分表模式:解決單機資料庫、表瓶頸
- 彈性伸縮模式:解決波峰、波谷業務流量不均勻方法之一
- 多機房模式:解決高可用、高性能的一種方法
架構和設計模式
什麼是架構?
簡單理解,架構就是骨骼,人類包括動物的身體主要是由骨骼來支撐的,然後是在其上的肌肉、神經、皮膚等,同樣IT系統的架構也具有相同的功能。架構對於軟體的重要性和意義不亞於骨架對人類身體的意義。
設計模式是什麼?
每當有同行來我司面試,這個問題我都會問,回答五花八門、千奇百怪,呵呵~~~在我看來,模式就是經驗,設計模式就是設計經驗,有了這些經驗,我們就能在特定情況、前提下使用特定的設計、組合設計,這樣可以大大節省我們的設計時間、簡化我們的設計思路、提高工作效率。
一. 單庫單應用模式
這是最簡單的一種模式,可能我們大部分的本科畢業設計、一些課堂設計、小的應用,基本上都是這種模式,這種模式的一般設計見下圖:
二. 內容分發模式
基本上所有的大型的網站、大廠都或多或少的採用這一種模式,比較常見的應用場景是用CDN技術把網頁、圖片、CSS、JS、LIB等這些靜態資源放到離用戶最近的伺服器。這種模式的一般設計見下圖:
如上圖,這種模式較單庫單應用模式多了CDN、雲存儲OSS(七牛、又拍、S3等相似)。一個典型的應用流程:以用戶上傳、查看圖片需求為例,如下:
- 上傳的時候,用戶A選擇本地機器上的一張圖片P上傳
- 程序會把這個圖片P上傳到雲存儲OSS上面,並將該圖片的URL返回
- 程序把這個URL字元串存儲在資料庫中,上傳完成
- 查看的時候,程序從資料庫得到該圖片的URL
- 程序通過DNS查詢這個URL的圖片伺服器地址
- 智能DNS解析這個URL,得到與用戶最近的伺服器(或集群)的地址B
- 接下來把伺服器B上的圖片返回給程序
- 程序顯示圖片,查看完成
由上可知,這個模式的關鍵是智能DNS,它能夠解析出離用戶最近的那個伺服器。運行原理大致為:根據請求者的IP得到請求地點B,然後通過計算或者配置得到與B最近或通訊時間最短的伺服器C,然後把C的IP地址返回給請求者。這種模式的優缺點如下:
- 優點:資源下載非常快、無需過多的開發與配置,同時也減輕了後端伺服器對資源的存儲壓力,減少帶寬的使用。
- 缺點:目前來說OSS,CDN的售價還是稍微有些貴(雖然已經降價好幾次了),只適用於中小規模的應用,另外,還有一些壞鄰居(你懂的)。由於網路傳輸的延遲、CDN的同步策略等,會有一些一致性、更新速度慢方面的問題。
三. 查詢分離模式
這種模式主要解決的痛點是單機資料庫壓力過大,從而導致業務緩慢甚至超時,查詢的響應時間變長的問題,也包括需要大量資料庫計算資源的業務查詢請求。可以認為這是單庫單應用模式的升級版本,也是絕大多數公司技術架構迭代演進過程中的必經之路。 這種模式的一般設計見下圖:
如上圖所示,這種模式較單庫單應用與內容分發多了幾個部分:一個是業務資料庫進行了主從分離,另一個是引入了ES,為什麼要這樣?都解決了哪些問題,下面具體結合業務需求場景進行敘述。
場景一:全文檢索
這個業務,大多數應用都會有,如果您使用傳統的資料庫查詢技術,大部分可能都會使用像like這種的SQL語句,高級一些可能是先分詞,後通過分詞index相關的記錄,弊端是SQL語句的性能與全表掃描機制導致了非常嚴重的性能問題,現在基本上很少見到。
這裡的ES(ElasticSearch),是一種查詢引擎,類似的還有Solr等,ES較Solr配置簡單、使用起來更方便,所以這裡選用了它。另外,ES這種引擎支持橫向擴展,理論上沒有性能的瓶頸。同時,它還支持各種插件、自定義分詞器等,可擴展性非常強。在這裡,使用ES不僅可以完全替代資料庫完成全文檢索功能,還可以實現分頁、排序、分組、分面等功能。具體的,請同學們自行學習。那怎麼用呢?一般的流程是這樣的:
- 服務端把一條業務數據落庫落表
- 服務端非同步把該條數據發送到ES
- ES把該條記錄按照事先寫入的規則、配置放入自己的索引庫中
- 客戶端查詢的時候,由服務端把這個請求先發送到ES,得到數據後,根據需求進行拼裝、組合數據,返回給客戶端
- 實際中具體怎麼用,還請同學們根據實際情況酌情做組合、取捨
場景二:海量的普通查詢
普通查詢是指我們的業務邏輯中的大部分輔助性的查詢,如:取錢的時候必先查詢一下餘額,根據用戶的ID查詢用戶的相關記錄,取得該用戶最新的一條取錢記錄等。我們用的還非常多,同時,寫入請求也是巨多的,導致大量的寫入、查詢操作壓向同一資料庫,然後,資料庫掛了,系統也掛了,領導必然生氣了......
所以就要求我們必須分散資料庫的壓力,一個業界較成熟的方案就是資料庫的讀寫分離,寫的時候入主庫,讀的時候從從庫讀。這樣就把壓力分散到不同的資料庫中了,如果一個讀庫性能不行,扛不住壓力的話,可以一主多從,橫向擴展。可謂是一劑良藥啊!那如何使用呢?一般的流程是這樣的:
- 服務端把一條業務數據落庫
- 資料庫同步或非同步或半同步把該條數據複製到從庫
- 服務端讀數據的時候直接去從庫讀相應的數據
看起來簡單吧,一些聰明的、愛思考的、上進的同學可能發現問題了,也包括上面介紹的場景一,就是延遲的問題,如:數據還沒有到從庫,我就馬上讀,那麼是讀不到的,會發生問題的。
對於這個問題,各家公司解決的思路不一樣,方法不盡相同。一個普遍的解決方案是:讀不到就讀主庫,當然這麼說是有前提條件的,但具體的方案這裡就不一一展開了。總結一下這種模式的優缺點,如下:
- 優點:減少資料庫的壓力,理論上可以提供無限高的讀性能,間接提高業務(寫)的性能,專用的查詢、索引、全文(分詞)解決方案。
- 缺點:數據的延遲,數據一致性的保證較難。
四. 微服務模式
上面的模式看似不錯,解決了性能問題,但是軟體系統天生的複雜性決定了,除了性能,還有其他例如高可用、健壯性等大量問題等待我們解決,所以繼續吧......
微服務是最近的熱點,大大小小、國內國外的公司都在學習、實踐這個模式,但大部分都沒有弄清楚為什麼要這麼做,也並不知道這麼做有什麼好處和壞處,在這裡,我將自己的親身體驗說一下。系統隨著業務與人員的增加,可能會遇到了如下的問題:
- 單機資料庫寫請求量大量增加,導致資料庫壓力變大
- 資料庫一旦掛了,那麼整個業務都掛了
- 業務代碼越來越多,都在一個GIT里,越來越難以維護
- 代碼腐化嚴重、臭味越來越濃
- 上線越來越頻繁,經常是一個小功能的修改,就要整個大項目要重新編譯
- 部門越來越多,該哪個部門改動大項目中的哪個東西,職責邊界比較模糊
- 其他一些外圍系統直接連接資料庫,導致一旦資料庫結構發生變化,所有的相關係統都要通知,甚至對修改不敏感的系統也要通知
- 每個應用伺服器需要開通所有的許可權、網路、FTP、各種各樣的,因為每個伺服器部署的應用都是一樣的
- 作為架構師,我已經失去了對這個系統的把控......
為了解決上述問題,需要使用微服務模式,這種模式的一般設計見下圖:
如上圖所示,把業務進行分塊,做了垂直切分,切成一個個獨立的系統,每個系統各自衍化,有自己的資料庫、緩存、ES等輔助系統,系統之間的實時交互通過RPC,非同步交互通過MQ,通過這種組合,共同完成整個系統功能。
那麼,這麼做是否真的解決上述問題了呢?不玩虛的,一個個來說。
- 問題一:由於拆分成了多個子系統,系統上的壓力被分散了,而各個子系統都有自己的資料庫實例,所以資料庫的壓力變小。
- 問題二:一個子系統A的資料庫掛了,只是影響到系統A和使用系統A的那些功能,不會所有的功能不可用,從而解決一個資料庫掛了,導致所有功能不可用的問題。
- 問題三、四:也因為拆分得到了解決,各個子系統有自己獨立的GIT代碼庫,不會相互影響。通用的模塊可通過庫、服務、平台的形式解決。
- 問題五:子系統A發生改變,需要上線,那麼我只需要編譯A,然後上線就可以了,不需要其他系統做同樣的事情。
- 問題六:順應了康威定律,一個部門該幹什麼事、輸出什麼,也通過服務的形式暴露出來,職責清晰。
- 問題七:所有需要數據的需求,都通過介面的形式發布出去,客戶通過介面獲取數據,從而屏蔽了底層資料庫結構,甚至數據來源,只需保證介面契約沒有發生變化即可,新的需求增加新的介面,不會影響老的介面。
- 問題八:不同的子系統需要不同的許可權,這個問題也優雅的解決了。
- 問題九:暫時控制住了複雜性,我只需控制好大的方面,定義好系統邊界、介面、大的流程,然後再分而治之、逐個擊破、合縱連橫。
目前來說,所有問題得到解決,bingo! 但是,這麼做還會有許多其他的副作用會隨之產生,如RPC、MQ的超高穩定性、超高性能,網路間的延遲,數據一致性等問題,這裡就不展開來講了,太多了,一本書(厚)都講不完。
另外,對於這個模式來說,最難把握的應該是度,切記不要切分過細,我見過一個功能一個子系統,上百個方法分成上百個子系統的,有點太過度了。實踐中,一個目前較為可行的方法是:能不分就不分,除非有非常必要的理由!
- 優點:相對高性能,可擴展性強,高可用,適合於中等以上規模公司架構。
- 缺點:複雜、度不好把握。不僅需要一個能在高層把控大方向、大流程、總體技術的人,還需要各個子系統有針對性的開發。把握不好度或者濫用的話,這個模式適得其反!
五. 多級緩存
這個模式可以說是應對超高查詢壓力的一種普遍採用的策略,基本的思想就是在所有鏈路的地方,盡能加緩存就加緩存。一般設計見下圖:
如上圖所示,一般在三個地方加入,一個是客戶端處,一個是API網關處,一個是具體的後端業務處,下面分別介紹:
客戶端處:這個地方加緩存是效果最好的---無延遲。因為不用經過長長的網路鏈條去後端業務處獲取數據,從而導致載入時間過長,客戶流失等損失。雖然有CDN的支持,但是從客戶端到CDN還是有網路延遲的,雖然不大。具體的技術依據不同的客戶端而定,對於WEB來講,有瀏覽器本地緩存、Cookie、Storage、緩存策略等技術;對於APP來講,有本地資料庫、本地文件、本地內存、進程內緩存支持。以上提到的各種技術有興趣的同學可以繼續展開學習。如果客戶端緩存沒有命中,那麼就會去後端業務拿數據,一般來講,都會有個API網關,在這裡加緩存也是非常有必要的。
API網關處:這個地方加緩存的好處是不用把請求發送到後方,直接在這裡就處理了,然後返回給請求者。常見的技術,如http請求,API網關用的基本都是nginx,可以使用nginx本身的緩存模塊,也可以使用Lua+Redis技術定製化。其他的也都大同小異。
後端業務處:這個我想就不用多說了,大家應該差不多都知道,什麼Redis,Memcache,Jvm內等等,就不贅述了。實踐中,要結合具體的實際情況,綜合利用各級緩存技術,使得各種請求最大程度到達後端業務之前就被解決掉,從而減少後端服務壓力、減少佔用帶寬、增強用戶體驗。至於是否只有這三個地方加緩存,我覺得要活學活用,心法比劍法重要!總結一下這個模式的優缺點:
- 優點:能抗住大量讀請求,減少後端壓力。
- 缺點:數據一致性問題較突出,容易發生雪崩,即:如果客戶端緩存失效、API網關緩存失效,那麼所有的大量請求瞬間壓向後端業務系統,後果可想而知。
六. 分庫分表模式
這種模式主要解決單表寫入、讀取、存儲壓力過大,從而導致業務緩慢甚至超時,交易失敗,容量不夠的問題。一般有水平切分和垂直切分兩種,這裡主要介紹水平切分。這個模式也是技術架構迭代演進過程中的必經之路。一般設計見下圖:
如上圖所示紅色部分,把一張表分到了幾個不同的庫中,從而分擔壓力。是不是很籠統?那我們接下來就詳細的講解一下。首先澄清幾個概念:
- 主機:硬體,指一台物理機,或者虛擬機,有自己的CPU,內存,硬碟等。
- 實例:資料庫實例,如一個MySQL服務進程。一個主機可以有多個實例,不同的實例有不同的進程,監聽不同的埠。
- 庫:指表的集合,如學校庫,可能包含教師表、學生表、食堂表等等,這些表在一個庫中。一個實例中可以有多個庫。庫與庫之間用庫名來區分。
- 表:庫中的表,不必多說,不懂的就不用往下看了,不解釋。那麼怎麼把單表分散呢?到底怎麼個分發呢?分發到哪裡呢?
- 主機:這是最主要的也是最重要的點,本質上分庫分表是因為計算與存儲資源不夠導致的,而這種資源主要是由物理機,主機提供的,所以在這裡分是最基本的,畢竟沒有可用的計算資源,怎麼分效果都不是太好的。
- 實例:實例控制著連接數,同時受OS限制,CPU、內存、硬碟、網路IO也會受間接影響。會出現熱實例的現象,即:有些實例特別忙,有些實例非常的空閑。一個典型的現象是:由於單表反應慢,導致連接池被打滿,所有其他的業務都受影響了。這時候,把表分到不同的實例是有一些效果的。
- 分庫:一般是由於單庫中最大單表數量的限制,才採取分庫。
- 分表:單表壓力過大,索引量大,容量大,單表的鎖。據以上,把單表水平切分成不同的表。
大型應用中,都是一台主機上只有一個實例,一個實例中只有一個庫,庫==實例==主機,所以才有了分庫分表這個簡稱。既然知道了基本理論,那麼具體是怎麼做的呢?邏輯是怎麼跑的呢?接下來用一個例子來講解一下:
需求很簡單,用戶表(user)單表數據量1億,查詢、插入、存儲都出現了問題,怎麼辦呢?
- 首先,分析問題,這個明顯是由於數據量太大了而導致的問題。
- 其次,設計方案,可以分為10個庫,這樣每個庫的數據量就降到了1KW,單表1KW數據量還是有些大,而且不利於以後量的增長,所以每個庫再分100個表,這個每個單表數據量就為10W了,對於查詢、索引更新、單表文件大小、打開速度,都有一些益處。接下來,給IT部門打電話,要10台物理機,擴展資料庫......
- 最後,邏輯實現,這裡應該是最有學問的地方。首先是寫入數據,需要知道寫到哪個分庫分表中,讀也是一樣的,所以,需要有個請求路由層,負責把請求分發、轉換到不同的庫表中,一般有路由規則的概念。
怎麼樣,簡單吧?哈哈,too 那義務。說說這個模式的問題,主要是帶來了事務上的問題,因為分庫分表,事務完成不了,而分散式事務又太笨重,所以這裡需要有一定的策略,保證在這種情況下事務能夠完成。採取的策略如:最終一致性、複製、特殊設計等。再有就是業務代碼的改造,一些關聯查詢要改造,一些單表orderBy的問題需要特殊處理,也包括groupBy語句,如何解決這些副作用不是一句兩句能說清楚的,以後有時間單獨講講這些。這種模式的優缺點如下:
- 優點:減少資料庫單表的壓力。
- 缺點:事務保證困難、業務邏輯需要做大量改造。
七. 彈性伸縮模式
這種模式主要解決突發流量的到來,導致無法橫向擴展或者橫向擴展太慢,進而影響業務,全站崩潰的問題。這個模式是一種相對來說比較高級的技術,也是各個大公司目前都在研究、試用的技術。截至今日,有這種思想的架構師就已經是很不錯了,能夠拿到較高薪資,更別提那些已經實踐過的,甚至實現了底層系統的那些,一般設計見下圖:
如上圖所示,多了一個彈性伸縮服務,用來動態的增加、減少實例。原理上非常簡單,但是這個模式到底解決什麼問題呢?先說說由來和意義。每年的雙11、618或者一些大促到來之前,我們都會為大流量的到來做以下幾個方面的工作:
- 提前準備10倍甚至更多的機器,即使用不上也要放在那裡備著,以防萬一。這樣浪費了大量的資源。
- 每台機器配置、調試、引流,以便讓所有的機器都可用。這樣浪費了大量的人力、物力,更容易出錯。
- 如果機器準備不充分,那麼還要加班加點的重複上面的工作。這樣做特別容易出錯,引來領導的不滿,沒時間回家陪老婆,然後你的老婆就......(自己想)
在雙十一之後,我們還要人工做縮容,非常的辛苦。一般一年中會有多次促銷,那麼我們就會一直這樣,實在是煩!
最嚴重的,突然間的大流量爆發,會讓我們猝不及防,半夜起來擴容是在正常不過的事情,為此,我們偷懶起來,要更多的機器備著,也就出現了大量的cpu利用率為1%的機器。
那麼如何改變這種情況呢?請接著看:
首先把所有的計算資源整合成資源池的概念,然後通過一些策略、監控、服務,動態的從資源池中獲取資源,用完後在放回到池子中,供其他系統使用。具體實現上比較成熟的兩種資源池方案是VM、docker,每個都有著自己強大的生態。監控的點有CPU、內存、硬碟、網路IO、服務質量等,根據這些,在配合一些預留、擴張、收縮策略,就可以簡單的實現自動伸縮。怎麼樣?是不是很神奇?深入的內容後續將詳細介紹。這種模式的優缺點如下:
- 優點:彈性、隨需計算,充分優化企業計算資源。
- 缺點:應用要從架構層做到可橫向擴展化改造、依賴的底層配套比較多,對技術水平、實力、應用規模要求較高。
八. 多機房模式
這種模式主要解決不同地區高性能、高可用的問題。隨著應用用戶不斷的增加,用戶群體分布在全球各地,如果把伺服器部署在一個地方,一個機房,比如北京,那麼美國的用戶使用應用的時候就會特別慢,因為每一個請求都需要通過海底光纜走上個那麼一秒鐘(預估)左右,這樣對用戶體驗及其不好。怎麼辦?使用多機房部署。這種模式的一般設計見下圖:
如上圖所示,一個典型的用戶請求流程如下:
- 用戶請求一個鏈接A
- 通過DNS智能解析到離用戶最近的機房B
- 使用B機房服務鏈接A
是不是覺得很簡單,沒啥?其實這裡面的問題沒有表面這麼簡單,下面一一道來。
- 首先是數據同步問題,在中國產生的數據要同步到美國,美國的也一樣,數據同步就會涉及數據版本、一致性、更新丟棄、刪除等問題。
- 其次是一地多機房的請求路由問題,典型的是如上圖,中國的北京機房和杭州機房,如果北京機房掛了,那麼要能夠通過路由把所有發往北京機房的請求轉發到杭州機房。異地也存在這個問題。
所以,多機房模式,也就是異地多活並不是那麼的簡單,這裡只是起了個頭。這種模式的優缺點如下:
- 優點:高可用、高性能、異地多活。
- 缺點:數據同步、數據一致性、請求路由。
至此,關於八種架構設計模式及其優缺點概述就介紹完了。希望大家可以靈活運用,共勉~
推薦閱讀: