




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、 基于支付場景下的微服務改造與性能優(yōu)化 一、支付場景的介紹本章主要介紹基于支付場景下的微服務實踐,微服務體現(xiàn)的真諦最終還是要理解業(yè)務,只有深入理解了業(yè)務才能結合領域來重新定義微服務,下面就簡單介紹一下互聯(lián)網支付。常見的互聯(lián)網支付的使用場景主要有以下幾種。刷卡支付:用戶展示微信錢包內的“刷卡條碼/二維碼”給商戶系統(tǒng),掃描后直接完成支付,適用于線下面對面收銀的場景,如超市、便利店等(被掃,線下)。掃碼支付:商戶系統(tǒng)按微信支付協(xié)議生成支付二維碼,用戶再用微信“掃一掃”來完成支付,適用于PC網站支付、實體店單品等場景(主掃,線上)。公眾號支付:用戶在微信中打開商戶的H5頁面,商戶在H5頁面通過調用微信
2、支付提供的JSAPI接口調用微信支付模塊來完成支付,適用于在公眾號、朋友圈、聊天窗口等微信內完成支付的場景。WAP支付:基于公眾號基礎開發(fā)的一種非微信內瀏覽器支付方式(需要單獨申請支付權限),可以滿足在微信外的手機H5頁面進行微信支付的需求!簡單來說,就是通過PC、手機網頁來實現(xiàn)下單支付(俗稱H5支付)。App支付:商戶通過在移動端應用App中集成開放SDK調用微信支付模塊來完成支付。網關支付:用戶需要開通網上銀行后在線完成支付,主要對象是國內銀行借記卡和信用卡,是銀行系統(tǒng)為企業(yè)或個人提供的安全、快捷、穩(wěn)定的支付服務。快捷支付:快捷支付指用戶購買商品時,不需開通網銀,只需提供銀行卡卡號、戶名、
3、手機號碼等信息,銀行驗證手機號碼正確性后,第三方支付發(fā)送手機動態(tài)口令到用戶手機號上,用戶輸入正確的手機動態(tài)口令即可完成支付。在支付場景下實現(xiàn)微服務的最終目標:能夠將單體支付系統(tǒng)按業(yè)務進行解耦,利用微服務生態(tài)來實施支付系統(tǒng),并且能夠保證系統(tǒng)的可靠性和并發(fā)能力,建設完整的運維體系以支撐日益龐大的微服務系統(tǒng)。二、支付業(yè)務建模和服務劃分我們在第2章介紹了領域建模的相關知識,由此可以知道幾個關鍵詞:領域、子域、限界上下文。有些讀者對領域、子域的概念比較容易理解,但是限界上下文就理解得比較模糊,這里再對這個關鍵詞簡單做一下介紹。可以把限界上下文理解為:一個系統(tǒng)、一個應用、一個服務或一個組件,而它又存在于領
4、域之中。舉個生活中的例子:我每天上班都會坐地鐵,從家里出發(fā)到單位需要換乘三次地鐵,分別是5號線、8號線和2號線。那么地鐵就可以理解為限界上下文,從5號線走到8號線這個過程就是領域事件,而為了到達目的地中間換乘地鐵,這個過程叫作上下文切換。再回到支付業(yè)務中,該如何根據(jù)業(yè)務和領域相關知識來劃分服務呢?我們以一個業(yè)務架構示例來講解,如圖11-1所示。當我們在工作中遇到一個完整的業(yè)務場景時,首先需要識別出一共有哪些領域,根據(jù)大的領域再來劃分子域,最后將具有相同領域或子域的限界上下文進行歸類。正確識別出領域其實是比較難的,需要設計人員前期對業(yè)務有大量的調研,有比較深入的了解后才能識別領域。從圖11-1中
5、可以看到整個業(yè)務架構圖分兩大部分,中間的是業(yè)務核心領域,兩邊的是支撐子域。我們重點介紹中間的部分,每一層就是一個領域,領域中又包括特定子域。(1)對接業(yè)務層:主要是一些業(yè)務系統(tǒng)對接支付系統(tǒng),包括電商業(yè)務、互金業(yè)務和一鍵支付三個限界上下文。(2)統(tǒng)一接入網關層:主要功能是對請求入口進行加解密、分流、限流和準入控制等。圖11-1(3)產品服務層。收銀臺:包括兩個限界上下文,分別是PC收銀臺和手機手銀臺。商戶:包括四個限界上下文,分別是分賬、鑒權、擔保和代扣。個人:包括兩個限界上下文,分別是充值和提現(xiàn)。(4)業(yè)務服務層:包括五個限界上下文,分別是交易服務、支付服務、退款服務、計費服務和風控服務。(5
6、)基礎服務層。網關:包括三個限界上下文,分別是支付網關、鑒權和支付路由。資金處理平臺:包括四個限界上下文對賬、清結算、備付金和會計。三、支付場景下微服務架構的詳解與分析使用微服務的核心是業(yè)務,沒有業(yè)務進行支撐的微服務是“虛的”,但只有業(yè)務與微服務相結合的思想而沒有微服務的架構體系也是無法將微服務落地的,所以本章重點介紹要做好微服務還需要完善哪些技術架構。下面我們將以一個實際工作中的案例為出發(fā)點,分析在中小公司中如何落地微服務。如圖11-2所示,左半部分是微服務的業(yè)務架構,右半部分是微服務的基礎技術架構。圖11-23.1 業(yè)務架構分析根據(jù)前面介紹的如何根據(jù)業(yè)務來劃分領域可以看到,整個業(yè)務架構部分
7、已經完成了領域的劃分,我們重點來看服務層。服務層是一個核心域里面包含了多個子域,每個子域都是按功能進行劃分的,比如支付中心子域里面包括支付服務、路由系統(tǒng)和銀行渠道等限界上下文,這些限界上下文是一個服務,還是一個系統(tǒng)呢?這就要結合康威定律來綜合考量團隊的規(guī)模,小公司創(chuàng)業(yè)初期研發(fā)人員少,可以將支付中心子域定義為限界上下文,里面包括三個獨立模塊,分別是支付服務模塊、路由模塊和銀行渠道模塊,待人員逐步增加到一定規(guī)模后,多個項目組同時修改一個支付中心限界上下文會導致互相影響的時候,就需要將支付中心上升為一個業(yè)務領域,而將之前的三個獨立模塊拆分為獨立系統(tǒng),由不同的項目組分別接管,各自維護各自部署,如圖11
8、-3所示。圖11-3可以看出左邊是未拆分前的結構,交易服務想要調用支付模塊就必須統(tǒng)一調用支付系統(tǒng),然后才能調用支付模塊,而右邊是經過拆分后的結構,這時交易服務可以直接調用支付服務系統(tǒng)、路由系統(tǒng)和銀行渠道系統(tǒng)中的任意一個,當然從業(yè)務流來講肯定要先調用支付服務系統(tǒng)。而數(shù)據(jù)層是根據(jù)業(yè)務進行數(shù)據(jù)庫的拆分,拆分原則與應用拆分相同,如圖11-4所示。圖11-4可以看到業(yè)務、應用和數(shù)據(jù)庫三者一體,物理上與其他業(yè)務隔離,不同應用服務的數(shù)據(jù)庫是不能直接訪問的,只能通過服務調用進行訪問。3.2 技術平臺詳解當我們將整個支付業(yè)務根據(jù)微服務理念做了合理劃分之后,業(yè)務架構的各層次就逐步清晰起來,而微服務架構的成功建設除
9、了業(yè)務上面的劃分,技術平臺和運維體系的支撐也是非常重要的,圖11-2的右半部分共分為三個層次,分別是統(tǒng)一平臺業(yè)務層、微服務基礎中間件層和自動化運維層。1) 統(tǒng)一業(yè)務平臺層這一層主要是通用的平臺業(yè)務系統(tǒng),包括數(shù)據(jù)分析服務、商戶運營服務、運維管控服務和進件報備服務,它們無法根據(jù)業(yè)務被歸類到某一業(yè)務系統(tǒng)中,只能作為支撐域存在,所以放到統(tǒng)一業(yè)務平臺層供所有業(yè)務線共同使用。2)微服務基礎中間件層微服務本身是一個生態(tài),為了支撐微服務這個龐大的體系,必須有很多基礎中間件進行輔助才能使微服務平穩(wěn)地運行。下面將根據(jù)筆者積累的實踐經驗對圖中一些重要的組件進行技術選型方面的介紹,另外圖中有很多組件在本書其他章節(jié)進行
10、了詳細介紹,這里就不再做說明。微服務框架目前市面上非常流行Spring Boot+Spring Cloud的微服務框架,這套框架確實是微服務的集大成者,涵蓋的范圍廣,可以支持動態(tài)擴展和多種插件。但是作為公司的管理者來說,并不能因為出了新的技術就立刻將公司核心業(yè)務用新的技術進行更替,這樣在生產上所帶來的風險將會非常大。比較合理的做法是,如果公司或部門是新成立的,還沒有做技術框架的選型,又想在公司內部推廣微服務的時候,嘗試使用Spring Boot和Spring Cloud框架,可以節(jié)省出公司或部門的很多時間來攻關前端業(yè)務,而不需要將更多精力放在如何進行微服務的建設上來。目前很多互聯(lián)網公司在生產過
11、程中使用的微服務框架并不是Spring Boot和Spring Cloud,會使用如Dubbo、gRPC、Thrift等RPC框架進行服務治理,而公司內部自己研發(fā)出很多微服務的外圍組件,比如APM監(jiān)控系統(tǒng)、分庫分表組件、統(tǒng)一配置中心、統(tǒng)一定時任務等。在這種情況下公司內部已經自建了比較完善的基礎架構平臺就沒必要整體更換為Spring Boot和Spring Cloud,否則代價極大,甚至會對公司的業(yè)務造成嚴重的后果。公司發(fā)展的策略一般都是以客戶(用戶)穩(wěn)定優(yōu)先,但公司技術也需要更新,可以先嘗試在公司邊緣業(yè)務中使用,達到認可后逐步推廣,循環(huán)漸進。筆者在進行微服務改造的過程中實際上是基于原有的Dub
12、bo做的改進,將Duboo和Spring Boot相結合形成服務治理框架。消息服務我們在談技術選型的時候,不能脫離業(yè)務空談選型,每種消息中間件必定有其優(yōu)點和不足,我們可以根據(jù)自身的場景擇優(yōu)選擇,下面筆者結合自己使用的兩種類型的MQ簡單說一下選型與使用場景。RabbitMQ是使用Erlang編寫的一個開源的消息隊列,本身支持很多協(xié)議:AMQP、XMPP、SMTP、STOMP,也正是如此,使它變得非常重量級,更適合企業(yè)級的開發(fā)。RabbitMQ是AMQP協(xié)議領先的一個實現(xiàn),它實現(xiàn)了代理(Broker)架構,意味著消息在發(fā)送到客戶端之前可以在中央節(jié)點上排隊。對路由(Routing)、負載均衡(Loa
13、d balance)或數(shù)據(jù)持久化都有很好的支持。但是在集群中使用的時候,分區(qū)配置不當偶爾會有腦裂現(xiàn)象出現(xiàn),總的來說,在支付行業(yè)用RabbitMQ還是非常多的。Kafka是LinkedIn于2010年12月開發(fā)并開源的一個分布式MQ系統(tǒng),現(xiàn)在是Apache的一個孵化項目,是一個高性能跨語言分布式Publish/Subscribe消息隊列系統(tǒng),其性能和效率在行業(yè)中是領先的,但是原先的版本經過大量測試,因為其主備Partition同步信息的機制問題,偶爾會造成數(shù)據(jù)丟失等問題,所以更多的應用場景還是在大數(shù)據(jù)、監(jiān)控等領域。目前市面上有很多支付公司都在使用RabbitMQ作為消息中間件,雖然很“重”但是卻
14、具有支付行業(yè)的不丟消息、MQ相對穩(wěn)定等特點。缺點則是不像ActiveMQ那樣可以使用Java實現(xiàn)定制化,比如想知道消息隊列中有多少剩余消息沒有消費,哪些通道獲取過消息,共有多少條,是否可以手動或自動觸發(fā)重試等,還有監(jiān)控和統(tǒng)計信息,目前做得還不是太完善,只能滿足基本功能的要求。接下來我們再來說說消息隊列在技術領域的使用場景。(1)可以做延遲設計。比如一些數(shù)據(jù)需要過五分鐘后再使用,這時就需要使用延遲隊列設計,比如在RabbitMQ中利用死信隊列實現(xiàn)。(2)異步處理。主要應用在多任務執(zhí)行的場景。(3)應用解耦。在大型微服務架構中,有一些無狀態(tài)的服務經??紤]使用MQ做消息通知和轉換。(4)分布式事務最
15、終一致性??梢允褂没谙⒅虚g件的隊列做分布式事務的消息補償,實現(xiàn)最終一致性。(5)流量削峰。一般在秒殺或團搶活動中使用廣泛,可以通過隊列控制秒殺的人數(shù)和商品,還可以緩解短時間壓垮應用系統(tǒng)的問題。(6)日志處理。我們在做監(jiān)控或日志采集的時候經常用隊列來做消息的傳輸和暫存。統(tǒng)一配置中心目前市面有很多種開源的統(tǒng)一配置中心組件可供使用,如攜程開源的Apollo、阿里的Diamond、百度的Disconf,每種組件都各有特點,我們在使用的過程中還需要根據(jù)實際情況來綜合考量。筆者公司目前采用的微服務架構是Spring Boot+Dubbo的方式,Apollo的架構使用了Spring Boot+Sprin
16、gCloud的方式,在架構方式上正好可以無縫對接,同時Apollo可以解決同城雙活方面的問題,所以從這些角度來看比較適合目前的場景。銀行通道監(jiān)控與切換由于每家銀行提供的業(yè)務及產品不同,例如B2C、B2B、大額支付、銀企直連、代收代付、快捷支付等,這些產品及服務并無統(tǒng)一的接口,要使用這些產品服務,支付機構只能一家家銀行進行接入,當對接的銀行通道過多時,每條通道的穩(wěn)定性就是支付工作中的重中之重,這是涉及用戶支付是否成功的關鍵,也是支付機構支付成功率的重要指標,基于此,要有針對性地進行銀行通道穩(wěn)定性的監(jiān)控與故障切換系統(tǒng)的建設,如圖11-5所示。圖11-5圖11-5是通道監(jiān)控與切換系統(tǒng)的整體架構,通過
17、在相應組件或應用上面增加Agent監(jiān)控代理攔截通道的請求情況,經過Collector進行數(shù)據(jù)匯總,然后將通道評分數(shù)據(jù)發(fā)送給Redis集群,而支付路由系統(tǒng)在進行通道選取的時候會從Redis集群中獲取通道的評分及通道相應的配置項進行綜合評定從而選取合適的通道,另外采集所有的監(jiān)控數(shù)據(jù)都會存放到InfluxDB中,通過Grafana進行預警展示,如果通道不可用則自動將通道關閉,同時通知研發(fā)部門進行問題排查。四、從代碼層面提升微服務架構的性能很多架構變遷或演進方面的文章大多是針對架構方面的介紹,很少有針對代碼級別的性能優(yōu)化介紹,這就好比蓋樓一樣,樓房的基礎架子搭得很好,但是蓋房的工人不夠專業(yè),有很多需要
18、注意的地方忽略了,在往里面添磚加瓦的時候出了問題,后果就是房子經常漏雨、墻上有裂縫等各種問題出現(xiàn),雖然不至于樓房塌陷,但樓房已經變成了危樓。判斷一個項目是否具有良好的設計需要從優(yōu)秀的代碼和高可用架構兩個方面來衡量,如圖11-6所示。圖11-6優(yōu)秀的代碼是要看程序的結構是否合理,程序中是否存在性能問題,依賴的第三方組件是否被正確使用等。而高可用架構是要看項目的可用性、擴展型,以及能夠支持的并發(fā)能力??梢哉f一個良好的項目設計是由兩部分組成的,缺一不可。4.1 從代碼和設計的角度看在實戰(zhàn)的過程中,不同的公司所研發(fā)的項目和場景也不一樣,下面主要以支付場景為出發(fā)點,從代碼和設計的角度總結一些常見的問題。
19、)數(shù)據(jù)庫經常發(fā)生死鎖現(xiàn)象以MySQL數(shù)據(jù)庫為例,select.for update語句是手工加鎖(悲觀鎖)語句,是一種行級鎖。通常情況下單獨使用select語句不會對數(shù)據(jù)庫數(shù)據(jù)加鎖,而使用for update語句則可以在程序層面實現(xiàn)對數(shù)據(jù)的加鎖保護,如果for update語句使用不當,則非常容易造成數(shù)據(jù)庫死鎖現(xiàn)象的發(fā)生,如表11-1所示。表11-1時間會話A會話B1事務開始2Select * from test where age = 10 for update事務開始3Select * from test where age = 20 for update4Select * from te
20、st where age = 20 for update此時事務一直等待會話BSelect * from test where age = 10 for update數(shù)據(jù)庫報錯:Deadlock found when trying to get lock; try restarting transaction在上述事例中,會話B會拋出死鎖異常,死鎖的原因就是A和B兩個會話互相等待,出現(xiàn)這種問題其實就是我們在項目中混雜了大量的事務+for update語句并且使用不當所造成的。MySQL數(shù)據(jù)庫鎖主要有三種基本鎖。Record Lock:單個行記錄的鎖。Gap Lock:間隙鎖,鎖定一個范圍,但不
21、包括記錄本身。Gap Lock+Record Lock(next-key lock):鎖定一個范圍,并且也鎖定記錄本身。當for update語句和gap lock、next-key lock鎖相混合使用,又沒有注意用法的時候,就非常容易出現(xiàn)死鎖的情況。2)數(shù)據(jù)庫事務占用時間過長先看一段偽代碼:public void test() Transaction.begin /事務開啟 try dao.insert /插入一行記錄 httpClient.queryRemoteResult() /請求訪問 dao.update /更新一行記錄 Tmit()/事務提交 catch(Exception e)
22、 Transaction.rollFor/事務回滾 項目中類似這樣的程序有很多,經常把類似httpClient,或者有可能造成長時間超時的操作混在事務代碼中,不僅會造成事務執(zhí)行時間超長,而且會嚴重降低并發(fā)能力。我們在使用事務的時候,遵循的原則是快進快出,事務代碼要盡量小。針對以上偽代碼,我們要把httpClient這一行拆分出來,避免同事務性的代碼混在一起。3)濫用線程池,造成堆和棧溢出Java通過Executors提供了四種線程池可供我們直接使用。newCachedThreadPool:創(chuàng)建一個可緩存線程池,這個線程池會根據(jù)實際需要創(chuàng)建新的線程,如果有空閑的線程,則空閑的線程也會被重復利用。
23、newFixedThreadPool:創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待。newScheduledThreadPool:創(chuàng)建一個定長線程池,支持定時及周期性任務執(zhí)行。newSingleThreadExecutor:創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務,保證所有任務按照指定順序(FIFO,LIFO,優(yōu)先級)執(zhí)行。JDK提供的線程池從功能上替我們做了一些封裝,也節(jié)省了很多參數(shù)設置的過程。如果使用不當則很容易造成堆和棧溢出的情況,示例代碼如下所示。private staticfinal ExecutorService executorServic
24、e = Executors.newCachedThreadPool(); /* * 異步執(zhí)行短頻快的任務 * param task */ public static voidasynShortTask(Runnable task) executorService.submit(task); /task.run(); CommonUtils.asynShortTask(newRunnable() Override public void run() String sms =sr.getSmsContent(); sms = sms.replaceAll(finalCode, AES.encryp
25、tToBase64(finalCode,ConstantUtils.getDB_AES_KEY(); sr.setSmsContent(sms); smsManageService.addSmsRecord(sr); );以上代碼的場景是每次請求過來都會創(chuàng)建一個線程,將DUMP日志導出進行分析,發(fā)現(xiàn)項目中啟動了一萬多個線程,而且每個線程都顯示為忙碌狀態(tài),已經將資源耗盡。我們仔細查看代碼會發(fā)現(xiàn),代碼中使用的線程池是使用以下代碼來申請的。private static final ExecutorServiceexecutorService = Executors.newCachedThreadPo
26、ol();在高并發(fā)的情況下,無限制地申請線程資源會造成性能嚴重下降,采用這種方式最大可以產生多少個線程呢?答案是Integer的最大值!查看如下源碼:public static ExecutorServicenewCachedThreadPool() return newThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, newSynchronousQueue(); 既然使用newCachedThreadPool可能帶來棧溢出和性能下降,如果使用newFixedThreadPool設置固定長度是不是可以解決問題呢?使用
27、方式如以下代碼所示,設置固定線程數(shù)為50:private static final ExecutorServiceexecutorService = Executors.newFixedThreadPool(50);修改完成以后,并發(fā)量重新上升到100TPS以上,但是當并發(fā)量非常大的時候,項目GC(垃圾回收能力下降),分析原因還是因為Executors.newFixedThreadPool(50)這一行,雖然解決了產生無限線程的問題,但采用newFixedThreadPool這種方式會造成大量對象堆積到隊列中無法及時消費,源碼如下:public static ExecutorService n
28、ewFixedThreadPool(int nThreads,ThreadFactory threadFactory) return newThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, newLinkedBlockingQueue(), threadFactory); 可以看到采用的是無界隊列,也就是說隊列可以無限地存放可執(zhí)行的線程,造成大量對象無法釋放和回收。其實JDK還提供了原生的線程池ThreadPoolExecutor,這個線程池基本上把控制的權力交給了使用者,使用者設置線程池的大小、任務隊列、拒絕策
29、略、線程空閑時間等,不管使用哪種線程池,都是建立在我們對其精準把握的前提下才能真正使用好。4)常用配置信息依然從數(shù)據(jù)庫中讀取不管是什么業(yè)務場景的項目,只要是老項目,我們經常會遇到一個非常頭疼的問題就是項目的配置信息是在本地項目的properties文件中存放的,或者是將常用的配置信息存放到數(shù)據(jù)庫中,這樣造成的問題是:如果使用本地properties文件,每次修改文件都需要一臺一臺地在線上環(huán)境中修改,在服務器數(shù)量非常多的情況下非常容易出錯,如果修改錯了則會造成生產事故。如果是用采集數(shù)據(jù)庫來統(tǒng)一存放配置信息,在并發(fā)量非常大的情況下,每一次請求都要讀取數(shù)據(jù)庫配置則會造成大量的I/O操作,會對數(shù)據(jù)庫造
30、成較大的壓力,嚴重的話對項目也會產生性能影響。比較合理的解決方案之一:使用統(tǒng)一配置中心利用緩存對配置信息進行統(tǒng)一管理,具體的實現(xiàn)方案可以參考深入分布式緩存這本書。5)從庫中查詢數(shù)據(jù),每次全部取出我們在代碼中經常會看到如下SQL語句:select * from order where status = init這句SQL從語法上確實看不出什么問題,但是放在不同的環(huán)境上卻會產生不同的效果,如果此時我們的數(shù)據(jù)庫中狀態(tài)為init的數(shù)據(jù)只有100條,那么這條SQL會非??斓夭樵兂鰜聿⒎祷亟o調用端,在這種情況下對項目沒有任何影響。如果此時我們的數(shù)據(jù)庫中狀態(tài)為init的數(shù)據(jù)有10萬條,那么這條SQL語句的執(zhí)
31、行結果將是一次性把10萬記錄全部返回給調用端,這樣做不僅會給數(shù)據(jù)庫查詢造成沉重的壓力,還會給調用端的內存造成極大的影響,帶來非常不好的用戶體驗。比較合理的解決方案之一:使用limit關鍵字控制返回記錄的數(shù)量。6)業(yè)務代碼研發(fā)不考慮冪等操作冪等就是用戶對于同一操作發(fā)起的一次請求或多次請求所產生的結果是一致的,不會因為多次點擊而產生多種結果。以支付場景為例,用戶在網上購物選擇完商品后進行支付,因為網絡的原因銀行卡上面的錢已經扣了,但是網站的支付系統(tǒng)返回的結果卻是支付失敗,這時用戶再次對這筆訂單發(fā)起支付請求,此時會進行第二次扣款,返回結果成功,用戶查詢余額返發(fā)現(xiàn)多扣錢了,流水記錄也變成了兩條,這種場
32、景就不是冪等。實際工作中的冪等其實就是對訂單進行防重,防重措施是通過在某條記錄上加鎖的方式進行的。針對以上問題,完全沒有必要使用悲觀鎖的方式來進行防重,否則不僅對數(shù)據(jù)庫本身造成極大的壓力,對于項目擴展性來說也是很大的擴展瓶頸,我們采用了三種方法來解決以上問題:使用第三方組件來做控制,比如ZooKeeper、Redis都可以實現(xiàn)分布式鎖。使用主鍵防重法,在方法的入口處使用防重表,能夠攔截所有重復的訂單,當重復插入時數(shù)據(jù)庫會報一個重復錯,程序直接返回。使用版本號(version)的機制來防重。注意:以上三種方式都必須設置過期時間,當鎖定某一資源超時的時候,能夠釋放資源讓競爭重新開始。7)使用緩存不
33、合理,存在驚群效應、緩存穿透等情況緩存穿透我們在項目中使用緩存通常先檢查緩存中數(shù)據(jù)是否存在,如果存在則直接返回緩存內容,如果不存在就直接查詢數(shù)據(jù)庫,然后進行緩存并將查詢結果返回。如果我們查詢的某一數(shù)據(jù)在緩存中一直不存在,就會造成每一次請求都查詢DB,這樣緩存就失去了意義,在流量大時,可能DB就“掛掉”了,這就是緩存穿透,如圖11-7所示。圖11-7要是有黑客利用不存在的緩存key頻繁攻擊應用,就會對數(shù)據(jù)庫造成非常大的壓力,嚴重的話會影響線上業(yè)務的正常進行。一個比較巧妙的做法是,可以將這個不存在的key預先設定一個值,比如“key”“NULL”。在返回這個NULL值的時候,應用就可以認為這是不存
34、在的key,應用就可以決定是繼續(xù)等待訪問,還是放棄掉這次操作。如果繼續(xù)等待訪問,則過一個時間輪詢點后,再次請求這個key,如果取到的值不再是NULL,則可以認為這時候key有值了,從而避免透傳到數(shù)據(jù)庫,把大量的類似請求擋在了緩存之中。緩存并發(fā)看完上面的緩存穿透方案后,可能會有讀者提出疑問,如果第一次使用緩存或緩存中暫時沒有需要的數(shù)據(jù),那么又該如何處理呢?在這種場景下,客戶端從緩存中根據(jù)key讀取數(shù)據(jù),如果讀到了數(shù)據(jù)則流程結束,如果沒有讀到數(shù)據(jù)(可能會有多個并發(fā)都沒有讀到數(shù)據(jù)),則使用緩存系統(tǒng)中的setNX方法設置一個值(這種方法類似加鎖),沒有設置成功的請求則“sleep”一段時間,設置成功的
35、請求則讀取數(shù)據(jù)庫獲取值,如果獲取到則更新緩存,流程結束,之前sleep的請求喚醒后直接從緩存中讀取數(shù)據(jù),此時流程結束,如圖11-8所示。圖11-8這個流程里面有一個漏洞,如果數(shù)據(jù)庫中沒有我們需要的數(shù)據(jù)該怎么處理?如果不處理請求則會造成死循環(huán),不斷地在緩存和數(shù)據(jù)庫中查詢,這時就可以結合緩存穿透的思路,這樣其他請求就可以根據(jù)“NULL”直接進行處理,直到后臺系統(tǒng)在數(shù)據(jù)庫成功插入數(shù)據(jù)后同步更新清理NULL數(shù)據(jù)和更新緩存。緩存過期導致驚群效應我們在使用緩存組件的時候,經常會使用緩存過期這一功能,這樣可以不定期地釋放使用頻率很低的緩存,節(jié)省出緩存空間。如果很多緩存設置的過期時間是一樣的,就會導致在一段時
36、間內同時生成大量的緩存,然后在另外一段時間內又有大量的緩存失效,大量請求就直接穿透到數(shù)據(jù)庫中,導致后端數(shù)據(jù)庫的壓力陡增,這就是“緩存過期導致的驚群效應”!比較合理的解決方案之一:為每個緩存的key設置的過期時間再加一個隨機值,可以避免緩存同時失效。最終一致性緩存的最終一致性是指當后端的程序在更新數(shù)據(jù)庫數(shù)據(jù)完成之后,同步更新緩存失敗,后續(xù)利用補償機制對緩存進行更新,以達到最終緩存的數(shù)據(jù)與數(shù)據(jù)庫的數(shù)據(jù)是一致的狀態(tài)。常用的方法有兩種,分別是基于MQ和基于binlog的方式。(1)基于MQ的緩存補償方案。這種方案是當緩存組件出現(xiàn)故障或網絡出現(xiàn)抖動的時候,程序將MQ作為補償?shù)木彌_隊列,通過重試的方式機制
37、更新緩存,如圖11-9所示。圖11-9說明:應用同時更新數(shù)據(jù)庫和緩存。如果數(shù)據(jù)庫更新成功,則開始更新緩存;如果數(shù)據(jù)庫更新失敗,則整個更新過程失敗。判斷更新緩存是否成功,如果成功則返回。如果緩存沒有更新成功,則將數(shù)據(jù)發(fā)到MQ中。應用監(jiān)控MQ通道,收到消息后繼續(xù)更新Redis。問題點:如果更新Redis失敗,同時在將數(shù)據(jù)發(fā)到MQ之前應用重啟了,那么MQ就沒有需要更新的數(shù)據(jù),如果Redis對所有數(shù)據(jù)沒有設置過期時間,同時在讀多寫少的場景下,那么只能通過人工介入來更新緩存。(2)基于binlog的方式來實現(xiàn)統(tǒng)一緩存更新方案。第一種方案對于應用的研發(fā)人員來講比較“重”,需要研發(fā)人員同時判斷據(jù)庫和Redi
38、s是否成功來做不同的考慮,而使用binlog更新緩存的方案能夠減輕業(yè)務研發(fā)人員的工作量,并且也有利于形成統(tǒng)一的技術方案,如圖11-10所示。圖11-10說明:應用直接寫數(shù)據(jù)到數(shù)據(jù)庫中。數(shù)據(jù)庫更新binlog日志。利用Canal中間件讀取binlog日志。Canal借助于限流組件按頻率將數(shù)據(jù)發(fā)到MQ中。應用監(jiān)控MQ通道,將MQ的數(shù)據(jù)更新到Redis緩存中??梢钥吹竭@種方案對研發(fā)人員來說比較輕量,不用關心緩存層面,雖然這個方案實現(xiàn)起來比較復雜,但卻容易形成統(tǒng)一的解決方案。問題點:這種方案的弊端是需要提前約定緩存的數(shù)據(jù)結構,如果使用者采用多種數(shù)據(jù)結構來存放數(shù)據(jù),則方案無法做成通用的方式,同時極大地增
39、加了方案的復雜度。8)程序中打印了大量的無用日志,并且引起性能問題先來看一段偽代碼:QuataDTO quataDTO = null;try quataDTO = getRiskLimit(payRequest.getQueryRiskInfo(),payRequest.getMerchantNo(), payRequest.getIndustryCatalog(),cardBinResDTO.getCardType(), cardBinResDTO.getBankCode(), bizName); catch (Exception e) (獲取風控限額異常, e);通過上面的代碼,發(fā)現(xiàn)了以下
40、需要注意的點:日志的打印必須以logger.error或logger.warn的方式打印出來。日志打印格式:系統(tǒng)來源 錯誤描述 關鍵信息,日志信息要打印出能看懂的信息,有前因和后果。甚至有些方法的入參和出參也要考慮打印出來。在輸入錯誤信息的時候,Exception不要以e.getMessage的方式打印出來。合理地日志打印,可以參考如下格式:logger.warn(innersys - + exceptionType.description + - + methodName + - +errorCode: + errorCode + , +errorMsg: + errorMsg + , e)
41、; (innersys - 入參 - +methodName + - + LogInfoEncryptUtil.getLogString(arguments)+ ); (innersys - 返回結果 - +methodName + - + LogInfoEncryptUtil.getLogString(result);在程序中大量地打印日志,雖然能夠打印很多有用信息幫助我們排查問題,但日志量太多不僅影響磁盤I/O,還會造成線程阻塞,對程序的性能造成較大影響。在使用Log4j1.2.14設置ConversionPattern的時候,使用如下格式:%d %-5p %c:%L %t - %m%n在
42、對項目進行壓測的時候卻發(fā)現(xiàn)了大量的鎖等待,如圖11-11所示。圖11-11對Log4j進行源碼分析,發(fā)現(xiàn)在org.apache.log4j.spi.LocationInfo類中有如下代碼:String s;/ Protect against multiple access to sw.synchronized(sw) t.printStackTrace(pw); s = sw.toString(); sw.getBuffer().setLength(0);/System.out.println(s is +s+.);int ibegin, iend;可以看出在該方法中用了synchronize
43、d鎖,然后又通過打印堆棧來獲取行號,于是將ConversionPattern的格式修改為%d %-5p %c %t - %m%n后,線程大量阻塞的問題解決了,極大地提高了程序的并發(fā)能力。9)關于索引的優(yōu)化組合索引的原則是偏左原則,所以在使用的時候需要多加注意。不需要過多地添加索引的數(shù)量,在添加的時候要考慮聚集索引和輔助索引,兩者的性能是有區(qū)別的。索引不會包含NULL值的列。只要列中包含NULL值都不會被包含在索引中,復合索引中只要有一列含有NULL值,那么這一列對于此復合索引就是無效的。所以我們在設計數(shù)據(jù)庫時不要讓字段的默認值為NULL。MySQL索引排序。MySQL查詢只使用一個索引,如果w
44、here子句中已經使用了索引,那么order by中的列是不會使用索引的。因此數(shù)據(jù)庫默認排序可以在符合要求的情況下不使用排序操作;盡量不要包含多個列的排序,如果需要,最好給這些列創(chuàng)建復合索引。使用索引的注意事項。以下操作符可以應用索引:m大于等于;mBetween;mIN;mLIKE 不以%開頭。以下操作符不能應用索引:mNOT IN;mLIKE %_開頭。4.2 從整體架構的角度看1)采用單體集群的部署模式當團隊和項目發(fā)展到一定規(guī)模后,就需要根據(jù)業(yè)務和團隊人數(shù)進行適當拆分。如果依然使用單體項目做整體部署,則項目之間互相影響極大,再加上團隊人員達到一定規(guī)模后,沒有辦法進行項目的維護和升級。2)
45、采用單機房的部署方式現(xiàn)在互聯(lián)網項目對穩(wěn)定性的要求越來越高,采用單機房部署的風險性也越來越高,像黑客惡意攻擊、機房斷電、網線損壞等不可預知的故障發(fā)生時,單機房是無法提供穩(wěn)定性保障的,這就需要互聯(lián)網企業(yè)開始建設同城雙活、異步多活等確保機房的穩(wěn)定性。3)采用Nginx+Hessian的方式實現(xiàn)服務化Hessian是一個輕量級的Remoting on HTTP框架,采用的是Binary RPC協(xié)議。因為其易用性等特點,直到現(xiàn)在依然有很多企業(yè)還在使用Hessian作為遠程通信工具,但Hessian并不具備微服務的特點,只作為遠程通信工具使用,而且Hessian多偏重于數(shù)據(jù)如何打包、傳輸與解包,所以很多時
46、候需要借助Nginx來做服務路由、負載和重試等,而且還需要在Nginx中進行配置,也不能動態(tài)對服務進行加載和卸載,所以在業(yè)務越來越復雜,請求量越來越多的情況下,Hessian不太適合作為微服務的服務治理框架,這時就需要Spring Cloud或Dubbo了。4)項目拆分不徹底,一個Tomcat共用多個應用(見圖11-12)圖11-12注:一個Tomcat中部署多個應用war包,彼此之間互相牽制,在并發(fā)量非常大的情況下性能降低非常明顯,如圖11-13所示。圖11-13注:拆分前的這種情況其實還是挺普遍的,之前一直認為項目中不會存在這種情況,但事實上還是存在了。解決的方法很簡單,每一個應用war只
47、部署在一個Tomcat中,這樣應用程序之間就不會存在資源和連接數(shù)的競爭情況,性能和并發(fā)能力提升較為明顯。5)無服務降級策略舉個例子來說明什么是服務降級,我們要出門旅游但只有一只箱子,我們想帶的東西太多了把箱子都塞滿了,結果發(fā)現(xiàn)還有很多東西沒有放,于是只能把所有東西全部再拿出來做對比和分類,找到哪些是必須要帶的,哪些是非必需的,最終箱子里面放滿了必需品,為了防止這種情況再次發(fā)生,下次再旅游的時候就可以提前多準備幾只箱子。其實服務降級也是類似的思路,在資源有限的情況下舍棄一些東西以保證更重要的事情能夠進行下去。服務降級的主要應用場景就是當微服務架構整體的負載超出了預設的上限閾值或即將到來的流量預計超過預設的閾值時,為了保證重要的服務能正常運行,將一些不重要、不緊急的服務延遲或暫停使用。6)支付運營報表,大數(shù)據(jù)量查詢我們先來回顧一下微服務的數(shù)據(jù)去中心化核心要點:每個微服務有自己私有的數(shù)據(jù)庫。每個微服務只能訪問自己的數(shù)據(jù)庫,而不能訪問其他服務的數(shù)據(jù)庫。某些業(yè)務場景下,請求除了要操作自己的數(shù)據(jù)庫,還要對其他服務的數(shù)據(jù)庫進行添加、刪除和修改等操作。在這種情況下不建議直接訪問其他服務的數(shù)據(jù)庫,而是通過調用每個服務提供
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 離職補償合同范本
- 環(huán)衛(wèi)項目合同范本
- 7 多元文化 多樣魅力 第3課時 教學設計-2023-2024學年道德與法治六年級下冊統(tǒng)編版
- 白楊教學設計
- 2023-2024學年泰山版信息技術(2018)第六冊《第一單元 裝扮美好生活 4 漂亮花瓶巧設計》教學設計
- Module 4 Unit 2 Children's Day(教學設計)-2023-2024學年牛津上海版(試用本)英語三年級下冊
- Module 12 help unit 3 language in use教學設計 -2024-2025學年外研版八年級英語上冊
- 新一年家長會教師的演講稿
- 21涼州詞教學設計-2024-2025學年四年級上冊語文統(tǒng)編版
- 秋季學期散學典禮校長講話稿
- 中草藥材種植基地項目申請報告
- 2022年南京鐵道職業(yè)技術學院單招職業(yè)技能題庫及答案解析
- 小兒急乳蛾(小兒急性扁桃體炎)中醫(yī)臨床路徑(2018年版)
- 地質災害安全教育 主題班會
- 市場營銷-OPPO手機市場營銷策略優(yōu)化研究
- 小學生主題班會 愛國主義教育 課件(共35張PPT)
- 煤礦安全生產管理能力管理機制與創(chuàng)新管理課件
- 造血細胞與基本檢驗方法-骨髓細胞基本形態(tài)及檢驗(血液學檢驗課件)
- 艾梅乙的實驗室診斷與溝通
- 新能源汽車驅動電機與控制技術高職PPT完整全套教學課件
- 小學綜合實踐課釘紐扣
評論
0/150
提交評論