微服務(wù)架構(gòu)下的高可用和高性能設(shè)計
今天再談下微服務(wù)架構(gòu)下的高可用性設(shè)計。
對于高可用性實(shí)際應(yīng)該包括了高可靠性,高性能和高擴(kuò)展性。因此談微服務(wù)架構(gòu)的高可用性,首先需要梳理三者之間的關(guān)系。
高可用性三個維度和相互關(guān)系
對于業(yè)務(wù)系統(tǒng)的高可用性,實(shí)際上包括了高可靠,高性能和高擴(kuò)展三個方面的內(nèi)容。而且三方面相互之間還存在相互的依賴和影響關(guān)系。
對于三者的關(guān)系,我們可以用下圖進(jìn)行描述。

上圖可以看到高可靠,高性能和高擴(kuò)展性三者之間的關(guān)系。
對于高可靠性來來說,傳統(tǒng)的HA架構(gòu),冗余設(shè)計都可以滿足高可靠性要求,但是并不代表系統(tǒng)具備了高性能和可擴(kuò)展性能力。反過來說,當(dāng)系統(tǒng)具備了高擴(kuò)展性的時候,一般我們在設(shè)計擴(kuò)展性的時候都會考慮到同時兼顧冗余和高可靠,比如我們常說的集群技術(shù)。
對于高性能和高擴(kuò)展性兩點(diǎn)來說,高擴(kuò)展性是高性能的必要條件,但是并不是充分條件。一個業(yè)務(wù)系統(tǒng)的高性能不是簡單的具備擴(kuò)展能力就可以,而是需要業(yè)務(wù)系統(tǒng)本身軟件架構(gòu)設(shè)計,代碼編寫各方面都滿足高性能設(shè)計要求。
對于高可靠和高性能,兩者反而表現(xiàn)出來一種相互制約的關(guān)系,即在高性能支撐的狀態(tài)下,往往是對系統(tǒng)的高可靠性形成嚴(yán)峻挑戰(zhàn),也正是這個原因我們會看到類似限流熔斷,SLA服務(wù)降級等各種措施來控制異常狀態(tài)下的大并發(fā)訪問和調(diào)用。
數(shù)據(jù)庫的高可用性
在我前面談微服務(wù)架構(gòu)的時候就談到,在微服務(wù)架構(gòu)下傳統(tǒng)的單體應(yīng)用要進(jìn)行拆分,這個拆分不僅僅是應(yīng)用層組件的拆分,還包括了數(shù)據(jù)庫本身的拆分。
如果一個傳統(tǒng)的單體應(yīng)用規(guī)劃為10個微服務(wù),則可能會垂直拆分為10個獨(dú)立的數(shù)據(jù)庫。這實(shí)際上減小了每一個數(shù)據(jù)庫本身面對的性能負(fù)荷,同時提升了數(shù)據(jù)庫整體的處理能力。
同時在拆分后雖然引入了各種跨庫查詢,分布式事務(wù)等問題,但是實(shí)際很多跨庫操作,復(fù)制的數(shù)據(jù)處理計算都不在數(shù)據(jù)庫里面完成,數(shù)據(jù)庫更多的是提供單純的CRUD類操作接口,這本身也是提升數(shù)據(jù)庫性能的一個關(guān)鍵。
如果采用Mysql數(shù)據(jù)庫。
要滿足高可靠性,你可以采用Dual-Master雙主架構(gòu),即兩個主節(jié)點(diǎn)雙活,但是僅一個節(jié)點(diǎn)提供數(shù)據(jù)庫接口能力,另外一個節(jié)點(diǎn)實(shí)時同步數(shù)據(jù)庫日志,作為備節(jié)點(diǎn)。當(dāng)主節(jié)點(diǎn)出現(xiàn)故障的時候,備節(jié)點(diǎn)自動轉(zhuǎn)變?yōu)橹鞴?jié)點(diǎn)服務(wù)。
簡單的雙主架構(gòu)兩節(jié)點(diǎn)間安裝代理,通過Binlog日志復(fù)制,上層通過類似Haproxy+Keepalive實(shí)現(xiàn)通過的VIP浮動IP提供和心跳監(jiān)測。
可以看到雙主架構(gòu)更多的是為高可靠服務(wù)。
如果要滿足高性能,常采用的是讀寫分離集群。即1個主節(jié)點(diǎn)承擔(dān)讀寫操作,多個從節(jié)點(diǎn)承擔(dān)讀操作。從節(jié)點(diǎn)仍然是通過Binlog日志進(jìn)行主節(jié)點(diǎn)信息同步。當(dāng)有數(shù)據(jù)訪問請求進(jìn)入的時候,前端Proxy可以自動分析是CUD類請求,還是R讀請求,以進(jìn)行請求的路由轉(zhuǎn)發(fā)。
當(dāng)我們進(jìn)行訂單新增操作的時候,當(dāng)新增成功的時候需要快速的刷新當(dāng)前訂單列表界面,第二次的刷新本身是讀操作,但是和前面的寫綁定很緊,實(shí)際上不太適合從Slave節(jié)點(diǎn)讀取數(shù)據(jù)的。這個時候可以在進(jìn)行Sql調(diào)用的時候明確指定是否仍然從主節(jié)點(diǎn)獲取數(shù)據(jù)。
當(dāng)然,大部分時候可能需要兩者結(jié)合,既提供足夠的高可靠性,又提供足夠的高性能。因此Mysql集群在搭建的時候既需要進(jìn)行雙主設(shè)置,又需要進(jìn)行多個從節(jié)點(diǎn)設(shè)置。

