高并發(fā)&高可用系統(tǒng)的常見應(yīng)對策略

解耦神器:MQ

MQ是分布式架構(gòu)中的解耦神器,應(yīng)用非常普遍。有些分布式事務(wù)也是利用MQ來做的劈愚。由于其高吞吐量,在一些業(yè)務(wù)比較復(fù)雜的情況闻妓,可以先做基本的數(shù)據(jù)驗證菌羽,然后將數(shù)據(jù)放入MQ,由消費者異步去處理后續(xù)的復(fù)雜業(yè)務(wù)邏輯由缆,這樣可以大大提高請求響應(yīng)速度注祖,提升用戶體驗。如果消費者業(yè)務(wù)處理比較復(fù)雜均唉,也可以獨立集群部署是晨,根據(jù)實際處理能力需求部署多個節(jié)點。需要注意的是:

  • 需要確認(rèn)消息發(fā)送MQ成功

比如RabbitMQ在發(fā)送消息到MQ時舔箭,就有發(fā)送回調(diào)確認(rèn)罩缴,雖然不能夠完全避免消息丟失,但也能夠避免一些極端情況下消息發(fā)送失敗的情況了层扶◇镎拢可以利用MQ的事務(wù)來避免更多情況的消息丟失

  • 消息持久化

需要注意配置消息持久化,避免MQ集群掛掉的情況下大量丟失消息的情況

  • 消息消費的冪等性

正常來說消息是不會重復(fù)發(fā)送的怒医,但是一些特殊情況也可能會導(dǎo)致消息重復(fù)發(fā)送給消費者炉抒,一般會在消息中加一個全局唯一的流水號,通過流水號來判斷消息是否已經(jīng)消費過

  • 注意用戶體驗

使用異步處理是在提高系統(tǒng)吞吐量考慮下的一種設(shè)計稚叹,相對于實時快速給用戶返回結(jié)果焰薄,肯定用戶體驗會更差一點,但這也是目前來說綜合考慮的一種不錯的方案了扒袖,因此在設(shè)計之初就需要評估是否需要異步處理塞茅,如果需要異步處理,那一定要考慮如何給用戶更友好的提示和引導(dǎo)季率。因為異步處理是技術(shù)實現(xiàn)結(jié)合實際業(yè)務(wù)情況的一種綜合解決方案野瘦,對于產(chǎn)品來說是不應(yīng)該關(guān)心的,需要技術(shù)人員主動盡早提出流程中異步處理的節(jié)點,在需求分析階段就考慮如何設(shè)計才能對用戶來說更加友好鞭光。如果在開發(fā)過程中才提出吏廉,很可能就會對用戶展示界面有較大調(diào)整,從而導(dǎo)致需求變更惰许、系統(tǒng)設(shè)計變更席覆,而后就是甩鍋、扯皮汹买、延期了

項目管理

代碼結(jié)構(gòu)和規(guī)范

  • 要注意代碼結(jié)構(gòu)的設(shè)計佩伤,提高代碼可重用率
  • 嚴(yán)格遵守代碼規(guī)范,代碼規(guī)范可以降低新成員的理解難度晦毙,也可以降低團(tuán)隊成員間互相理解的難度
  • 參考:https://my.oschina.net/dengfuwei/blog/1611917

人員管理

  • 分工要明確生巡,需要有隨時接收并處理問題的人員
  • 信息透明,團(tuán)隊成員需要對系統(tǒng)有足夠的了解见妒,需要讓團(tuán)隊成員有獨當(dāng)一面的能力
  • 知識庫孤荣,整理技術(shù)上、業(yè)務(wù)上的常見問題徐鹤、經(jīng)驗垃环,方便新成員快速理解并融入
  • 分享邀层,定期分享技術(shù)上返敬、業(yè)務(wù)上的知識,團(tuán)隊成員共同快速進(jìn)步寥院。適當(dāng)?shù)姆窒硐到y(tǒng)運(yùn)行成果劲赠,可以適當(dāng)鼓舞團(tuán)隊士氣
  • 適當(dāng)與業(yè)務(wù)溝通,了解一線業(yè)務(wù)需求和使用情況秸谢,以便不斷改善凛澎,也可以在系統(tǒng)設(shè)計上有更長遠(yuǎn)的考慮
  • 適當(dāng)用一些項目管理工具,適當(dāng)將一些工作進(jìn)行量化估蹄。不適合團(tuán)隊的成員也需要及時淘汰

模塊化設(shè)計

根據(jù)業(yè)務(wù)場景塑煎,將業(yè)務(wù)抽離成獨立模塊,對外通過接口提供服務(wù)臭蚁,減少系統(tǒng)復(fù)雜度和耦合度最铁,實現(xiàn)可復(fù)用,易維護(hù)垮兑,易拓展

項目中實踐例子:

Before:

在返還購 APP 里有個【我的紅包】的功能冷尉,用戶的紅包數(shù)據(jù)來自多個業(yè)務(wù),如:邀請新用戶注冊領(lǐng)取 100 元紅包系枪,大促活動雙倍紅包雀哨,等各種活動紅包,多個活動業(yè)務(wù)都實現(xiàn)了一套不同規(guī)則的紅包領(lǐng)取和紅包獎勵發(fā)放的機(jī)制,導(dǎo)致紅包不可管理雾棺,不能復(fù)用膊夹,難維護(hù)難拓展

