對於一個須要處理高併發的系統而言,能夠從多個層面去解決這個問題。css
一、數據庫系統:數據庫系統能夠採起集羣策略以保證某臺數據庫服務器的宕機不會影響整個系統,而且經過負載均衡策略來下降每一臺數據庫服務器的壓力(固然用一臺服務器應付通常而言沒啥問題,找一臺當備機放着應付宕機就行,若是一臺應付不了,那麼再加一臺,可是備機仍是要的,至少一臺),另外採起讀/寫分離的方法下降數據庫負載,再加上分庫和分表進一步下降數據庫負載,從而能夠從容地應對高併發問題。固然成本會比較高,畢竟要這麼多服務器。html
二、分佈式緩存系統:創建分佈式緩存系統是相當重要的,全部的讀寫都先進入緩存系統,而後由緩存系統來安排從數據庫系統/源服務器的讀寫。好比讀的話,要是找不到,則把數據從數據庫中讀出放入緩存系統裏(一樣的,把頁面從源服務器中獲取並保存在緩存服務器中),再讀取給客戶端;寫的話,則先寫入緩存,由緩存系統把數據異步提交到消息隊列中,而後寫入數據庫裏。緩存分爲硬盤緩存和內存緩存,硬盤緩存更多地用來緩存頁面和頁面資源例如多媒體資源,而內存緩存更多地用來緩存數據庫的數據和應用中的一些狀態。硬盤緩存有Squid,內存緩存有Memcached,微軟在.Net Framework 4.0推了個Velocity也是內存緩存。前端
三、監控系統:這麼多服務器和系統,總歸是須要監控的,否則出了問題排查起來會很是麻煩,因此上述系統在開發時也要考慮到監控這一塊,作好日誌記錄,而後配合監控系統能夠一會兒查到問題根源。node
四、前端系統:和數據庫系統同樣能夠採用服務器集羣和負載均衡,能夠把各個服務細分而後註冊到服務中心再分別部署到不一樣的服務器上即採用分佈式服務的方式,還能夠使用多線程,另外爲了更好地用戶體驗,能夠多用異步方式和客戶端操做來顯示數據或者執行操做,ajax,js等等能夠派很多用處,此外,還能夠使用網頁靜態化(這樣不但下降了開銷還會提升網頁被搜索到的機率,.Net有URLRewrite能夠作到,只須要引入dll並註冊而後設置Rewrite的規則便可),還有圖片等多媒體資源單獨設置服務器與頁面分離,使用鏡像網站或者CDN技術(Content Delivery Network,智能鏡像網站+緩存技術,讓用戶能夠訪問本身最近的鏡像網站的緩存服務器中的緩存頁面)mysql
五、服務器CPU和IO的平衡:對於全部的服務器,都須要保證它們的CPU和IO保持平衡,若是失衡,須要查找緣由,更改部署和配置以達到平衡。nginx
對於通常的小型服務器,最佳線程數=CPU核的個數*2 + 2,固然這個只是經驗之談,實際公式比較複雜,爲最佳線程數=((線程等待時間+線程cpu時間)/線程cpu時間) * cpu核的數量。ajax
memcached的存儲結構是把內存劃分紅不一樣尺寸的內存塊,並創建多個相同尺寸的內存塊做爲一個內存塊羣,存儲的時候根據數據的實際大小選用最合適尺寸的內存塊進行存儲,這樣子的缺點就是若是數據大小不是和內存塊大小一致就會浪費一些內存,因此再設置內存塊大小的時候要確保不一樣的尺寸之間的大小差距不要太大。redis
memcached不會刪除保存的數據,它是在接收到獲取命令時先檢查下數據是否過時,若是過時就返回沒有數據,而後標誌下這個內存塊能夠被從新保存數據,當內存都被使用須要釋放數據時,它會根據LRU(Least Recently Used)機制來釋放內存塊。算法
memcached不支持分佈式,也就是說不一樣服務器上的memcached不會互相通訊,因此對於使用多臺memcached服務器,須要客戶程序來把須要保存的數據分發到不一樣的memcached服務器上,分發的方法就是用key經過一個分發策略來肯定須要發送的服務器,而後進行發送保存,獲取時也用key經過相同的分發策略肯定服務器並獲取。若是某臺memcached服務器發生故障,能夠經過從新分配服務器的方法把數據保存到新的服務器上。spring
在開發高併發系統時有三把利器用來保護系統:緩存、降級和限流。緩存的目的是提高系統訪問速度和增大系統能處理的容量,可謂是抗高併發流量的銀彈;而降級是當服務出問題或者影響到核心流程的性能則須要暫時屏蔽掉,待高峯或者問題解決後再打開;而有些場景並不能用緩存和降級來解決,好比稀缺資源(秒殺、搶購)、寫服務(如評論、下單)、頻繁的複雜查詢(評論的最後幾頁),所以需有一種手段來限制這些場景的併發/請求量,即限流。
限流的目的是經過對併發訪問/請求進行限速或者一個時間窗口內的的請求進行限速來保護系統,一旦達到限制速率則能夠拒絕服務(定向到錯誤頁或告知資源沒有了)、排隊或等待(好比秒殺、評論、下單)、降級(返回兜底數據或默認數據,如商品詳情頁庫存默認有貨)。
通常開發高併發系統常見的限流有:限制總併發數(好比數據庫鏈接池、線程池)、限制瞬時併發數(如nginx的limit_conn模塊,用來限制瞬時併發鏈接數)、限制時間窗口內的平均速率(如Guava的RateLimiter、nginx的limit_req模塊,限制每秒的平均速率);其餘還有如限制遠程接口調用速率、限制MQ的消費速率。另外還能夠根據網絡鏈接數、網絡流量、CPU或內存負載等來限流。
先有緩存這個銀彈,後有限流來應對61八、雙十一高併發流量,在處理高併發問題上能夠說是如虎添翼,不用擔憂瞬間流量致使系統掛掉或雪崩,最終作到有損服務而不是不服務;限流須要評估好,不可亂用,不然會正常流量出現一些奇怪的問題而致使用戶抱怨。
在實際應用時也不要太糾結算法問題,由於一些限流算法實現是同樣的只是描述不同;具體使用哪一種限流技術仍是要根據實際場景來選擇,不要一味去找最佳模式,白貓黑貓能解決問題的就是好貓。
因在實際工做中遇到過許多人來問如何進行限流,所以本文會詳細介紹各類限流手段。那麼接下來咱們從限流算法、應用級限流、分佈式限流、接入層限流來詳細學習下限流技術手段。
常見的限流算法有:令牌桶、漏桶。計數器也能夠進行粗暴限流實現。
MQ是分佈式架構中的解耦神器,應用很是廣泛。有些分佈式事務也是利用MQ來作的。因爲其高吞吐量,在一些業務比較複雜的狀況,能夠先作基本的數據驗證,而後將數據放入MQ,由消費者異步去處理後續的複雜業務邏輯,這樣能夠大大提升請求響應速度,提高用戶體驗。若是消費者業務處理比較複雜,也能夠獨立集羣部署,根據實際處理能力需求部署多個節點。須要注意的是:
好比RabbitMQ在發送消息到MQ時,就有發送回調確認,雖然不可以徹底避免消息丟失,但也可以避免一些極端狀況下消息發送失敗的狀況了。能夠利用MQ的事務來避免更多狀況的消息丟失
須要注意配置消息持久化,避免MQ集羣掛掉的狀況下大量丟失消息的狀況
正常來講消息是不會重複發送的,可是一些特殊狀況也可能會致使消息重複發送給消費者,通常會在消息中加一個全局惟一的流水號,經過流水號來判斷消息是否已經消費過
使用異步處理是在提升系統吞吐量考慮下的一種設計,相對於實時快速給用戶返回結果,確定用戶體驗會更差一點,但這也是目前來講綜合考慮的一種不錯的方案了,所以在設計之初就須要評估是否須要異步處理,若是須要異步處理,那必定要考慮如何給用戶更友好的提示和引導。由於異步處理是技術實現結合實際業務狀況的一種綜合解決方案,對於產品來講是不該該關心的,須要技術人員主動儘早提出流程中異步處理的節點,在需求分析階段就考慮如何設計才能對用戶來講更加友好。若是在開發過程當中才提出,極可能就會對用戶展現界面有較大調整,從而致使需求變動、系統設計變動,然後就是甩鍋、扯皮、延期了
代碼結構和規範
人員管理
根據業務場景,將業務抽離成獨立模塊,對外經過接口提供服務,減小系統複雜度和耦合度,實現可複用,易維護,易拓展
項目中實踐例子:
Before:
在返還購 APP 裏有個【個人紅包】的功能,用戶的紅包數據來自多個業務,如:邀請新用戶註冊領取 100 元紅包,大促活動雙倍紅包,等各類活動紅包,多個活動業務都實現了一套不一樣規則的紅包領取和紅包獎勵發放的機制,致使紅包不可管理,不能複用,難維護難拓展
After:
設計概要
Before VS After
產品有時提出的業務需求沒有往這方面去考慮,結合場景和將來拓展須要,在需求討論的時候提出模塊化設計方案,並能夠協助產品進行設計
在項目開發中常常會遇到些相似的功能,可是不一樣的開發人員都各自實現,或者由於不能複用又從新開發一個,致使了相似功能的重複開發,因此咱們須要對可以抽離獨立服務的功能進行抽離,達到複用的效果,而且能夠不斷拓展完善,節約了後續開發成本,提升開發效率,易於維護和拓展
項目中實踐例子:
Before
在業務中常常須要對用戶進行信息通知,如:短信定時通知,APP 消息推送,微信通知,等
開發人員在接到需求中有通知功能的時候沒有考慮後續拓展,就接入第三方信息通知平臺,而後簡單封裝個信息通知方法,後續也有相似信息通知需求的時候,另外一個開發人員發現當前這個通知方法沒法知足本身的需求,而後又本身去了解第三方平臺從新封裝了通知方法,或者後續需求加了定時通知的功能,開發人員針對業務去實現了個定時通知功能,可是隻能本身業務上使用,其餘業務沒法接入,沒有人去作這塊功能的抽離,長此以往就演變成功能重複開發,且不易於維護和拓展
After
接觸到這種能夠抽離通用服務需求的時候,就會與產品確認這種需求是否後續會存在相似的須要,而後建議這把塊需求抽離成通用服務,方便後續維護和拓展
設計概要
Before VS After
Before VS After
先後端分離
對於併發量較大的應用,能夠將先後端分離開,這樣對於前端的資源就能夠使用nginx等效率高的服務器,而且數據是在前端渲染,不是在服務端經過jsp、freemarker等渲染後返回前端。至關於把本來服務端處理的任務分散到用戶端瀏覽器,能夠很大程度的提升頁面響應速度。先後端分離主要考慮的應該就是跨域的問題了,對於跨域主要考慮如下場景:
動靜分離
動靜分離主要也是對於性能上的優化措施,不一樣人對於動靜分離的理解不同,主要有如下兩種
數據預先處理
對於一些業務場景,能夠提早預處理一些數據,在使用的時候就能夠直接使用處理結果了,減小請求時的處理邏輯。如對於限制某些用戶參與資格,能夠提早將用戶打好標記,這樣在用戶請求時就能夠直接判斷是否有參與資格,若是數據量比較大,還能夠根據必定規則將數據分佈存儲,用戶請求時也根據此規則路由到對應的服務去判斷用戶參與資格,減輕單節點壓力和單服務數據量,提升總體的處理能力和響應速度
資源前置
目前不少都是分佈式微服務架構,就可能會致使調用鏈路很長,所以能夠將一些基本的判斷儘可能前置,好比用戶參與資格、前面提到的限流前置、或者一些資源直接由前端請求到目的地址,而不是經過服務端轉發;涉及機率型的高併發請求,能夠考慮在用戶訪問時即隨機一部分結果,在前端告知用戶參與失敗。總之,就是將能提早的儘可能提早,避免調用鏈路中不符合條件的節點作無用功
補償機制
對於一些業務處理失敗後須要有補償機制,例如:重試、回退等
冪等性
在實際處理中可能會出現各類各樣的狀況致使重複處理,就須要保證處理的冪等性,通常能夠使用全局惟一的流水號來進行惟一性判斷,避免重複處理的問題,主要是在MQ消息處理、接口調用等場景。全局惟一的流水號能夠參考tweeter的snowflake算法【sequence-spring-boot-starter】。具體生成的位置就須要根據實際業務場景決定了,主要是須要考慮各類極端的異常狀況
監控告警
在高併發系統中,用戶量自己就很大,一旦出現問題影響範圍就會比較大,因此監控告警就須要及時的反饋出系統問題,以便快速恢復服務。必需要創建比較完善的應對流程,建議也能夠創建對應的經驗庫,對常見問題進行記錄,一方面避免重複發生,另外一方面在發生問題時能夠及時定位問題。
自動化運維方面須要大力建設,能夠很大程度提升線上問題的響應和解決速度。而且須要有全鏈路監控機制,能夠更方便的排查線上問題並快速解決。全鏈路監控能夠考慮像pingpoint、zipkin、OpenCensus等
架構獨立服務
項目開發過程當中有些需求是與所在項目業務無關,如:收集用戶行爲習慣,收集商品曝光點擊,數據收集提供給 BI 進行統計報表輸出,公用拉新促活業務(柚子街和返還公用),相似這種需求,咱們結合應用場景,考慮服務的獨立性,以及將來的拓展須要,架構獨立項目進行維護,在服務器上獨立分佈式部署不影響現有主業務服務器資源
項目中實踐例子:
架構用戶行爲跟蹤獨立服務,在開發前預估了下這個服務的請求量,並會有相對大量的併發請求
架構方案:
用戶行爲跟蹤服務的服務架構圖
高併發除了須要對服務器進行垂直擴展和水平擴展以外,做爲後端開發能夠經過高併發優化,保證業務在高併發的時候可以穩定的運行,避免業務停滯帶來的損失,給用戶帶來很差的體驗
服務端緩存
內存數據庫
方式
注意
方式
場景:
服務端緩存架構圖
場景
雖然Redis集羣這種緩存的性能已經很高了,可是也避免不了網絡消耗,在高併發系統中,這些消耗是可能會引發很嚴重後果的,也須要儘可能減小。能夠考慮多級緩存,將一些變動頻率很是低的數據放入應用內緩存,這樣就能夠在應用內直接處理了,相比使用集中式緩存來講,在高併發場景仍是可以提升很大效率的,能夠參考【cache-redis-caffeine-spring-boot-starter】實現兩級緩存,也能夠參考開源中國的J2Cache,支持多種兩級緩存的方式。須要注意的就是緩存失效時一級緩存的清理,由於一級緩存是在應用內,對於集羣部署的系統,應用之間是無法直接通訊的,只能藉助其餘工具來進行通知並清理一級緩存。如利用Redis的發佈訂閱功能來實現同一應用不一樣節點間的通訊
CDN也是一種緩存,只是主要適用於一些靜態資源,好比:css、js、png圖片等,前端會使用的較多。在一些場景下,能夠結合動靜分離、先後端分離,將前端資源所有放入CDN中,可以很大程度提升訪問效率。須要注意的是前端靜態資源是可能會更新的,當有更新的時候須要刷新CDN緩存。或者另外一種策略是在靜態資源的地址上增長一個相似版本號的標誌,這樣每次修改後的路徑就會不同,上線後CDN就會直接回源到本身應用內獲取最新的文件並緩存在CDN中。使用CDN就須要一套比較完善的自動化部署的工具了,否則每次修改後上線就會比較麻煩
前端html中能夠配置靜態資源在前端的緩存,配置後瀏覽器會緩存一些資源,當用戶刷新頁面時,只要不是強制刷新,就能夠不用再經過網絡請求獲取靜態資源,也可以必定程度提升頁面的響應速度
當使用緩存的時候,若是緩存中查詢不到數據,就會回源到數據庫中查詢。可是若是某些數據在數據庫中也沒有,若是不作處理,那麼每次請求都會回源到數據庫查詢數據。若是有人惡意利用這種不存在的數據大量請求系統,那麼就會致使大量請求到數據庫中執行查詢操做。這種狀況就叫作緩存穿透。在高併發場景下更須要防止這種狀況的發生
防止:若是數據庫中查詢不到數據,能夠往緩存裏放一個指定的值,從緩存中取值時先判斷一下,若是是這個指定的值就直接返回空,這樣就能夠都從緩存中獲取數據了,從而避免緩存穿透的問題。也能夠根據緩存對象的實際狀況,採用兩級緩存的方式,這樣也能夠減小緩存設備的請求量。redis是經常使用的緩存,可是不能存儲null,所以spring cache模塊中定義了一個NullValue對象,用來表明空值。spring boot中Redis方式實現spring cache是有一些缺陷的(spring boot 1.5.x版本),具體參考[https://my.oschina.net/dengfuwei/blog/1616221]中提到的#RedisCache實現中的缺陷#
緩存雪崩主要是指因爲緩存緣由,大量請求到達了數據庫,致使數據庫壓力過大而崩潰。除了上面提到的緩存穿透的緣由,還有多是緩存過時的瞬間有大量的請求須要處理,從緩存中判斷無數據,而後就直接查詢數據庫了。這也是在高併發場景下比較容易出現的問題
防止:當緩存過時時,回源到數據庫查詢的時候須要作下處理,如:加互斥鎖。這樣就可以避免在某個時間點有大量請求到達數據庫了,固然也能夠對方法級別作限流處理,好比:hystrix、RateLimiter。也能夠經過封裝實現緩存在過時前的某個時間點自動刷新緩存。spring cache的註解中有一個sync屬性,主要是用來表示回源到數據查詢時是否須要保持同步,因爲spring cache只是定義標準,沒有具體緩存實現,因此只是根據sync的值調用了不一樣的Cache接口的方法,因此須要在Cache接口的實現中注意這點
在緩存的使用方面,會有各類各樣複雜的狀況,建議能夠整理一下各類場景並持續完善,這樣能夠在後續使用緩存的過程當中做爲參考,也能夠避免由於考慮不周全引發的異常,對於員工的培養也是頗有好處的
異步編程
方式:
場景:
方式
場景:
注意:
缺陷:
【業務異步處理】架構圖
【業務異步處理】除了能夠在高併發業務中使用,在上面通用服務的設計裏也是用這種架構方式
在類秒殺的活動中經過限制請求量,能夠避免超賣,超領等問題
高併發的活動業務,經過前端控流,分散請求,減小併發量
更多限流方案參看對高併發流量控制的一點思考
服務端限流
應用限流後就決定了只能處理必定量的請求,對於增加期應用來講,通常仍是但願可以處理更多的用戶請求,畢竟意味着帶來更多的用戶、更多的收益。因此就須要監控應用流量,根據實際狀況及時進行擴容,提升整個系統的處理能力,以便爲更多的用戶提供服務
2.用戶體驗
當應用達到限流值時,須要給用戶更好的提示和引導,這也是須要在需求分析階段就須要考慮的
3.限流前置
在實際的系統架構中,用戶請求可能會通過多級纔會到達應用節點,好比:nginx-->gateway-->應用。若是條件容許,能夠在儘可能靠前的位置作限流設置,這樣能夠儘早的給用戶反饋,也能夠減小後續層級的資源浪費。不過畢竟在應用內增長限流配置的開發成本相對來講較低,而且可能會更靈活,因此須要根據團隊實際狀況而定了。nginx作限流設置能夠使用Lua+Redis配合來實現;應用內限流能夠使用RateLimiter來作。固然均可以經過封裝來實現動態配置限流的功能,好比【ratelimiter-spring-boot-starter】
當服務器資源消耗已經達到必定的級別的時候,爲了保證核心業務正常運行,須要丟卒保車,棄車保帥,服務降級是最後的手段,避免服務器宕機致使業務停滯帶來的損失,以及給用戶帶來很差的體驗
在微服務架構中,會有不少的接口調用,當某些服務出現調用時間較長或沒法提供服務的時候,就可能會形成請求阻塞,從而致使響應緩慢,吞吐量下降的狀況。這時候就有必要對服務進行降級處理。當超過指定時間或服務不可用的時候,採起備用方案繼續後續流程,避免請求阻塞時間太長。好比對於機率性的請求(如抽獎),當處理時間過長時直接認爲隨機結果是無效的(如未中獎)。須要注意的是
能夠使用hystrix來實現熔斷降級處理
高併發優化概要圖
大多數公司的產品設計和程序猿對於推廣活動業務的防刷意識不強,在活動業務設計和開發的過程當中沒有把防刷的功能加入業務中,給那些喜歡刷活動的人創造了不少的空子
等到你發現本身被刷的時候,已經產生了不小的損失,少則幾百幾千,多則幾萬
隨着利益的誘惑,如今已經浮現了一個新的職業 「刷客」,專業刷互聯網活動爲生,養了 N 臺手機 + N 個手機號碼 + N 個微信帳號,刷到的獎勵金進行提現,刷到活動商品進行低價轉手處理,開闢了一條新的灰色產業鏈
咱們要拿起武器 (代碼) 進行自個人防護,風控,加高門檻,經過校驗和限制減小風險發生的各類可能性,減小風險發生時形成的損失
這裏列出經常使用套路(具體應用結合業務場景):
校驗請求合法性
業務風控
應對角色
防刷 / 防羊毛黨套路概要圖
附加
多操做
當 == 同用戶 == 屢次觸發點擊,或者經過模擬併發請求,就會出現多操做的問題,好比:簽到功能,一天只能簽到一次,能夠得到 1 積分,可是併發的狀況下會出現用戶能夠得到多積分的問題
簡化簽到邏輯通常是這樣的:
查詢是否有簽到記錄 --> 否 --> 添加今日簽到記錄 --> 累加用戶積分 --> 簽到成功
查詢是否有簽到記錄 --> 是 --> 今日已經簽到過
假設這個時候用戶 A 併發兩個簽到請求,這時會同時進入到 【查詢是否有簽到記錄】,而後同時返回否,就會添加兩條的簽到記錄,而且多累加積分
最理想簡單的方案,只須要在簽到記錄表添加【簽到日期】+【用戶 ID】的組合惟一索引,當併發的時候只有會一條能夠添加成功,其餘添加操做會由於惟一約束而失敗
當 == 多用戶 == 併發點擊參與活動,如:抽獎活動,這個時候獎品只有一個庫存了,理論上只有一個用戶能夠得到,可是併發的時候每每會出現他們都成功得到獎品,致使獎品多支出,加大了活動成本
有問題的邏輯流程通常是這樣的:
中獎 --> 查詢獎品庫存 --> 有 --> 更新獎品庫存 --> 添加中獎紀錄 --> 告知中獎
中獎 --> 查詢獎品庫存 --> 無 --> 告知無中獎
假設抽獎活動,當前獎品 A 只有最後一個庫存,而後用戶 A、B、C,同時參與活動同時中獎獎品都是 A,這個時候查詢商品庫存是存在 1 個,就會進行更新庫存,添加中獎紀錄,而後就同時中獎了
最理想根本就不須要用多作一個庫存的 SELECT 獎品庫存操做,只須要 UPDATE 獎品庫存 - 1 WHERE 獎品庫存 >=1,UPDATE 成功後就說明是有庫存的,而後再作後續操做,併發的時候只會有一個用戶 UPDATE 成功
庫存扣減的實現方式有不少種,並且涉及到扣減庫存的時候還須要結合實際業務場景來決定實現方案,除了扣減庫存,還須要記錄一些業務數據。數據庫在高併發量的應用中很容易遇到瓶頸,因此能夠考慮使用Redis + MQ來作請求的處理,由MQ消費者去實現後續的業務邏輯。這樣可以較快速的響應請求,避免請求阻塞而引起更多的問題
利用Redis中的incr命令來實現庫存扣減的操做。Redis從2.6.0版本開始內置了Lua解釋器,而且對Lua腳本的執行是具備原子性的,因此能夠利用此特性來作庫存的扣減,具體實現能夠參考【stock-spring-boot-starter】,starter中主要實現了初始化/重置庫存、扣減庫存、恢復庫存
Redis集羣的效率已經很是高了,可以支撐必定量的併發扣減庫存,而且因爲Redis執行Lua腳本的原子性能夠避免超扣的問題。若是一個Redis集羣還知足不了業務須要,能夠考慮將庫存進行拆分。即將庫存拆成多份,分別放到不一樣的Redis集羣當中,多個Redis集羣採用輪詢策略,基本可以在大致上保證各個Redis集羣的剩餘庫存量不會相差太大。不過也不能絕對的保證數量均勻,因此在扣減庫存操做返回庫存不足時,仍是須要必定的策略去解決這個問題,好比扣減庫存返回庫存不足時,繼續輪詢到下一個Redis集羣,當全部Redis集羣都返回庫存不足時,能夠在應用節點內或某個統一的地方打個標記表示已沒有庫存,避免每一個請求都輪詢所有的Redis集羣。
因爲利用Redis的incr命令來扣減庫存,無法存儲請求源的信息,因此扣減庫存的冪等性由應用來保證,能夠利用客戶端token或流水號之類的來作
扣減庫存都會伴隨一些業務數據須要記錄,若是實時記錄到數據庫,仍然很容易達到瓶頸,因此能夠利用MQ,將相關信息放入MQ,而後由MQ消費者去異步處理後續的業務邏輯。固然若是MQ消息發送失敗須要恢復Redis中的庫存,Redis操做和MQ操做沒法徹底保證一致性,因此在保證正常狀況下數據一致性的前提下,還須要相似對帳同樣來驗證扣減庫存和實際庫存的一致性。不過在這以前,我認爲須要更優先考慮限流問題,須要提早壓測出應用的性能瓶頸,根據壓測結果對請求配置限流,優先保證高併發狀況下應用不會崩潰掉,這樣才能更好的保證接收到的請求可以按正常代碼邏輯處理,減小發生庫存不一致的狀況
在開發業務接口的時候須要把 == 同用戶 == 和 == 多用戶 == 併發的場景考慮進去,這樣就能夠避免在併發的時候產生數據異常問題,致使成本多支出
能夠使用下面的工具進行模擬併發測試:
當前在互聯網+的大潮下,衆所周知淘寶、京東這些交易系統天天產生的數據量都是海量的,天天的交易併發也是驚人的,尤爲是「雙11」、「6.18」這些活動,對系統的峯值響應提出了很是高的要求,因此對系統架構也就有了很要的要求。
在寫這篇博客的前2天,據說某系統在25人的用戶量下就宕機了,實在讓人震驚,因此捋了下互聯網交易系統咱們能夠採起哪些技術來解決互聯網平臺下大數據量高併發的問題。
首先根據架構分層把不一樣技術進行了一些分類,以下圖:
互聯網技術架構分層策略圖
接下來我會逐一解釋各個技術的大概原理和思路,供你們參考和學習:
負載均衡英文名稱爲Load Balance,意思就是分攤到多個操做單元上進行執行,例如Web服務器、FTP服務器、企業關鍵應用服務器和其它關鍵任務服務器等,從而共同完成工做任務。
好比Nginx是一款能夠經過反向代理實現負載均衡的服務器,把流量導向不一樣的服務器;如今的雲平臺都提供了負載均衡服務,不過須要單獨付費,好比阿里的SLB。
內容分發網絡基本思路是儘量避開互聯網上有可能影響數據傳輸速度和穩定性的瓶頸和環節,使內容傳輸的更快、更穩定。
經過在網絡各處放置節點服務器所構成的在現有的互聯網基礎之上的一層智能虛擬網絡,CDN系統可以實時地根據網絡流量和各節點的鏈接、負載情況以及到用戶的距離和響應時間等綜合信息將用戶的請求從新導向離用戶最近的服務節點上。
其目的,是使用戶可就近取得所需內容,解決Internet網絡擁擠的情況,提升用戶訪問網站的響應速度。這個不須要單獨去實現,能夠用現成的產品去作,好比: Akamai(好些,比較貴),Verizon EdgeCast(便宜些),ChinaCach;若是是雲平臺基本上都提供了這個服務,不過也須要付費的,好比阿里雲基於本身的CDN加速提供了不一樣形式的加速;好比基於P2P技術的PCDN,加強防禦DDoS、CC、Web應用攻擊的SCDN以及全站加速。
傳統的B/S架構都是把用戶會話放到Session裏面,在在線用戶量不高的狀況下沒啥問題,可是對於如今互聯網採起了分佈式或者微服務架構,就很難單獨去維護Session了,由於Session會分佈在不一樣的服務器上,會話的同步會面臨着很大的問題。因此一種方式是把Session的維護拿到Cookie裏去作,不依賴於某臺或多臺服務器,同時也減小了服務器的開銷。固然,也能夠利用內存緩存服務器來統一存儲Session信息,有的內存緩存服務器還能把內存數據持久化到磁盤來提升可用性和可恢復性,就不會有同步問題了。
動態頁面靜態化,爲何又要把動態網頁以靜態網頁的形式發佈呢?一個很重要的緣由就是搜索引擎;另外一個重要緣由就是提升程序性能。
不少大型網站,進去的時候看它頁面很複雜,可是加載也沒有耗費多長時間,緣由在於先於用戶獲取資源或數據庫數據,進而經過靜態化處理,生成靜態頁面。全部人都訪問這一個靜態頁面,而靜態化處理的頁面自己的訪問速度要較動態頁面快不少倍,所以程序性能會有大大的提高。使用場景是那些常常須要訪問可是數據不常常更新的時候。這種狀況就是時候將動態頁面靜態化了,好比淘寶的寶貝信息頁面,頁面動態部分能夠用AJAX加載進來,好比月銷多少筆。
緩存,Web服務層的緩存依賴於下面三個方面:
在CDN這類技術當中作大量頁面緩存來提升就近訪問速度;
本身搭建內存緩存服務器對頻率訪問比較高的頁面進行緩存,好比首頁等。
利用瀏覽器能自動進行Gzip解壓縮的原理對訪問頁面和資源(含圖片、JavaScript、CSS等)進行Gzip壓縮,減小文件大小,以此來提升網絡加載速度。
原理是把多個須要加載的內容合成一個文件,減小加載次數和網絡鏈接時間,提升訪問效率,好比把小圖標集合合成一個大圖片,把CSS/JavaScript 合成到一個文件裏面。
集羣和傳統的高性能計算機技術相比,計算機集羣經過一組鬆散集成的計算機軟件和/或硬件鏈接起來高度緊密地協做完成計算工做。在某種意義上,它們能夠被看做是一臺計算機。
集羣系統中的單個計算機一般稱爲節點,一般經過局域網鏈接,但也有其它的可能鏈接方式。集羣計算機一般用來改進單個計算機的計算速度和/或可靠性。通常狀況下,集羣計算機比單個計算機(好比工做站或超級計算機)性能價格比要高得多,大多數集羣採用主從式來管理集羣節點,好比Websphere Cluster。
和常見的分佈式的不一樣點在於:集羣是同一個業務部署在多個服務器上;分佈式是一個業務分拆成多個子業務,或者自己就是不一樣的業務,部署在不一樣的服務器上。
簡單地說,分佈式是以縮短單個任務的執行時間來提高效率,而集羣則是經過提升單位時間內執行的任務數來提高效率。
分佈式系統是支持分佈式處理的軟件系統,是由通訊網絡互聯的多處理機體系結構上執行任務的系統。簡單來講,分佈式處理就是多臺相連的計算機各自承擔同一工做任務的不一樣部分,在人的控制下同時運行,共同完成同一件工做任務。包括分佈式操做系統、分佈式程序設計語言及其編譯系統、分佈式文件系統、分佈式數據庫系統、分佈式調度系統等。這經常伴隨須要作負載均衡、熔斷和限流等;還得考慮是全量計算仍是增量計算等。
因此隨着分佈式的發展,微服務架構就變得愈來愈流行,它的主要做用是將功能分解到離散的各個服務當中,從而下降系統的耦合性,並提供更加靈活的服務支持,圍繞業務領域組件來建立應用,這些應用可獨立地進行開發、管理和迭代。在分散的組件中使用雲架構和平臺式部署、管理和服務功能,使產品交付變得更加簡單,因此業務的解耦和拆分的就變得愈來愈重要。
如今隨着容器(Docker)的發展讓分佈式、微服務變得更加靈活和容易,也讓如今支持大數據量高併發提供了很好的基礎設施。
這一層的緩存主要是對高頻數據進行緩存,好比對中間計算結果進行緩存,並且是基於內存緩存居多,好比Memcached和Redis,有的還提供緩存持久化,好比Redis。在分佈式計算中常常要對批量數據進行緩存預讀取以提升計算速度。
同步轉異步的思路一方面不讓進程或者線程阻塞在順序執行裏,從而加快程序的執行,就像Node.js用異步和Java用同步作相同計算測試,好多時候速度Node.js比Java還快,不信你們能夠試試,像雙11的搶購都是採用了異步機制。
另外一方面不讓用戶一直等在那裏,用戶能夠繼續作別的事情,等異步執行完畢通知用戶。這裏面大量用到了消息隊列(MQ),流行的產品不少,最先的WebsphereMQ,到如今的Kafka、RabbitMQ,有些甚至和流行的開源框架緊密集成,好比RabbitMQ和SpringBoot。
由於在大數據量併發狀況下,讀的操做頻率遠遠超過寫操做,因此經過讀寫分離來提升讀的速度,其思路是讓主數據庫(master)處理事務性增、改、刪操做(INSERT、UPDATE、DELETE),而從數據庫(slave)處理SELECT查詢操做,下面是淘寶最先的時候採用的讀寫分離策略:
讀寫分離示意圖
集羣就再也不作過多說明,數據庫集羣是利用至少兩臺或者多臺數據庫服務器,構成一個虛擬單一數據庫邏輯映像,像單數據庫系統那樣,向客戶端提供透明的數據服務。其目的仍是爲了增長數據吞吐量,提升數據庫性能,知足大數據量下對數據的讀寫速度要求。
阿里雲的RDS雲數據庫就繼承了上述2大特徵,外面看來是一個MySQL集羣,提供統一的透明訪問,而在內部就自動實現了讀寫分離,爲高性能數據庫存儲提供了很大便利。
三種分佈式存儲方案,你們可自行百度,各類介紹比較比較多,這裏再也不贅述,好比阿里的OSS存儲也是一種分佈式存儲。
緩存無處不在,連CPU都有二級緩存,在數據訪問這一層,能夠根據你的數據須要充分利用緩存技術來提供讀寫速度,好比對要求不是特別實時的大數據進行預統計分析,而後緩存下來作報表等,這個時候直接從緩存裏讀取便可,提升統計速度。
NoSQL,泛指非關係型的數據庫。隨着互聯網Web2.0網站的興起,傳統的關係數據庫在應付Web2.0網站已經顯得力不從心,暴露了不少難以克服的問題,而非關係型的數據庫則因爲其自身的特色(高可擴展性、分佈式計算、低成本、架構的靈活性、半結構化數據,沒有複雜的關係)獲得了很是迅速的發展。NoSQL數據庫的產生就是爲了解決大規模數據集合多重數據種類帶來的挑戰,尤爲是大數據應用難題。其數據庫類型有列存儲、文檔存儲、Key/Value存儲、對象存儲和圖存儲等。
表分區是DB對於很是大的表進行優化的一種有效方法,是根據數據庫定義不一樣的分區策略決定的,好比取模、時間和哈希等,是很是有效的一種手段,在不少狀況下比表分割更有效。
好比,有一個代碼表使用分區表把100萬紀錄分在10個分區中(ID每從1到10萬爲一個分區),那樣寫查詢語句的時候,只要給出查詢條件中所須要的代碼,DB自動會定位到對應的分區進行查詢,大大下降的查詢時間。
而採用表分割那必須先根據查詢的代碼指定所要查詢的表,才能找到相應的記錄,是由DBA或架構師根據業務須要來定義如何分割的。表分割分爲水平分割和垂直分割:
水平分割:根據一列或多列數據的值把數據行放到兩個獨立的表中;
垂直分割:把主碼和一些列放到一個表,而後把主碼和另外的列放到另外一個表中。
邊界網關協議,主要用於互聯網AS(自治系統)之間的互聯,BGP的最主要功能在於控制路由的傳播和選擇最好的路由。中國網通與中國電信都具備AS號(自治系統號),全國各大網絡運營商多數都是經過BGP協議與自身的AS號來互聯的。
使用此方案來實現雙線路須要在CNNIC(中國互聯網信息中心)申請IDC本身的IP地址段和AS號,而後經過BGP協議將此段IP地址廣播到移動,網通、電信等其它的網絡運營商,使用BGP協議互聯後移動。網通與電信的全部骨幹路由設備將會判斷到IDC機房IP段的最佳路由,以保證移動、網通和電信用戶的高速訪問。如今很多的雲平臺都支持BGP。
參考:高併發限流策略