天天看點

電商那些年,我摸爬打滾出的高并發架構實戰幹貨

作者:SFLYQ

http://blog.thankbabe.com/2016/09/14/high-concurrency-scheme/

一、關于高并發

高并發是指在同一個時間點,有很多使用者同時通路URL位址,比如:淘寶的雙11、雙12,就會産生高并發。又如貼吧的爆吧,就是惡意的高并發請求,也就是DDOS攻擊,再屌絲點的說法就像玩LOL被ADC暴擊了一樣,那傷害你懂的。

1、高并發會來帶的後果

服務端:導緻站點伺服器/DB伺服器資源被占滿崩潰,資料的存儲和更新結果和理想的設計是不一樣的,比如:出現重複的資料記錄,多次添加了使用者積分等。

使用者角度:尼瑪,這麼卡,老子來參加活動的,重新整理了還是這樣,垃圾網站,再也不來了!

我的經曆:在做公司産品網站的過程中,經常會有這樣的需求,比如搞個活動專題、抽獎、簽到、積分競拍等等,如果沒有考慮到高并發下的資料處理,那就Game Over了,很容易導緻抽獎被多抽走,簽到發現一個使用者有多條記錄等等,各種超出正常邏輯的現象,這就是做産品網站必須考慮的問題,因為這些都是面向大量使用者的,而不是像做ERP管理系統、OA系統那樣,隻是面向員工。

下面我進行執行個體分析,簡單粗暴,動态分析,純屬本人經驗分享,如有說錯或者更好的建議,請留言,大家一起成長。

2、并發下的資料處理

通過表設計,如:記錄表添加唯一限制,資料處理邏輯使用事物防止并發下的資料錯亂問題。通過服務端鎖程序防止包并發下的資料錯亂問題。這裡主要講述的是在并發請求下的資料邏輯處理的接口,如何保證資料的一緻性和完整性,這裡的并發可能是大量使用者發起的,也可能攻擊者通過并發工具發起的并發請求。

例子1:通過表設計防止并發導緻資料錯亂

需求點:

【簽到功能】一天一個使用者隻能簽到一次,簽到成功後使用者擷取到一個積分。

已知表:

1、使用者表,包含積分字段;

2、高并發意淫分析(屬于開發前的猜測): 在高并發的情況下,會導緻一個使用者簽到記錄會有多條,或者使用者簽到後不止加一積分。