在上圖這種邏輯部署架構(gòu)下,基本就可以同時滿足高可靠和高性能兩個方面的需求。但是從上面架構(gòu)部署可以看到,備節(jié)點(diǎn)的主和從都處于一種熱備無法實(shí)際提供能力狀態(tài)。
是否可以將所有Slave掛到一個Master上?
如果這樣設(shè)計,那么當(dāng)主Master出現(xiàn)故障的時候,就需要對多個Slave節(jié)點(diǎn)進(jìn)行自動化漂移。這一方面是整體實(shí)現(xiàn)比較復(fù)雜,另外就是可靠性也不如上面這種架構(gòu)。
對數(shù)據(jù)庫性能擴(kuò)展的思考
首先來看前面架構(gòu)本身的一些潛在問題點(diǎn):
第一就是CUD操作仍然是單節(jié)點(diǎn)提供能力。對于讀操作占大部分場景的,基本可以通過雙主+讀寫分離集群實(shí)現(xiàn)很好的性能擴(kuò)展。但是如果CUD操作頻繁仍然可能出現(xiàn)性能問題。
其次,數(shù)據(jù)庫性能問題一般分為兩個層面,其一就是大并發(fā)請求下的性能,這個可以通過集群負(fù)載均衡去解決,其二是單個請求訪問大數(shù)據(jù)庫表模糊查詢性能,這個是服務(wù)通過負(fù)載去解決的。
也就是說上面的設(shè)計,在大并發(fā)的CUD操作,對大數(shù)據(jù)表的關(guān)聯(lián)查詢或模糊查詢操作仍然可能出現(xiàn)明顯的性能問題。
如何來解決這個問題?

簡單來說就是寫入通過消息中間件來將同步轉(zhuǎn)異步,進(jìn)行前端削峰。而對于查詢則進(jìn)行內(nèi)容緩存或創(chuàng)建二級索引,提升查詢效率。
對于查詢本身又包括了偏結(jié)構(gòu)化數(shù)據(jù)查詢和處理,類似采用Redis庫或Memcached進(jìn)行緩存;而對于非結(jié)構(gòu)化數(shù)據(jù),類似消息報文,日志等采用Solr或ElasticSearch構(gòu)建二級索引并實(shí)現(xiàn)全文檢索能力。
當(dāng)面臨大量的數(shù)據(jù)寫入操作類操作的時候,單個Master節(jié)點(diǎn)往往性能很難支撐住,這個時候采用類似RabbitMQ,RocketMQ,Kafka等消息中間件來進(jìn)行異步銷峰處理就是必要的。這個異步實(shí)際上涉及到兩個層面的異步。
其一是對于發(fā)短信,記錄日志,啟流程等接口服務(wù)異步。其二是對長耗時寫入操作異步,先反饋用戶請求收到,處理完再通知用戶拿結(jié)果。
而對于查詢操作,前面談到的并發(fā)查詢可以進(jìn)行集群負(fù)載。
但是對于大數(shù)據(jù)量表,比如上億記錄的大表模糊查詢,這塊就必須進(jìn)行二級索引。對這種大的數(shù)據(jù)表的查詢即使沒有并發(fā)查詢,如果不進(jìn)行二級索引,查詢效率和響應(yīng)速度仍然很慢。
對半結(jié)構(gòu)化信息啟用分布式存儲

對于類似日志,接口服務(wù)調(diào)用日志等半結(jié)構(gòu)化信息,本身數(shù)據(jù)量很大,如果全部存儲在結(jié)構(gòu)化數(shù)據(jù)庫中,那么對存儲空間需求很大,而且很難擴(kuò)展。特別是前面的Mysql集群方案本身還是采用本地磁盤進(jìn)行存儲的情況下。
因此需要對歷史日志進(jìn)行清除,同時將歷史日志遷移到分布式存儲庫,比如Hdfs或Hbase庫,然后基于分布式存儲再構(gòu)建二級緩存能力。
構(gòu)建DaaS數(shù)據(jù)層進(jìn)行水平擴(kuò)展