After:

  • 重構(gòu)紅包業(yè)務(wù)

  • 紅包可后臺管理

  • 紅包信息管理,可添加捌浩,可編輯割疾,可配置紅包使用的規(guī)則,可管理用戶紅包

  • 紅包獎勵發(fā)放統(tǒng)一處理

  • 應(yīng)用業(yè)務(wù)的接入只需要專注給用戶進(jìn)行紅包發(fā)放即可

設(shè)計概要


hongbao_nt.jpg

Before VS After


hongbao2.png

產(chǎn)品有時提出的業(yè)務(wù)需求沒有往這方面去考慮嘉栓,結(jié)合場景和未來拓展需要宏榕,在需求討論的時候提出模塊化設(shè)計方案,并可以協(xié)助產(chǎn)品進(jìn)行設(shè)計


通用服務(wù)抽離

在項目開發(fā)中經(jīng)常會遇到些類似的功能侵佃,但是不同的開發(fā)人員都各自實現(xiàn)麻昼,或者因為不能復(fù)用又重新開發(fā)一個,導(dǎo)致了類似功能的重復(fù)開發(fā)馋辈,所以我們需要對能夠抽離獨立服務(wù)的功能進(jìn)行抽離抚芦,達(dá)到復(fù)用的效果,并且可以不斷拓展完善迈螟,節(jié)約了后續(xù)開發(fā)成本叉抡,提高開發(fā)效率,易于維護(hù)和拓展

項目中實踐例子:

Before

在業(yè)務(wù)中經(jīng)常需要對用戶進(jìn)行信息通知答毫,如:短信定時通知褥民,APP 消息推送,微信通知洗搂,等

開發(fā)人員在接到需求中有通知功能的時候沒有考慮后續(xù)拓展消返,就接入第三方信息通知平臺,然后簡單封裝個信息通知方法耘拇,后續(xù)也有類似信息通知需求的時候撵颊,另一個開發(fā)人員發(fā)現(xiàn)當(dāng)前這個通知方法無法滿足自己的需求,然后又自己去了解第三方平臺重新封裝了通知方法惫叛,或者后續(xù)需求加了定時通知的功能倡勇,開發(fā)人員針對業(yè)務(wù)去實現(xiàn)了個定時通知功能,但是只能自己業(yè)務(wù)上使用嘉涌,其他業(yè)務(wù)無法接入妻熊,沒有人去做這塊功能的抽離,久而久之就演變成功能重復(fù)開發(fā)洛心,且不易于維護(hù)和拓展

After

接觸到這種可以抽離通用服務(wù)需求的時候固耘,就會與產(chǎn)品確認(rèn)這種需求是否后續(xù)會存在類似的需要,然后建議這把塊需求抽離成通用服務(wù)词身,方便后續(xù)維護(hù)和拓展

設(shè)計概要

tongyongfuwu_nt.jpg

Before VS After

tongyongfuwu.png

架構(gòu)設(shè)計

前后端分離

對于并發(fā)量較大的應(yīng)用厅目,可以將前后端分離開,這樣對于前端的資源就可以使用nginx等效率高的服務(wù)器,并且數(shù)據(jù)是在前端渲染损敷,不是在服務(wù)端通過jsp葫笼、freemarker等渲染后返回前端。相當(dāng)于把原本服務(wù)端處理的任務(wù)分散到用戶端瀏覽器拗馒,可以很大程度的提高頁面響應(yīng)速度路星。前后端分離主要考慮的應(yīng)該就是跨域的問題了,對于跨域主要考慮以下場景:

  • 不跨域诱桂,建議使用這種方式洋丐。主要實現(xiàn)是將js、css挥等、圖片等靜態(tài)資源放到CDN友绝,使用nginx反向代理來區(qū)分html(或使用nodejs的服務(wù)端)和服務(wù)端數(shù)據(jù)的請求度迂。這樣能夠保證前端和后端的請求都在同一個域名之下扮念。這樣做的好處主要是不用考慮跨域的問題,也可以避免跨域的一些坑摔敛,以支持更多的場景(比如RESTful)辞槐。采用這種方式的麻煩點主要是涉及到CDN掷漱、nginx、應(yīng)用服務(wù)器的配置榄檬,所以在版本升級的時候需要有一些自動化的工具來提高效率卜范、避免手動出現(xiàn)一些錯誤,并且要有一個較好的機(jī)制來保障版本升級的兼容性丙号,比如CDN中的資源可以考慮在請求路徑上增加一個版本號(在 動靜分離 中也會提到)
  • 服務(wù)端跨域先朦。主要是在服務(wù)端配置以支持跨域請求。這種主要需要考慮的是服務(wù)端的權(quán)限處理犬缨,因為跨域默認(rèn)是不會將訪問的域的cookie傳到服務(wù)端的,所以需要其他方式來傳遞一個請求的標(biāo)志棉浸,用來控制權(quán)限
  • 客戶端跨域怀薛。這種方式不太適合業(yè)務(wù)類型系統(tǒng),主要適用于一些公開的服務(wù)迷郑,比如:天氣查詢枝恋、手機(jī)號歸屬地查詢等等

動靜分離