我的設計:首先根據需求我會添加一張簽到記錄表,重點來了,這張表需要把使用者唯一辨別字段(ID,Token)和簽到日期字段添加為唯一限制,或者唯一索引,這樣就可以防止并發的時候插入重複使用者的簽到記錄。然後再程式代碼邏輯裡,先執行簽到資料的添加(這裡可以防止并發,添加成功後再進行積分的添加,這樣就可以防止重複地添加積分了。最後我還是建議所有的資料操作都寫在一個sql事務裡面, 這樣在添加失敗,或者編輯使用者積分失敗的時候可以復原資料。

例子2:事務+通過更新鎖,防止并發導緻資料錯亂;或者事物+Update的鎖表機制

需求點:【抽獎功能】抽獎一次消耗一個積分,抽獎中獎後編輯剩餘獎品總數,剩餘獎品總數為0,或者使用者積分為0的時候無法進行抽獎。

已知表:使用者表,包含積分字段 獎品表,包含獎品剩餘數量字段。

高并發意淫分析(屬于開發前的猜測):在高并發的情況下,會導緻使用者參與抽獎的時候積分被扣除,而獎品實際上已經被抽完了。

我的設計:在事物裡,通過WITH(UPDLOCK)鎖住商品表,或者Update 表的獎品剩餘數量和最後編輯時間字段,來把資料行鎖住,然後進行使用者積分的消耗,都完成後送出事物,失敗就復原。 這樣就可以保證,隻有可能存在一個操作在操作這件商品的數量,隻有等到這個操作事物送出後,其他的操作這個商品行的事物才會繼續執行。

例子3:通過程式代碼防止包并發下的資料錯亂問題

需求點:【緩存資料到cache裡】,當緩存不存在的時候,從資料庫中擷取并儲存在cache裡,如果存在從cache裡擷取,每天10點必須更新一次,其他時間點緩存兩個小時更新一次 到10點的時候,凡是打開頁面的使用者會自動重新整理頁面。

問題點:這裡有個邏輯使用者觸發緩存的更新,使用者重新整理頁面,當緩存存在的時候,會取到最後一次緩存更新時間,如果目前時間大于十點,并且最後緩存時間是10點前,則會從資料庫中重新擷取資料儲存到cache中。 還有用戶端頁面會在10點時候用js發起頁面的重新整理,就是因為有這樣的邏輯,導緻10點的時候有很多并發請求同時過來,然後就會導緻很多的sql查詢操作,理想的邏輯是,隻有一個請求會去資料庫擷取,其他都是從緩存中擷取資料。(因為這個sql查詢很耗伺服器性能,是以導緻在10點的時候,突然間資料庫伺服器壓力暴增)

解決問題:C#通過(鎖)lock,在從資料讀取到緩存的那段代碼前面加上鎖,這樣在并發的情況下隻會有一個請求是從資料庫裡擷取資料,其他都是從緩存中擷取。

3、通路量大的資料統計接口

需求: 使用者行為資料統計接口,用來記錄商品展示次數,使用者通過點選圖檔,或者連結,或者其他方式進入到商品詳情的行為次數。

問題點:這接口是給前端ajax使用,通路量會很大,一頁面展示的時候就會有幾十件商品的展示,滾動條滾到到頁面顯示商品的時候就會請求接口進行展示資料的統計,每次翻頁又會加載幾十件。

意淫分析:設想如果同時有1W個使用者同時線上通路頁面,一個次拉動滾動條螢幕頁面展示10件商品,這樣就會有10W個請求過來,服務端需要把請求資料入庫。在實際線上環境可能還會超過這個請求量,如果不經過進行高并發設計處理,伺服器分分鐘給跪了。

解決問題:我們通過nodejs寫了一個資料處理接口,把統計資料先存到redis的list裡。(使用nodejs寫接口的好處是,nodejs使用單線程異步事件機制,高并發處理能力強,不會因為資料邏輯處理問題導緻伺服器資源被占用而導緻伺服器當機) 然後再使用nodejs寫了一個腳本,腳本功能就是從redis裡出列資料儲存到mysql資料庫中。這個腳本會一直運作,當redis沒有資料需要同步到資料庫中的時候,sleep,讓在進行資料同步操作。

4、高并發的下的伺服器壓力均衡,合理站點架設,DB部署

以下我所知道的:

伺服器代理nginx,做伺服器的均衡負載,把壓力均衡到多台伺服器;

部署叢集MySQL資料庫, Redis伺服器,或者MongoDB伺服器,把一些常用的查詢資料,并且不會經常的變化的資料儲存到其他NoSQL DB伺服器中,來減少資料庫伺服器的壓力,加快資料的響應速度;

資料緩存,Cache;

在高并發接口的設計中可以使用具有高并發能力的程式設計語言去開發,如:nodejs做web接口;

伺服器部署,圖檔伺服器分離,靜态檔案走CDN;

DBA資料庫的優化查詢條件,索引優化;

消息存儲機制,将資料添加到資訊隊列中(redis list),然後再寫工具去入庫

腳本合理控制請求,如,防止使用者重複點選導緻的ajax多餘的請求,等等。

5、并發測試神器推薦

Apache JMeter

Microsoft Web Application Stress Tool

Visual Studio 性能負載

二、關于高并發架構

為了讓業務可以流暢地運作并且給使用者一個好的互動體驗,我們需要根據業務場景預估達到的并發量等因素,來設計适合自己業務場景的高并發處理方案。

在電商相關産品開發的這些年,我有幸遇到了并發下的各種坑,這一路摸爬滾打過來有着不少的血淚史,這裡進行總結,作為自己的歸檔記錄,同時分享給大家。

1、伺服器架構

業務從發展的初期到逐漸成熟,伺服器架構也是從相對單一到叢集,再到分布式服務。

一個可以支援高并發的服務少不了好的伺服器架構,需要有均衡負載,資料庫需要主從叢集,NoSQL緩存需要主從叢集,靜态檔案需要上傳CDN,這些都是能讓業務程式流暢運作的強大後盾。

伺服器這塊多是需要運維人員來配合搭建,具體我就不多說了,點到為止。

大緻需要用到的伺服器架構如下:

伺服器:

均衡負載(如:nginx,阿裡雲SLB)

資源監控

分布式

資料庫:

主從分離,叢集

DBA 表優化,索引優化,等

NoSQL:

Redis

MongoDB

memcache

CDN:

html

css

js

image

具體的架構方案請點此檢視。

2、并發測試

高并發相關的業務,需要進行并發的測試,通過大量的資料分析評估出整個架構可以支撐的并發量。

測試高并發可以使用第三方伺服器或者自己測試伺服器,利用測試工具進行并發請求測試,分析測試資料得到可以支撐并發數量的評估,這個可以作為一個預警參考,俗話說知己自彼百戰不殆。

第三方服務:

阿裡雲性能測試

并發測試工具:

Visual Studio性能負載測試

3 **[實戰方案**](http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247486088&idx=1&sn=48812205b3040a3ab869db39904047f6&chksm=eb538fbedc2406a818ffa658caa29359689c78f790610faeed47e2d3e64e2eba62a6bdbf58a8&scene=21#wechat_redirect)

1)通用方案

日使用者流量大,但是比較分散,偶爾會有使用者高聚的情況;

場景: 使用者簽到,使用者中心,使用者訂單等。

伺服器架構圖:

電商那些年,我摸爬打滾出的高并發架構實戰幹貨

更多技術架構方案請點此檢視。

說明:

場景中的這些業務基本是使用者進入APP後會操作到的,除了活動日(618、雙11等),這些業務的使用者量都不會高聚集,同時這些業務相關的表都是大資料表,業務多是查詢操作,是以我們需要減少使用者直接命中DB的查詢;優先查詢緩存,如果緩存不存在,再進行DB查詢,将查詢結果緩存起來。

更新使用者相關緩存需要分布式存儲,比如使用使用者ID進行hash分組,把使用者分布到不同的緩存中,這樣一個緩存集合的總量不會很大,不會影響查詢效率。

方案如:

使用者簽到擷取積分:

計算出使用者分布的key,Redis,hash中查找使用者今日簽到資訊

如果查詢到簽到資訊,傳回簽到資訊

如果沒有查詢到,DB查詢今日是否簽到過,如果有簽到過,就把簽到資訊同步Redis緩存。

如果DB中也沒有查詢到今日的簽到記錄,就進行簽到邏輯,操作DB添加今日簽到記錄,添加簽到積分(這整個DB操作是一個事務)

緩存簽到資訊到Redis,傳回簽到資訊

注意這裡會有并發情況下的邏輯問題,如:一天簽到多次,發放多次積分給使用者。

使用者訂單:

這裡我們隻緩存使用者第一頁的訂單資訊,一頁40條資料,使用者一般也隻會看第一頁的訂單資料

使用者通路訂單清單,如果是第一頁讀緩存,如果不是讀DB

計算出使用者分布的key,Redis,hash中查找使用者訂單資訊

如果查詢到使用者訂單資訊,傳回訂單資訊

如果不存在就進行DB查詢第一頁的訂單資料,然後緩存redis,傳回訂單資訊

使用者中心:

計算出使用者分布的key,Redis hash中查找使用者訂單資訊

如果查詢到使用者資訊,傳回使用者資訊

如果不存在進行使用者DB查詢,然後緩存redis,傳回使用者資訊

其他業務:

上面例子多是針對使用者存儲緩存,如果是公用的緩存資料需要注意一些問題,如:公用的緩存資料需要考慮并發下的可能會導緻大量命中DB查詢,可以使用管理背景更新緩存,或者DB查詢的鎖住操作。

以上例子是一個相對簡單的高并發架構,并發量不是很高的情況可以很好的支撐,但是随着業務的壯大,使用者并發量增加,我們的架構也會進行不斷的優化和演變,比如對業務進行服務化,每個服務有自己的并發架構,自己的均衡伺服器,分布式資料庫,NoSQL主從叢集,如:使用者服務、訂單服務。

2)消息隊列

