架构设计 | 高并发流量削峰,共享资源加锁机制
本文源碼:GitHub·點(diǎn)這里 || GitEE·點(diǎn)這里
一、高并發(fā)簡介
在互聯(lián)網(wǎng)的業(yè)務(wù)架構(gòu)中,高并發(fā)是最難處理的業(yè)務(wù)之一,常見的使用場景:秒殺,搶購,訂票系統(tǒng);高并發(fā)的流程中需要處理的復(fù)雜問題非常多,主要涉及下面幾個(gè)方面:
- 流量管理,逐級(jí)承接削峰;
- 網(wǎng)關(guān)控制,路由請(qǐng)求,接口熔斷;
- 并發(fā)控制機(jī)制,資源加鎖;
- 分布式架構(gòu),隔離服務(wù)和數(shù)據(jù)庫;
高并發(fā)業(yè)務(wù)核心還是流量控制,控制流量下沉速度,或者控制承接流量的容器大小,多余的直接溢出,這是相對(duì)復(fù)雜的流程。其次就是多線程并發(fā)下訪問共享資源,該流程需要加鎖機(jī)制,避免數(shù)據(jù)寫出現(xiàn)錯(cuò)亂情況。
二、秒殺場景
1、預(yù)搶購業(yè)務(wù)
活動(dòng)未正式開始,先進(jìn)行活動(dòng)預(yù)約,先把一部分流量收集和控制起來,在真正秒殺的時(shí)間點(diǎn),很多數(shù)據(jù)可能都已經(jīng)預(yù)處理好了,可以很大程度上削減系統(tǒng)的壓力。有了一定預(yù)約流量還可以提前對(duì)庫存系統(tǒng)做好準(zhǔn)備,一舉兩得。
場景:活動(dòng)預(yù)約,定金預(yù)約,高鐵搶票預(yù)購。
2、分批搶購
分批搶購和搶購的場景實(shí)現(xiàn)的機(jī)制是一致的,只是在流量上緩解了很多壓力,秒殺10W件庫存和秒殺100件庫存系統(tǒng)的抗壓不是一個(gè)級(jí)別。如果秒殺10W件庫存,系統(tǒng)至少承擔(dān)多于10W幾倍的流量沖擊,秒殺100件庫存,體系可能承擔(dān)幾百或者上千的流量就結(jié)束了。下面流量削峰會(huì)詳解這里的策略機(jī)制。
場景:分時(shí)段多場次搶購,高鐵票分批放出。
3、實(shí)時(shí)秒殺
最有難度的場景就是準(zhǔn)點(diǎn)實(shí)時(shí)的秒殺活動(dòng),假如10點(diǎn)整準(zhǔn)時(shí)搶1W件商品,在這個(gè)時(shí)間點(diǎn)前后會(huì)涌入高并發(fā)的流量,刷新頁面,或者請(qǐng)求搶購的接口,這樣的場景處理起來是最復(fù)雜的。
- 首先系統(tǒng)要承接住流量的涌入;
- 頁面的不斷刷新要實(shí)時(shí)加載;
- 高并發(fā)請(qǐng)求的流量控制加鎖等;
- 服務(wù)隔離和數(shù)據(jù)庫設(shè)計(jì)的系統(tǒng)保護(hù);
場景:618準(zhǔn)點(diǎn)搶購,雙11準(zhǔn)點(diǎn)秒殺,電商促銷秒殺。
三、流量削峰
1、Nginx代理
Nginx是一個(gè)高性能的HTTP和反向代理web服務(wù)器,經(jīng)常用在集群服務(wù)中做統(tǒng)一代理層和負(fù)載均衡策略,也可以作為一層流量控制層,提供兩種限流方式,一是控制速率,二是控制并發(fā)連接數(shù)。
基于漏桶算法,提供限制請(qǐng)求處理速率能力;限制IP的訪問頻率,流量突然增大時(shí),超出的請(qǐng)求將被拒絕;還可以限制并發(fā)連接數(shù)。
高并發(fā)的秒殺場景下,經(jīng)過Nginx層的各種限制策略,可以控制流量在一個(gè)相對(duì)穩(wěn)定的狀態(tài)。
2、CDN節(jié)點(diǎn)
CDN靜態(tài)文件的代理節(jié)點(diǎn),秒殺場景的服務(wù)有這樣一個(gè)操作特點(diǎn),活動(dòng)倒計(jì)時(shí)開始之前,大量的用戶會(huì)不斷的刷新頁面,這時(shí)候靜態(tài)頁面可以交給CDN層面代理,分擔(dān)數(shù)據(jù)服務(wù)接口的壓力。
CDN層面也可以做一層限流,在頁面內(nèi)置一層策略,假設(shè)有10W用戶點(diǎn)擊搶購,可以只放行1W的流量,其他的直接提示活動(dòng)結(jié)束即可,這也是常用的手段之一。
話外之意:平時(shí)參與的搶購活動(dòng),可能你的請(qǐng)求根本沒有到達(dá)數(shù)據(jù)接口層面,就極速響應(yīng)商品已搶完,自行意會(huì)吧。
3、網(wǎng)關(guān)控制
網(wǎng)關(guān)層面處理服務(wù)接口路由,一些校驗(yàn)之外,最主要的是可以集成一些策略進(jìn)入網(wǎng)關(guān),比如經(jīng)過上述層層的流量控制之后,請(qǐng)求已經(jīng)接近核心的數(shù)據(jù)接口,這時(shí)在網(wǎng)關(guān)層面內(nèi)置一些策略控制:如果活動(dòng)是想激活老用戶,網(wǎng)關(guān)層面快速判斷用戶屬性,老用戶會(huì)放行請(qǐng)求;如果活動(dòng)的目的是拉新,則放行更多的新用戶。
經(jīng)過這些層面的控制,剩下的流量已經(jīng)不多了,后續(xù)才真正開始執(zhí)行搶購的數(shù)據(jù)操作。
話外之意:如果有10W人參加搶購活動(dòng),真正下沉到底層的搶購流量可能就1W,甚至更少,在分散到集群服務(wù)中處理。
4、并發(fā)熔斷
在分布式服務(wù)的接口中,還有最精細(xì)的一層控制,對(duì)于一個(gè)接口在單位之間內(nèi)控制請(qǐng)求處理的數(shù)量,這個(gè)基于接口的響應(yīng)時(shí)間綜合考慮,響應(yīng)越快,單位時(shí)間內(nèi)的并發(fā)量就越高,這里邏輯不難理解。
言外之意:流量經(jīng)過層層控制,數(shù)據(jù)接口層面分擔(dān)的壓力已經(jīng)不大,這時(shí)候就是面對(duì)秒殺業(yè)務(wù)中的加鎖問題了。
四、分布式加鎖
1、悲觀鎖
機(jī)制描述
所有請(qǐng)求的線程必須在獲取鎖之后,才能執(zhí)行數(shù)據(jù)庫操作,并且基于序列化的模式,沒有獲取鎖的線程處于等待狀態(tài),并且設(shè)定重試機(jī)制,在單位時(shí)間后再次嘗試獲取鎖,或者直接返回。
過程圖解
Redis基礎(chǔ)命令
SETNX:加鎖的思路是,如果key不存在,將key設(shè)置為value如果key已存在,則 SETNX 不做任何動(dòng)作。并且可以給key設(shè)置過期時(shí)間,過期后其他線程可以繼續(xù)嘗試鎖獲取機(jī)制。
借助Redis的該命令模擬鎖的獲取動(dòng)作。
代碼實(shí)現(xiàn)
這里基于Redis實(shí)現(xiàn)的鎖獲取和釋放機(jī)制。
import org.springframework.stereotype.Component; import redis.clients.jedis.Jedis; import javax.annotation.Resource; @Component public class RedisLock {@Resourceprivate Jedis jedis ;/*** 獲取鎖*/public boolean getLock (String key,String value,long expire){try {String result = jedis.set( key, value, "nx", "ex", expire);return result != null;} catch (Exception e){e.printStackTrace();}finally {if (jedis != null) jedis.close();}return false ;}/*** 釋放鎖*/public boolean unLock (String key){try {Long result = jedis.del(key);return result > 0 ;} catch (Exception e){e.printStackTrace();}finally {if (jedis != null) jedis.close();}return false ;} }這里基于Jedis的API實(shí)現(xiàn),這里提供一份配置文件。
@Configuration public class RedisConfig {@Beanpublic JedisPoolConfig jedisPoolConfig (){JedisPoolConfig jedisPoolConfig = new JedisPoolConfig() ;jedisPoolConfig.setMaxIdle(8);jedisPoolConfig.setMaxTotal(20);return jedisPoolConfig ;}@Beanpublic JedisPool jedisPool (@Autowired JedisPoolConfig jedisPoolConfig){return new JedisPool(jedisPoolConfig,"127.0.0.1",6379) ;}@Beanpublic Jedis jedis (@Autowired JedisPool jedisPool){return jedisPool.getResource() ;} }問題描述
在實(shí)際的系統(tǒng)運(yùn)行期間可能出現(xiàn)如下情況:線程01獲取鎖之后,進(jìn)程被掛起,后續(xù)該執(zhí)行的沒有執(zhí)行,鎖失效后,線程02又獲取鎖,在數(shù)據(jù)庫更新后,線程01恢復(fù),此時(shí)在持有鎖之后的狀態(tài),繼續(xù)執(zhí)行后就會(huì)容易導(dǎo)致數(shù)據(jù)錯(cuò)亂問題。
這時(shí)候就需要引入鎖版本概念的,假設(shè)線程01獲取鎖版本1,如果沒有執(zhí)行,線程02獲取鎖版本2,執(zhí)行之后,通過鎖版本的比較,線程01的鎖版本過低,數(shù)據(jù)更新就會(huì)失敗。
CREATE TABLE `dl_data_lock` (`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',`inventory` INT (11) DEFAULT '0' COMMENT '庫存量',`lock_value` INT (11) NOT NULL DEFAULT '0' COMMENT '鎖版本',PRIMARY KEY (`id`) ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '鎖機(jī)制表';說明:lock_value就是記錄鎖版本,作為控制數(shù)據(jù)更新的條件。
<update id="updateByLock">UPDATE dl_data_lock SET inventory=inventory-1,lock_value=#{lockVersion}WHERE id=#{id} AND lock_value <#{lockVersion} </update>說明:這里的更新操作,不但要求線程獲取鎖,還會(huì)判斷線程鎖的版本不能低于當(dāng)前更新記錄中的最新鎖版本。
2、樂觀鎖
機(jī)制描述
樂觀鎖大多是基于數(shù)據(jù)記錄來控制,在更新數(shù)據(jù)庫的時(shí)候,基于前置的查詢條件判斷,如果查詢出來的數(shù)據(jù)沒有被修改,則更新操作成功,如果前置的查詢結(jié)果作為更新的條件不成立,則數(shù)據(jù)寫失敗。
過程圖解
代碼實(shí)現(xiàn)
業(yè)務(wù)流程,先查詢要更新的記錄,然后把讀取的列,作為更新條件。
@Override public Boolean updateByInventory(Integer id) {DataLockEntity dataLockEntity = dataLockMapper.getById(id);if (dataLockEntity != null){return dataLockMapper.updateByInventory(id,dataLockEntity.getInventory())>0 ;}return false ; }例如如果要把庫存更新,就把讀取的庫存數(shù)據(jù)作為更新條件,如果讀取庫存是100,在更新的時(shí)候庫存變了,則更新條件自然不能成立。
<update id="updateByInventory">UPDATE dl_data_lock SET inventory=inventory-1 WHERE id=#{id} AND inventory=#{inventory} </update>五、分布式服務(wù)
1、服務(wù)保護(hù)
在處理高并發(fā)的秒殺場景時(shí),經(jīng)常出現(xiàn)服務(wù)掛掉場景,常見某些APP的營銷頁面,出現(xiàn)活動(dòng)火爆頁面丟失的提示情況,但是不影響整體應(yīng)用的運(yùn)行,這就是服務(wù)的隔離和保護(hù)機(jī)制。
基于分布式的服務(wù)結(jié)構(gòu)可以把高并發(fā)的業(yè)務(wù)服務(wù)獨(dú)立出來,不會(huì)因?yàn)槊霘⒎?wù)掛掉影響整體的服務(wù),導(dǎo)致服務(wù)雪崩的場景。
2、數(shù)據(jù)庫保護(hù)
數(shù)據(jù)庫保護(hù)和服務(wù)保護(hù)是相輔相成的,分布式服務(wù)架構(gòu)下,服務(wù)和數(shù)據(jù)庫是對(duì)應(yīng)的,理論上秒殺服務(wù)對(duì)應(yīng)的就是秒殺數(shù)據(jù)庫,不會(huì)因?yàn)槊霘鞉斓?#xff0c;導(dǎo)致整個(gè)數(shù)據(jù)庫宕機(jī)。
六、源代碼地址
GitHub·地址 https://github.com/cicadasmile/data-manage-parent GitEE·地址 https://gitee.com/cicadasmile/data-manage-parent推薦閱讀:《架構(gòu)設(shè)計(jì)系列》,蘿卜青菜,各有所需
| 00 | 架構(gòu)設(shè)計(jì):單服務(wù).集群.分布式,基本區(qū)別和聯(lián)系 |
| 01 | 架構(gòu)設(shè)計(jì):分布式業(yè)務(wù)系統(tǒng)中,全局ID生成策略 |
| 02 | 架構(gòu)設(shè)計(jì):分布式系統(tǒng)調(diào)度,Zookeeper集群化管理 |
| 03 | 架構(gòu)設(shè)計(jì):接口冪等性原則,防重復(fù)提交Token管理 |
| 04 | 架構(gòu)設(shè)計(jì):緩存管理模式,監(jiān)控和內(nèi)存回收策略 |
| 05 | 架構(gòu)設(shè)計(jì):異步處理流程,多種實(shí)現(xiàn)模式詳解 |
總結(jié)
以上是生活随笔為你收集整理的架构设计 | 高并发流量削峰,共享资源加锁机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法训练 未名湖边的烦恼
- 下一篇: 蓝桥杯--算法训练 表达式计算