動靜分離主要也是對于性能上的優(yōu)化措施,不同人對于動靜分離的理解不一樣嗡害,主要有以下兩種

  • 動態(tài)數(shù)據(jù)和靜態(tài)資源分離焚碌。主要是指將靜態(tài)資源(如:js、css霸妹、圖片等)放到CDN十电,這樣可以提高靜態(tài)資源的請求速度,減少應(yīng)用服務(wù)器的帶寬占用。需要注意的是鹃骂,因為使用了CDN台盯,當(dāng)版本升級的時候可以考慮在靜態(tài)資源的訪問路徑上加一個版本號,這樣升級之后可以避免CDN不刷新的問題畏线,如果是APP應(yīng)用静盅,可以避免版本不兼容的問題,所以就需要在部署環(huán)節(jié)做一些自動化的工具寝殴,避免人工操作出現(xiàn)失誤
  • 服務(wù)端根據(jù)動態(tài)資源生成對應(yīng)的靜態(tài)資源蒿叠,用戶訪問的始終是靜態(tài)資源。這種比較常見于CMS(內(nèi)容管理系統(tǒng))蚣常、博客等類型的應(yīng)用栈虚。主要方式是提前根據(jù)動態(tài)數(shù)據(jù)生成對應(yīng)的靜態(tài)資源(即html靜態(tài)頁面),這樣用戶訪問的時候就直接訪問html頁面了史隆,可以較大程度的提高訪問速度魂务。這種方式主要適合數(shù)據(jù)變化不太頻繁的場景

避免過度設(shè)計

  • 避免因為少數(shù)極端情況做過多處理

  • 避免過度拆分微服務(wù),盡量避免分布式事務(wù)

  • 慎用前后端分離泌射,比如一些內(nèi)部管理型的使用量不高的應(yīng)用粘姜,是沒必要做前后端分離的

數(shù)據(jù)預(yù)先處理

對于一些業(yè)務(wù)場景,可以提前預(yù)處理一些數(shù)據(jù)熔酷,在使用的時候就可以直接使用處理結(jié)果了孤紧,減少請求時的處理邏輯。如對于限制某些用戶參與資格拒秘,可以提前將用戶打好標(biāo)記号显,這樣在用戶請求時就可以直接判斷是否有參與資格,如果數(shù)據(jù)量比較大躺酒,還可以根據(jù)一定規(guī)則將數(shù)據(jù)分布存儲押蚤,用戶請求時也根據(jù)此規(guī)則路由到對應(yīng)的服務(wù)去判斷用戶參與資格,減輕單節(jié)點壓力和單服務(wù)數(shù)據(jù)量羹应,提高整體的處理能力和響應(yīng)速度

資源前置

目前很多都是分布式微服務(wù)架構(gòu)揽碘,就可能會導(dǎo)致調(diào)用鏈路很長,因此可以將一些基本的判斷盡量前置园匹,比如用戶參與資格雳刺、前面提到的限流前置、或者一些資源直接由前端請求到目的地址裸违,而不是通過服務(wù)端轉(zhuǎn)發(fā)掖桦;涉及概率型的高并發(fā)請求,可以考慮在用戶訪問時即隨機(jī)一部分結(jié)果供汛,在前端告知用戶參與失敗枪汪∮磕拢總之,就是將能提前的盡量提前料饥,避免調(diào)用鏈路中不符合條件的節(jié)點做無用功

補(bǔ)償機(jī)制

對于一些業(yè)務(wù)處理失敗后需要有補(bǔ)償機(jī)制蒲犬,例如:重試、回退等

  • 重試需要限制重試次數(shù)岸啡,避免死循環(huán)原叮,超過次數(shù)的需要及時告警,以便人工處理或其他處理巡蘸。重試就需要保證冪等性奋隶,避免重復(fù)處理導(dǎo)致的不一致的問題
  • 回退。當(dāng)超過重試次數(shù)或一些處理失敗后悦荒,需要回退的唯欣,需要考慮周全一些,避免出現(xiàn)數(shù)據(jù)不一致的情況

冪等性

在實際處理中可能會出現(xiàn)各種各樣的情況導(dǎo)致重復(fù)處理搬味,就需要保證處理的冪等性境氢,一般可以使用全局唯一的流水號來進(jìn)行唯一性判斷,避免重復(fù)處理的問題碰纬,主要是在MQ消息處理萍聊、接口調(diào)用等場景。全局唯一的流水號可以參考tweeter的snowflake算法【sequence-spring-boot-starter】悦析。具體生成的位置就需要根據(jù)實際業(yè)務(wù)場景決定了寿桨,主要是需要考慮各種極端的異常情況

監(jiān)控告警

在高并發(fā)系統(tǒng)中,用戶量本身就很大强戴,一旦出現(xiàn)問題影響范圍就會比較大亭螟,所以監(jiān)控告警就需要及時的反饋出系統(tǒng)問題,以便快速恢復(fù)服務(wù)骑歹。必須要建立比較完善的應(yīng)對流程预烙,建議也可以建立對應(yīng)的經(jīng)驗庫,對常見問題進(jìn)行記錄陵刹,一方面避免重復(fù)發(fā)生默伍,另一方面在發(fā)生問題時可以及時定位問題。

自動化運(yùn)維方面需要大力建設(shè)衰琐,可以很大程度提高線上問題的響應(yīng)和解決速度。并且需要有全鏈路監(jiān)控機(jī)制炼蹦,可以更方便的排查線上問題并快速解決羡宙。全鏈路監(jiān)控可以考慮像pingpoint、zipkin掐隐、OpenCensus等

架構(gòu)獨立服務(wù)