秒殺、秒搶等活動業務,使用者在瞬間湧入産生高并發請求。

場景:定時領取紅包等。

電商那些年,我摸爬打滾出的高并發架構實戰幹貨

場景中的定時領取是一個高并發的業務,像秒殺活動使用者會在到點的時間湧入,DB瞬間就接受到一記暴擊,hold不住就會當機,然後影響整個業務;

像這種不是隻有查詢的操作并且會有高并發的插入或者更新資料的業務,前面提到的通用方案就無法支撐,并發的時候都是直接命中DB;

設計這塊業務的時候就會使用消息隊列的,可以将參與使用者的資訊添加到消息隊列中,然後再寫個多線程程式去消耗隊列,給隊列中的使用者發放紅包;

定時領取紅包;

一般習慣使用 redis的 list;

當使用者參與活動,将使用者參與資訊push到隊列中;

然後寫個多線程程式去pop資料,進行發放紅包的業務;

這樣可以支援高并發下的使用者可以正常的參與活動,并且避免資料庫伺服器當機的危險。

附加: 通過消息隊列可以做很多的服務。

如:定時短信發送服務,使用sset(sorted set),發送時間戳作為排序依據,短信資料隊列根據時間升序,然後寫個程式定時循環去讀取sset隊列中的第一條,目前時間是否超過發送時間,如果超過就進行短信發送。