前面談到在拆分了微服務(wù)后已經(jīng)進(jìn)行了垂直擴(kuò)展,比如一個資產(chǎn)管理系統(tǒng)可以拆分為資產(chǎn)新增,資產(chǎn)調(diào)撥,資產(chǎn)折舊,資產(chǎn)盤點(diǎn)等10個微服務(wù)模塊。
但是在拆分后仍然發(fā)現(xiàn)資產(chǎn)數(shù)據(jù)量極大,比如在集團(tuán)集中化這種大型項(xiàng)目可以看到,一個省的資產(chǎn)數(shù)據(jù)表就接近上億條記錄。這種時候?qū)⑺惺?shù)據(jù)全部集中化在一個數(shù)據(jù)庫管理不現(xiàn)實(shí)。因此需要進(jìn)一步按省份或組織域進(jìn)行水平拆分。
在水平拆分后,在上層構(gòu)建DaaS層提供統(tǒng)一對外訪問能力。
應(yīng)用集群擴(kuò)展
對于應(yīng)用集群擴(kuò)展實(shí)際比數(shù)據(jù)庫層要簡單,應(yīng)用中間件層可以很方便的結(jié)合集群管理節(jié)點(diǎn)或者獨(dú)立的負(fù)載均衡硬件或軟件進(jìn)行集群能力擴(kuò)展。對于應(yīng)用集群擴(kuò)展,本身就是提升整個性能的關(guān)鍵方式。在集群的擴(kuò)展過程中還是有些問題需要進(jìn)一步討論。
集群做到完全的無狀態(tài)化
如果集群做到完全的無狀態(tài)化,那么集群就可以做到和負(fù)載均衡設(shè)備或軟件結(jié)合來實(shí)現(xiàn)負(fù)載均衡擴(kuò)展能力。比如硬件常用的F5或radware等,軟件如HAProxy,Nginx等。
Session會話信息如何處理?對于Session本身是有狀態(tài)的,因此對于Session信息可以考慮存儲到數(shù)據(jù)庫或Redis緩存庫中。
集群節(jié)點(diǎn)在啟動的時候往往需要讀取一些全局變量或配置文件信息,這些信息如果簡單的存在在本地磁盤往往難以集中化管理。因此當(dāng)前主流思路是啟用全局的配置中心來統(tǒng)一管理配置。
如果應(yīng)用功能實(shí)現(xiàn)中存在文件的上傳和存儲,那么這些文件存儲在磁盤本地本身也是有狀態(tài)的,因此對于這些文件本身也需要通過文件服務(wù)能力或分布式對象存儲服務(wù)能力來實(shí)現(xiàn)。
微服務(wù)架構(gòu)下各個微服務(wù)間本身存在接口交互和協(xié)同,對于接口調(diào)用的具體地址信息也需要通過服務(wù)注冊中心獲取,獲取后可以緩存在本地,但是必須有變更后實(shí)時更新機(jī)制。
四層負(fù)載和七層負(fù)載

首先看下最簡單的四層負(fù)載和七層負(fù)載的一個說明:
- 四層負(fù)載:即在OSI第4層工作,就是TCP層,可以根據(jù)IP+端口進(jìn)行負(fù)載均衡。此種Load Balance不理解應(yīng)用協(xié)議(如HTTP/FTP/MySQL等等)。
- 七層負(fù)載:工作在OSI的最高層,應(yīng)用層,可以基于Http協(xié)議和URL內(nèi)容進(jìn)行負(fù)載均衡。此時負(fù)載均衡能理解應(yīng)用協(xié)議。
當(dāng)前可以看到對于F5,Array等硬件負(fù)載均衡設(shè)備本身也是支持7層負(fù)載均衡的,同時在四層負(fù)載均衡的時候我們還可以設(shè)置是否進(jìn)行會話保持等高級特性。要明白四層負(fù)載均衡本質(zhì)是轉(zhuǎn)發(fā),而七層負(fù)載本質(zhì)是內(nèi)容交換和代理。
也就是說在不需要進(jìn)行狀態(tài)保留和基于內(nèi)容的路由的時候,我們完全可以啟用四層負(fù)載均衡來獲取更好的性能。
在微服務(wù)架構(gòu)前后端分離開發(fā)后。
后端微服務(wù)組件可以完全提供Rest API接口服務(wù)能力,那么本身就無狀態(tài)。而對于前端微服務(wù)組件直接面對最終用戶訪問,需要保持Session狀態(tài)。在這種情況下就可以進(jìn)行兩層負(fù)載均衡設(shè)計,即前端采用七層負(fù)載,而后端采用四層負(fù)載均衡。
前端緩存
前端緩存主要是分為HTTP緩存和瀏覽器緩存。其中HTTP緩存是在HTTP請求傳輸時用到的緩存,主要在服務(wù)器代碼上設(shè)置;而瀏覽器緩存則主要由前端開發(fā)在前端js上進(jìn)行設(shè)置。緩存可以說是性能優(yōu)化中簡單高效的一種優(yōu)化方式了。一個優(yōu)秀的緩存策略可以縮短網(wǎng)頁請求資源的距離,減少延遲,并且由于緩存文件可以重復(fù)利用,還可以減少帶寬,降低網(wǎng)絡(luò)負(fù)荷。
具體可以參考:
https://www.jianshu.com/p/256d0873c398
軟件性能問題分析和診斷