項目開發(fā)過程中有些需求是與所在項目業(yè)務(wù)無關(guān)狗热,如:收集用戶行為習(xí)慣钞馁,收集商品曝光點擊,數(shù)據(jù)收集提供給 BI 進(jìn)行統(tǒng)計報表輸出匿刮,公用拉新促活業(yè)務(wù)(柚子街和返還公用)僧凰,類似這種需求,我們結(jié)合應(yīng)用場景熟丸,考慮服務(wù)的獨立性训措,以及未來的拓展需要,架構(gòu)獨立項目進(jìn)行維護(hù)光羞,在服務(wù)器上獨立分布式部署不影響現(xiàn)有主業(yè)務(wù)服務(wù)器資源

項目中實踐例子:

架構(gòu)用戶行為跟蹤獨立服務(wù)绩鸣,在開發(fā)前預(yù)估了下這個服務(wù)的請求量,并會有相對大量的并發(fā)請求

架構(gòu)方案:

  • 項目搭建選擇用 nodejs 來做服務(wù)端

  • 單進(jìn)程纱兑,基于事件驅(qū)動和無阻塞 I/O呀闻,所以非常適合處理并發(fā)請求

  • 負(fù)載均衡:cluster 模塊 / PM2

  • 架構(gòu) nodejs 獨立服務(wù)

  • 提供服務(wù)接口給客戶端

  • 接口不直接 DB 操作,保證并發(fā)下的穩(wěn)定性

  • 數(shù)據(jù)異步入庫

  • 通過程序把數(shù)據(jù)從:消息隊列 =>mysql

  • nodejs+express+redis(list)/mq+mysql

用戶行為跟蹤服務(wù)的服務(wù)架構(gòu)圖

dulifuwu.png

高并發(fā)優(yōu)化

高并發(fā)除了需要對服務(wù)器進(jìn)行垂直擴(kuò)展和水平擴(kuò)展之外潜慎,作為后端開發(fā)可以通過高并發(fā)優(yōu)化捡多,保證業(yè)務(wù)在高并發(fā)的時候能夠穩(wěn)定的運(yùn)行,避免業(yè)務(wù)停滯帶來的損失铐炫,給用戶帶來不好的體驗

緩存:

服務(wù)端緩存

內(nèi)存數(shù)據(jù)庫

  • redis
  • memcache

方式

  • 優(yōu)先緩存
  • 穿透 DB 問題
  • 只讀緩存
  • 更新 / 失效刪除

注意

  • 內(nèi)存數(shù)據(jù)庫的分配的內(nèi)存容量有限垒手,合理規(guī)劃使用,濫用最終會導(dǎo)致內(nèi)存空間不足
  • 緩存數(shù)據(jù)需要設(shè)置過期時間驳遵,無效 / 不使用的數(shù)據(jù)自動過期
  • 壓縮數(shù)據(jù)緩存數(shù)據(jù)淫奔,不使用字段不添加到緩存中
  • 根據(jù)業(yè)務(wù)拆分布式部署緩存服務(wù)器

客戶端緩存

方式

  • 客戶端請求數(shù)據(jù)接口,緩存數(shù)據(jù)和數(shù)據(jù)版本號堤结,并且每次請求帶上緩存的數(shù)據(jù)版本號
  • 服務(wù)端根據(jù)上報的數(shù)據(jù)版本號與數(shù)據(jù)當(dāng)前版本號對比
  • 版本號一樣不返回數(shù)據(jù)列表唆迁,版本號不一樣返回最新數(shù)據(jù)和最新版本號

場景:

  • 更新頻率不高的數(shù)據(jù)

服務(wù)端緩存架構(gòu)圖

huancun_pt.png

場景

  • 多級緩存

雖然Redis集群這種緩存的性能已經(jīng)很高了,但是也避免不了網(wǎng)絡(luò)消耗竞穷,在高并發(fā)系統(tǒng)中唐责,這些消耗是可能會引起很嚴(yán)重后果的,也需要盡量減少瘾带∈蟾纾可以考慮多級緩存,將一些變更頻率非常低的數(shù)據(jù)放入應(yīng)用內(nèi)緩存看政,這樣就可以在應(yīng)用內(nèi)直接處理了朴恳,相比使用集中式緩存來說,在高并發(fā)場景還是能夠提高很大效率的允蚣,可以參考【cache-redis-caffeine-spring-boot-starter】實現(xiàn)兩級緩存于颖,也可以參考開源中國的J2Cache,支持多種兩級緩存的方式嚷兔。需要注意的就是緩存失效時一級緩存的清理森渐,因為一級緩存是在應(yīng)用內(nèi)做入,對于集群部署的系統(tǒng),應(yīng)用之間是沒法直接通信的同衣,只能借助其他工具來進(jìn)行通知并清理一級緩存竟块。如利用Redis的發(fā)布訂閱功能來實現(xiàn)同一應(yīng)用不同節(jié)點間的通信

  • CDN

CDN也是一種緩存,只是主要適用于一些靜態(tài)資源耐齐,比如:css浪秘、js、png圖片等蚪缀,前端會使用的較多秫逝。在一些場景下,可以結(jié)合動靜分離询枚、前后端分離违帆,將前端資源全部放入CDN中,能夠很大程度提高訪問效率金蜀。需要注意的是前端靜態(tài)資源是可能會更新的刷后,當(dāng)有更新的時候需要刷新CDN緩存≡ǔ或者另一種策略是在靜態(tài)資源的地址上增加一個類似版本號的標(biāo)志尝胆,這樣每次修改后的路徑就會不一樣,上線后CDN就會直接回源到自己應(yīng)用內(nèi)獲取最新的文件并緩存在CDN中护桦。使用CDN就需要一套比較完善的自動化部署的工具了含衔,不然每次修改后上線就會比較麻煩

  • 前端緩存