3)一級緩存

高并發請求連接配接緩存伺服器超出伺服器能夠接收的請求連接配接量,部分使用者出現建立連接配接逾時無法讀取到資料的問題;

是以需要有個方案當高并發時候時候可以減少命中緩存伺服器;

這時候就出現了一級緩存的方案,一級緩存就是使用站點伺服器緩存去存儲資料,注意隻存儲部分請求量大的資料,并且緩存的資料量要控制,不能過分的使用站點伺服器的記憶體而影響了站點應用程式的正常運作,一級緩存需要設定秒機關的過期時間,具體時間根據業務場景設定,目的是當有高并發請求的時候可以讓資料的擷取命中到一級緩存,而不用連接配接緩存NoSQL資料伺服器,減少NoSQL資料伺服器的壓力。

比如APP首屏商品資料接口,這些資料是公共的不會針對使用者自定義,而且這些資料不會頻繁的更新,像這種接口的請求量比較大就可以加入一級緩存;

電商那些年,我摸爬打滾出的高并發架構實戰幹貨

合理地規範和使用NoSQL緩存資料庫,根據業務拆分緩存資料庫的叢集,這樣基本可以很好支援業務,一級緩存畢竟是使用站點伺服器緩存是以還是要善用。

4)靜态化資料

高并發請求資料不變化的情況下如果可以不請求自己的伺服器擷取資料那就可以減少伺服器的資源壓力。

對于更新頻繁度不高,并且資料允許短時間内的延遲,可以通過資料靜态化成JSON、XML、HTML等資料檔案上傳CDN,在拉取資料的時候優先到CDN拉取,如果沒有擷取到資料再從緩存,資料庫中擷取,當管理人員操作背景編輯資料再重新生成靜态檔案上傳同步到CDN,這樣在高并發的時候可以使資料的擷取命中在CDN伺服器上。

CDN節點同步有一定的延遲性,是以找一個靠譜的CDN伺服器商也很重要。

5)其他方案

對于更新頻繁度不高的資料,APP、PC浏覽器,可以緩存資料到本地,然後每次請求接口的時候上傳目前緩存資料的版本号,服務端接收到版本号判斷版本号與最新資料版本号是否一緻,如果不一樣就進行最新資料的查詢并傳回最新資料和最新版本号,如果一樣就傳回狀态碼告知資料已經是最新。減少伺服器壓力:資源、帶寬。