對于業(yè)務(wù)系統(tǒng)性能診斷,如果從靜態(tài)角度我們可以考慮從以下三個方面進(jìn)行分類
- 操作系統(tǒng)和存儲層面
- 中間件層面(包括了數(shù)據(jù)庫,應(yīng)用服務(wù)器中間件)
- 軟件層面(包括了數(shù)據(jù)庫SQL和存儲過程,邏輯層,前端展現(xiàn)層等)
那么一個業(yè)務(wù)系統(tǒng)應(yīng)用功能出現(xiàn)問題了,我們當(dāng)然也可以從動態(tài)層面來看實(shí)際一個應(yīng)用請求從調(diào)用開始究竟經(jīng)過了哪些代碼和硬件基礎(chǔ)設(shè)施,通過分段方法來定位和查詢問題。
比如我們常見的就是一個查詢功能如果出現(xiàn)問題了,首先就是找到這個查詢功能對應(yīng)的SQL語句在后臺查詢是否很慢,如果這個SQL本身就慢,那么就要優(yōu)化優(yōu)化SQL語句。如果SQL本身快但是查詢慢,那就要看下是否是前端性能問題或者集群問題等。
軟件代碼的問題往往是最不能忽視的一個性能問題點(diǎn)
對于業(yè)務(wù)系統(tǒng)性能問題,我們經(jīng)常想到的就是要擴(kuò)展數(shù)據(jù)庫的硬件性能,比如擴(kuò)展CPU和內(nèi)存,擴(kuò)展集群,但是實(shí)際上可以看到很多應(yīng)用的性能問題并不是硬件性能導(dǎo)致的,而是由于軟件代碼性能引起的。對于軟件代碼常見的性能問題我在以往的博客文章里面也談過到,比較典型的包括了。
- 循環(huán)中初始化大的結(jié)構(gòu)對象,數(shù)據(jù)庫連接等
- 資源不釋放導(dǎo)致的內(nèi)存泄露等
- 沒有基于場景需求來適度通過緩存等方式提升性能
- 長周期事務(wù)處理耗費(fèi)資源
- 處理某一個業(yè)務(wù)場景或問題的時候,沒有選擇最優(yōu)的數(shù)據(jù)結(jié)構(gòu)或算法
以上都是常見的一些軟件代碼性能問題點(diǎn),而這些往往需要通過我們進(jìn)行Code Review或代碼評審的方式才能夠發(fā)現(xiàn)出來。因此如果要做全面的性能優(yōu)化,對于軟件代碼的性能問題排查是必須的。
其次就是可以通過APM性能監(jiān)控工具來發(fā)現(xiàn)性能問題。
傳統(tǒng)模式下,當(dāng)出現(xiàn)CPU或內(nèi)存滿負(fù)荷的時候,如果要查找到具體是哪個應(yīng)用,哪個進(jìn)程或者具體哪個業(yè)務(wù)功能,哪個sql語句導(dǎo)致的往往并不是容易的事情。在實(shí)際的性能問題優(yōu)化中往往也需要做大量的日志分析和問題定位,最終才可能找到問題點(diǎn)。
而通過APM可以很好的解決這個問題。
比如在我們最近的項(xiàng)目實(shí)施中,結(jié)合APM和服務(wù)鏈監(jiān)控,我們可以快速的發(fā)現(xiàn)究竟是哪個服務(wù)調(diào)用出現(xiàn)了性能問題,或者快速的定位出哪個SQL語句有驗(yàn)證的性能問題。這個都可以幫助我們快速的進(jìn)行性能問題分析和診斷。
資源上承載的是應(yīng)用,應(yīng)用本身又包括了數(shù)據(jù)庫和應(yīng)用中間件容器,同時也包括了前端;在應(yīng)用之上則是對應(yīng)到具體的業(yè)務(wù)功能。因此APM一個核心就是要將資源-》應(yīng)用-》功能之間進(jìn)行整合分析和銜接。通過APM來發(fā)現(xiàn)應(yīng)用運(yùn)行中的性能問題并解決。