前端html中可以配置靜態(tài)資源在前端的緩存,配置后瀏覽器會緩存一些資源二庵,當(dāng)用戶刷新頁面時贪染,只要不是強(qiáng)制刷新,就可以不用再通過網(wǎng)絡(luò)請求獲取靜態(tài)資源催享,也能夠一定程度提高頁面的響應(yīng)速度

  • 緩存穿透

當(dāng)使用緩存的時候杭隙,如果緩存中查詢不到數(shù)據(jù),就會回源到數(shù)據(jù)庫中查詢因妙。但是如果某些數(shù)據(jù)在數(shù)據(jù)庫中也沒有痰憎,如果不做處理,那么每次請求都會回源到數(shù)據(jù)庫查詢數(shù)據(jù)攀涵。如果有人惡意利用這種不存在的數(shù)據(jù)大量請求系統(tǒng)铣耘,那么就會導(dǎo)致大量請求到數(shù)據(jù)庫中執(zhí)行查詢操作。這種情況就叫做緩存穿透以故。在高并發(fā)場景下更需要防止這種情況的發(fā)生

防止:如果數(shù)據(jù)庫中查詢不到數(shù)據(jù)涡拘,可以往緩存里放一個指定的值,從緩存中取值時先判斷一下据德,如果是這個指定的值就直接返回空鳄乏,這樣就可以都從緩存中獲取數(shù)據(jù)了,從而避免緩存穿透的問題棘利。也可以根據(jù)緩存對象的實際情況橱野,采用兩級緩存的方式,這樣也可以減少緩存設(shè)備的請求量善玫。redis是常用的緩存水援,但是不能存儲null,因此spring cache模塊中定義了一個NullValue對象茅郎,用來代表空值蜗元。spring boot中Redis方式實現(xiàn)spring cache是有一些缺陷的(spring boot 1.5.x版本),具體參考[https://my.oschina.net/dengfuwei/blog/1616221]中提到的#RedisCache實現(xiàn)中的缺陷#

  • 緩存雪崩

緩存雪崩主要是指由于緩存原因系冗,大量請求到達(dá)了數(shù)據(jù)庫奕扣,導(dǎo)致數(shù)據(jù)庫壓力過大而崩潰。除了上面提到的緩存穿透的原因掌敬,還有可能是緩存過期的瞬間有大量的請求需要處理惯豆,從緩存中判斷無數(shù)據(jù),然后就直接查詢數(shù)據(jù)庫了奔害。這也是在高并發(fā)場景下比較容易出現(xiàn)的問題

防止:當(dāng)緩存過期時楷兽,回源到數(shù)據(jù)庫查詢的時候需要做下處理,如:加互斥鎖华临。這樣就能夠避免在某個時間點有大量請求到達(dá)數(shù)據(jù)庫了芯杀,當(dāng)然也可以對方法級別做限流處理,比如:hystrix雅潭、RateLimiter揭厚。也可以通過封裝實現(xiàn)緩存在過期前的某個時間點自動刷新緩存。spring cache的注解中有一個sync屬性寻馏,主要是用來表示回源到數(shù)據(jù)查詢時是否需要保持同步棋弥,由于spring cache只是定義標(biāo)準(zhǔn),沒有具體緩存實現(xiàn)诚欠,所以只是根據(jù)sync的值調(diào)用了不同的Cache接口的方法顽染,所以需要在Cache接口的實現(xiàn)中注意這點

在緩存的使用方面,會有各種各樣復(fù)雜的情況轰绵,建議可以整理一下各種場景并持續(xù)完善粉寞,這樣可以在后續(xù)使用緩存的過程中作為參考,也可以避免因為考慮不周全引起的異常左腔,對于員工的培養(yǎng)也是很有好處的


異步

異步編程

方式:

  • 多線程編程
  • nodejs 異步編程

場景:

  • 參與活動成功后進(jìn)行短信通知
  • 非主業(yè)務(wù)邏輯流程需要的操作唧垦,允許異步處理其他輔助業(yè)務(wù),等

業(yè)務(wù)異步處理

方式

  • 業(yè)務(wù)接口將客戶端上報的數(shù)據(jù) PUSH 到消息隊列(MQ 中間件)液样,然后就響應(yīng)結(jié)果給用戶
  • 編寫?yīng)毩⒊绦蛉ビ嗛喯㈥犃姓窳粒惒教幚順I(yè)務(wù)

場景:

  • 大促活動整點搶限量紅包

  • 參與成功后委婉提示:預(yù)計 X 天后進(jìn)行紅包發(fā)放

  • 并發(fā)量比較大的業(yè)務(wù)巧还,且沒有其他更好的優(yōu)化方案,業(yè)務(wù)允許異步處理

注意:

  • 把控隊列消耗的進(jìn)度
  • 保證冪等性和數(shù)據(jù)最終一致性

缺陷:

  • 犧牲用戶體驗

【業(yè)務(wù)異步處理】架構(gòu)圖

yewuyibu.png

【業(yè)務(wù)異步處理】除了可以在高并發(fā)業(yè)務(wù)中使用坊秸,在上面通用服務(wù)的設(shè)計里也是用這種架構(gòu)方式


限流

在類秒殺的活動中通過限制請求量麸祷,可以避免超賣,超領(lǐng)等問題

高并發(fā)的活動業(yè)務(wù)褒搔,通過前端控流阶牍,分散請求,減少并發(fā)量
更多限流方案參看對高并發(fā)流量控制的一點思考

服務(wù)端限流

  • redis 計數(shù)器
  • 如:類秒殺活動

客戶端控流

  • 通過參與活動游戲的方式

  • 紅包雨 / 小游戲星瘾,等方式

  1. 監(jiān)控走孽,及時擴(kuò)容

應(yīng)用限流后就決定了只能處理一定量的請求,對于增長期應(yīng)用來說琳状,一般還是希望能夠處理更多的用戶請求磕瓷,畢竟意味著帶來更多的用戶、更多的收益算撮。所以就需要監(jiān)控應(yīng)用流量生宛,根據(jù)實際情況及時進(jìn)行擴(kuò)容,提高整個系統(tǒng)的處理能力肮柜,以便為更多的用戶提供服務(wù)

  1. 用戶體驗

當(dāng)應(yīng)用達(dá)到限流值時陷舅,需要給用戶更好的提示和引導(dǎo),這也是需要在需求分析階段就需要考慮的

  1. 限流前置

在實際的系統(tǒng)架構(gòu)中审洞,用戶請求可能會經(jīng)過多級才會到達(dá)應(yīng)用節(jié)點莱睁,比如:nginx-->gateway-->應(yīng)用。如果條件允許芒澜,可以在盡量靠前的位置做限流設(shè)置仰剿,這樣可以盡早的給用戶反饋,也可以減少后續(xù)層級的資源浪費痴晦。不過畢竟在應(yīng)用內(nèi)增加限流配置的開發(fā)成本相對來說較低南吮,并且可能會更靈活,所以需要根據(jù)團(tuán)隊實際情況而定了誊酌。nginx做限流設(shè)置可以使用Lua+Redis配合來實現(xiàn)部凑;應(yīng)用內(nèi)限流可以使用RateLimiter來做。當(dāng)然都可以通過封裝來實現(xiàn)動態(tài)配置限流的功能碧浊,比如【ratelimiter-spring-boot-starter】


服務(wù)降級

當(dāng)服務(wù)器資源消耗已經(jīng)達(dá)到一定的級別的時候涂邀,為了保證核心業(yè)務(wù)正常運(yùn)行,需要丟卒保車箱锐,棄車保帥比勉,服務(wù)降級是最后的手段,避免服務(wù)器宕機(jī)導(dǎo)致業(yè)務(wù)停滯帶來的損失,以及給用戶帶來不好的體驗

業(yè)務(wù)降級

  • 從復(fù)雜服務(wù)浩聋,變成簡單服務(wù)
  • 從動態(tài)交互观蜗,變成靜態(tài)頁面

分流到 CDN

  • 從 CDN 拉取提前備好的 JSON 數(shù)據(jù)
  • 引導(dǎo)到 CDN 靜態(tài)頁面

停止服務(wù)

  • 停止非核心業(yè)務(wù),并進(jìn)行委婉提示

熔斷降級

在微服務(wù)架構(gòu)中赡勘,會有很多的接口調(diào)用嫂便,當(dāng)某些服務(wù)出現(xiàn)調(diào)用時間較長或無法提供服務(wù)的時候,就可能會造成請求阻塞闸与,從而導(dǎo)致響應(yīng)緩慢,吞吐量降低的情況岸售。這時候就有必要對服務(wù)進(jìn)行降級處理践樱。當(dāng)超過指定時間或服務(wù)不可用的時候,采取備用方案繼續(xù)后續(xù)流程凸丸,避免請求阻塞時間太長拷邢。比如對于概率性的請求(如抽獎),當(dāng)處理時間過長時直接認(rèn)為隨機(jī)結(jié)果是無效的(如未中獎)屎慢。需要注意的是

  • 配置熔斷降級的時間需要綜合權(quán)衡一下具體配置多少瞭稼,而且正常情況下是能夠快速響應(yīng)的,當(dāng)出現(xiàn)處理時間超時的情況或服務(wù)不可用的情況腻惠,就需要監(jiān)控及時告警环肘,以便盡快恢復(fù)服務(wù)
  • 當(dāng)出現(xiàn)熔斷降級的時候,需要有對應(yīng)的機(jī)制集灌,比如:重試悔雹、回退。需要保證業(yè)務(wù)數(shù)據(jù)在代碼邏輯上的一致性

可以使用hystrix來實現(xiàn)熔斷降級處理


高并發(fā)優(yōu)化概要圖

gaobinfa_nc.jpg

防刷 / 防羊毛黨

大多數(shù)公司的產(chǎn)品設(shè)計和程序猿對于推廣活動業(yè)務(wù)的防刷意識不強(qiáng)欣喧,在活動業(yè)務(wù)設(shè)計和開發(fā)的過程中沒有把防刷的功能加入業(yè)務(wù)中腌零,給那些喜歡刷活動的人創(chuàng)造了很多的空子
等到你發(fā)現(xiàn)自己被刷的時候,已經(jīng)產(chǎn)生了不小的損失唆阿,少則幾百幾千益涧,多則幾萬

隨著利益的誘惑,現(xiàn)在已經(jīng)浮現(xiàn)了一個新的職業(yè) “刷客”驯鳖,專業(yè)刷互聯(lián)網(wǎng)活動為生闲询,養(yǎng)了 N 臺手機(jī) + N 個手機(jī)號碼 + N 個微信賬號,刷到的獎勵金進(jìn)行提現(xiàn)臼隔,刷到活動商品進(jìn)行低價轉(zhuǎn)手處理嘹裂,開辟了一條新的灰色產(chǎn)業(yè)鏈

我們要拿起武器 (代碼) 進(jìn)行自我的防御,風(fēng)控摔握,加高門檻寄狼,通過校驗和限制減少風(fēng)險發(fā)生的各種可能性,減少風(fēng)險發(fā)生時造成的損失

這里列出常用套路(具體應(yīng)用結(jié)合業(yè)務(wù)場景):

校驗請求合法性

  • 請求參數(shù)合法性判斷

  • 請求頭校驗

  • user-agent

  • referer

  • ... ...

  • 簽名校驗

  • 對請求參數(shù)進(jìn)行簽名

  • 設(shè)備限制

  • IP 限制

  • 微信 unionid/openid 合法性判斷

  • 驗證碼 / 手機(jī)短信驗證碼

  • 犧牲體驗

  • 自建黑名單系統(tǒng)過濾

業(yè)務(wù)風(fēng)控

  • 限制設(shè)備 / 微信參與次數(shù)
  • 限制最多獎勵次數(shù)
  • 獎池限制
  • 根據(jù)具體業(yè)務(wù)場景設(shè)計... ...

應(yīng)對角色

  • 普通用戶

  • 技術(shù)用戶

  • 專業(yè)刷客

  • 目前還沒有很好的限制方式

防刷 / 防羊毛黨套路概要圖

fangshua.jpg

附加

  • APP/H5 中簽名規(guī)則應(yīng)該由客戶端童鞋開發(fā),然后拓展 API 給前端 JS 調(diào)用泊愧,在 H5 發(fā)起接口請求的時候調(diào)用客戶端拓展的簽名伊磺,這樣可以避免前端 JS 里構(gòu)造簽名規(guī)則而被發(fā)現(xiàn)破解

并發(fā)問題

多操作

  • 場景:

當(dāng) == 同用戶 == 多次觸發(fā)點擊,或者通過模擬并發(fā)請求删咱,就會出現(xiàn)多操作的問題屑埋,比如:簽到功能,一天只能簽到一次痰滋,可以獲得 1 積分摘能,但是并發(fā)的情況下會出現(xiàn)用戶可以獲得多積分的問題

  • 剖析:

簡化簽到邏輯一般是這樣的:

查詢是否有簽到記錄 --> 否 --> 添加今日簽到記錄 --> 累加用戶積分 --> 簽到成功

查詢是否有簽到記錄 --> 是 --> 今日已經(jīng)簽到過

假設(shè)這個時候用戶 A 并發(fā)兩個簽到請求,這時會同時進(jìn)入到 【查詢是否有簽到記錄】敲街,然后同時返回否团搞,就會添加兩條的簽到記錄,并且多累加積分

  • 解決方案:

最理想簡單的方案多艇,只需要在簽到記錄表添加【簽到日期】+【用戶 ID】的組合唯一索引逻恐,當(dāng)并發(fā)的時候只有會一條可以添加成功,其他添加操作會因為唯一約束而失敗

庫存負(fù)數(shù)

  • 場景:

當(dāng) == 多用戶 == 并發(fā)點擊參與活動峻黍,如:抽獎活動复隆,這個時候獎品只有一個庫存了,理論上只有一個用戶可以獲得姆涩,但是并發(fā)的時候往往會出現(xiàn)他們都成功獲得獎品挽拂,導(dǎo)致獎品多支出,加大了活動成本

  • 剖析:

有問題的邏輯流程一般是這樣的:

中獎 --> 查詢獎品庫存 --> 有 --> 更新獎品庫存 --> 添加中獎紀(jì)錄 --> 告知中獎

中獎 --> 查詢獎品庫存 --> 無 --> 告知無中獎

假設(shè)抽獎活動阵面,當(dāng)前獎品 A 只有最后一個庫存轻局,然后用戶 A、B样刷、C仑扑,同時參與活動同時中獎獎品都是 A,這個時候查詢商品庫存是存在 1 個置鼻,就會進(jìn)行更新庫存镇饮,添加中獎紀(jì)錄,然后就同時中獎了

  • 解決方案:

最理想根本就不需要用多做一個庫存的 SELECT 獎品庫存操作箕母,只需要 UPDATE 獎品庫存 - 1 WHERE 獎品庫存 >=1储藐,UPDATE 成功后就說明是有庫存的,然后再做后續(xù)操作嘶是,并發(fā)的時候只會有一個用戶 UPDATE 成功

庫存扣減

庫存扣減的實現(xiàn)方式有很多種钙勃,而且涉及到扣減庫存的時候還需要結(jié)合實際業(yè)務(wù)場景來決定實現(xiàn)方案,除了扣減庫存聂喇,還需要記錄一些業(yè)務(wù)數(shù)據(jù)辖源。數(shù)據(jù)庫在高并發(fā)量的應(yīng)用中很容易遇到瓶頸蔚携,所以可以考慮使用Redis + MQ來做請求的處理,由MQ消費者去實現(xiàn)后續(xù)的業(yè)務(wù)邏輯克饶。這樣能夠較快速的響應(yīng)請求酝蜒,避免請求阻塞而引發(fā)更多的問題

  • 使用Redis來做庫存扣減

利用Redis中的incr命令來實現(xiàn)庫存扣減的操作。Redis從2.6.0版本開始內(nèi)置了Lua解釋器矾湃,并且對Lua腳本的執(zhí)行是具有原子性的亡脑,所以可以利用此特性來做庫存的扣減,具體實現(xiàn)可以參考【stock-spring-boot-starter】邀跃,starter中主要實現(xiàn)了初始化/重置庫存霉咨、扣減庫存、恢復(fù)庫存

Redis集群的效率已經(jīng)非常高了坞嘀,能夠支撐一定量的并發(fā)扣減庫存躯护,并且由于Redis執(zhí)行Lua腳本的原子性可以避免超扣的問題。如果一個Redis集群還滿足不了業(yè)務(wù)需要丽涩,可以考慮將庫存進(jìn)行拆分。即將庫存拆成多份裁蚁,分別放到不同的Redis集群當(dāng)中矢渊,多個Redis集群采用輪詢策略,基本能夠在大體上保證各個Redis集群的剩余庫存量不會相差太大枉证。不過也不能絕對的保證數(shù)量均勻矮男,所以在扣減庫存操作返回庫存不足時,還是需要一定的策略去解決這個問題室谚,比如扣減庫存返回庫存不足時毡鉴,繼續(xù)輪詢到下一個Redis集群,當(dāng)所有Redis集群都返回庫存不足時秒赤,可以在應(yīng)用節(jié)點內(nèi)或某個統(tǒng)一的地方打個標(biāo)記表示已沒有庫存猪瞬,避免每個請求都輪詢?nèi)康腞edis集群。

  • 扣減庫存的冪等性

由于利用Redis的incr命令來扣減庫存入篮,沒法存儲請求源的信息陈瘦,所以扣減庫存的冪等性由應(yīng)用來保證,可以利用客戶端token或流水號之類的來做

  • MQ異步處理業(yè)務(wù)數(shù)據(jù)

扣減庫存都會伴隨一些業(yè)務(wù)數(shù)據(jù)需要記錄潮售,如果實時記錄到數(shù)據(jù)庫痊项,仍然很容易達(dá)到瓶頸,所以可以利用MQ酥诽,將相關(guān)信息放入MQ鞍泉,然后由MQ消費者去異步處理后續(xù)的業(yè)務(wù)邏輯。當(dāng)然如果MQ消息發(fā)送失敗需要恢復(fù)Redis中的庫存肮帐,Redis操作和MQ操作無法完全保證一致性咖驮,所以在保證正常情況下數(shù)據(jù)一致性的前提下,還需要類似對賬一樣來驗證扣減庫存和實際庫存的一致性。不過在這之前游沿,我認(rèn)為需要更優(yōu)先考慮限流問題饰抒,需要提前壓測出應(yīng)用的性能瓶頸,根據(jù)壓測結(jié)果對請求配置限流诀黍,優(yōu)先保證高并發(fā)情況下應(yīng)用不會崩潰掉袋坑,這樣才能更好的保證接收到的請求能夠按正常代碼邏輯處理,減少發(fā)生庫存不一致的情況

總結(jié):

在開發(fā)業(yè)務(wù)接口的時候需要把 == 同用戶 == 和 == 多用戶 == 并發(fā)的場景考慮進(jìn)去眯勾,這樣就可以避免在并發(fā)的時候產(chǎn)生數(shù)據(jù)異常問題枣宫,導(dǎo)致成本多支出

可以使用下面的工具進(jìn)行模擬并發(fā)測試:

  • Apache JMeter
  • Charles Advanced Repeat
  • Visual Studio 性能負(fù)載
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吃环,隨后出現(xiàn)的幾起案子也颤,更是在濱河造成了極大的恐慌,老刑警劉巖郁轻,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翅娶,死亡現(xiàn)場離奇詭異,居然都是意外死亡好唯,警方通過查閱死者的電腦和手機(jī)竭沫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骑篙,“玉大人蜕提,你說我怎么就攤上這事“卸耍” “怎么了谎势?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杨名。 經(jīng)常有香客問我脏榆,道長,這世上最難降的妖魔是什么镣煮? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任姐霍,我火速辦了婚禮,結(jié)果婚禮上典唇,老公的妹妹穿的比我還像新娘镊折。我一直安慰自己,他們只是感情好介衔,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布恨胚。 她就那樣靜靜地躺著,像睡著了一般炎咖。 火紅的嫁衣襯著肌膚如雪赃泡。 梳的紋絲不亂的頭發(fā)上寒波,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音升熊,去河邊找鬼俄烁。 笑死,一個胖子當(dāng)著我的面吹牛级野,可吹牛的內(nèi)容都是我干的页屠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蓖柔,長吁一口氣:“原來是場噩夢啊……” “哼辰企!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起况鸣,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤牢贸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后镐捧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體潜索,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年懂酱,在試婚紗的時候發(fā)現(xiàn)自己被綠了帮辟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡玩焰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出芍锚,到底是詐尸還是另有隱情昔园,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布并炮,位于F島的核電站默刚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏逃魄。R本人自食惡果不足惜荤西,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伍俘。 院中可真熱鬧邪锌,春花似錦、人聲如沸癌瘾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妨退。三九已至妇萄,卻和暖如春蜕企,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冠句。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工轻掩, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人懦底。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓唇牧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親基茵。 傳聞我的和親對象是個殘疾皇子奋构,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345