日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

原文出自:公眾號 sowhat1412

原文鏈接:
https://mp.weixin.qq.com/s/rbXrhzIJG2NtYt_61OmzTA

1 秒殺場景

秒殺場景

  1. 登陸12306進行火車票搶座
  2. 1599元購入飛天茅臺
  3. 周董演唱會的門票
  4. 雙十一秒殺活動

秒殺場景關(guān)注點

  1. 嚴格防止超賣:庫存1000件賣了1020件,要殺個碼農(nóng)祭天了!防止超賣是秒殺系統(tǒng)設(shè)計最核心的部分。
  2. 防止黑產(chǎn):防止不懷好意的羊毛黨薅羊毛。
  3. 保證用戶體驗:高并發(fā)下,給用戶提供友善的購物體驗,盡可能支持比較高的QPS等等。

接下來就讓我們按照關(guān)注點,不斷細化秒殺場景。

2 第1版-裸奔

硬核講解:秒殺設(shè)計

裸奔秒殺

不加思考,上來直接按照 SpringBoot + MyBatis 模式進行秒殺系統(tǒng)的設(shè)計,流程如下:

  1. Controller層獲得用戶秒殺請求后調(diào)用Service層。
  2. Service層獲得請求后要要檢查已售數(shù)據(jù)跟庫存總量是否一致,一致說明商品賣沒了,不一致說明還有庫存,那就調(diào)用DAO層對已售數(shù)量進行加1。
  3. DAO層獲得請求后直接通過MyBatis操作數(shù)據(jù)庫實現(xiàn)已售數(shù)量加1跟訂單創(chuàng)建。

如果你用Postman去測試會發(fā)現(xiàn)是OK的,但如果你用專業(yè)的并發(fā)測試工具JMeter模式多用戶并發(fā)請求會發(fā)現(xiàn)訂單創(chuàng)建數(shù)量 > 庫存量 - 已售量。原因解釋下,比如用戶A、B并發(fā)進行秒殺請求,此時庫存=100,已售=64。

  1. A用戶進行描述請求,此時調(diào)用到了Service層,發(fā)現(xiàn)已售不等于庫存,此時拿到庫存數(shù)是64,A將庫存更新為63,然后創(chuàng)建訂單。
  2. B用戶進行描述請求,此時調(diào)用到了Service層,發(fā)現(xiàn)已售不等于庫存,此時拿到庫存數(shù)是64,B將庫存更新為63,然后創(chuàng)建訂單。
  3. 此時庫存減少了1個但是訂單創(chuàng)建多個,賣超了!
    無鎖并發(fā)請求,賣超了

3 第2版-悲觀鎖

硬核講解:秒殺設(shè)計

syn悲觀鎖


遇見 并發(fā)問題 很容易想到以前學過并發(fā)編程嘛,既然Controller默認是單例模式,那我用 synchronized 將Controller層調(diào)用Service層的代碼進行加鎖同步即可。

這樣就可以解決賣超問題了,但是須知,既然是悲觀鎖,如果有1000個并發(fā)請求,那只有1個拿到鎖了。有999個會去競爭這個鎖的。

@Transactional
@Service
@Transactional
@Slf4j
public class OrderServiceImpl implements OrderService
{
//校驗庫存
Stock stock = checkStock(id);
//更新庫存
updateSale(stock);
//創(chuàng)建訂單
return createOrder(stock);
}

當然了你也可以用Spring自帶的事務(wù)注解來實現(xiàn)悲觀鎖的操作,因為用了@Transactional就可以實現(xiàn)通過事務(wù)來控制,要么全部成功,要么全部失敗,用事務(wù)時有兩點需注意:

  1. 盡可能將MySQL執(zhí)行語句往方法體后面靠,因為MySQL事務(wù)的commit語句是在第一次執(zhí)行MySQL相關(guān)語句開始,一直到方法的結(jié)束。
  2. 設(shè)置事務(wù)的超時時間,如果不設(shè)置默認是-1是無限長。并且事務(wù)中設(shè)置的耗時timeout = 最后一個MySQL語句耗時 + 以及最后一個MySQL之前的所有耗時。

需注意:悲觀鎖狀態(tài)下會保證商品賣出去,如果沒拿到鎖的線程會阻塞的等待拿鎖。但是他的阻塞也會給用戶帶來非常不良好的體驗。

4 第3版-樂觀鎖

硬核講解:秒殺設(shè)計

MySQL版本號


我們?yōu)槊總€數(shù)量的已售數(shù)據(jù)配備個版本號,在Service層調(diào)用時獲得用戶的已售數(shù)跟對應(yīng)版本號,然后更新時將已售數(shù)跟版本號同時更新。因為 MySQL在更新時會自帶樂觀加速機制,如果更新成功則表示搶購成功,更新失敗則表示搶購失敗,此時你會發(fā)現(xiàn)不是手速越快就一定能搶到的哦,但起碼保證了不會超賣,

update 庫存表 set
已售數(shù)=已售數(shù)+1,版本號=版本號+1
where 秒殺id =#{id} and 版本號 = #{version}

需注意:樂觀鎖狀態(tài)下,由于是隨機性的秒殺失敗,所以可能活動結(jié)束后還會有幾個沒售出去的!

5 第4版-限流

最核心的超賣問題已經(jīng)解決了,接下來就是各種優(yōu)化手段了。在高并發(fā)請求中如果不對接口限流會對后臺服務(wù)器造成極大壓力,所以一般秒殺系統(tǒng)為了不影響其他業(yè)務(wù)會單獨部署到個某個服務(wù)器上,同時還會設(shè)置好限流。

常用的限流方法有我們在 redis 中曾經(jīng)說過,主要有漏桶算法、令牌桶算法。而google開源項目Guava中RateLimiter使用的就是令牌桶控制算法。在開發(fā)高并發(fā)系統(tǒng)時有三把利器用來保護系統(tǒng):緩存、降級、限流

  1. 緩存:緩存的目的是提升系統(tǒng)訪問速度和增大系統(tǒng)處理容量。
  2. 降級:降級是當服務(wù)器壓力劇增的情況下,根據(jù)當前業(yè)務(wù)情況及流量對一些服務(wù)和頁面有策略的降級,以此釋放服務(wù)器資源以保證核心任務(wù)的正常運行。
  3. 限流:限流的目的是通過對并發(fā)訪問/請求進行限速,或者對一個時間窗口內(nèi)的請求進行限速來保護系統(tǒng),一旦達到限制速率則可以拒絕服務(wù)、排隊或等待、降級等處理。

5.1 漏桶算法

漏桶算法思路:把水比作是請求,漏桶比作是系統(tǒng)處理能力極限,水先進入到漏桶里,漏桶里的水按一定速率流出,當流出的速率小于流入的速率時,由于漏桶容量有限,后續(xù)進入的水直接溢出(拒絕請求),以此實現(xiàn)限流。

硬核講解:秒殺設(shè)計

 

5.2 令牌桶算法

令牌桶算法原理:可以理解成醫(yī)院的掛號看病,只有拿到號以后才可以進行診病。

硬核講解:秒殺設(shè)計

 


流程大致

  1. 所有的請求在處理之前都需要拿到一個可用的令牌才會被處理。
  2. 根據(jù)限流大小,設(shè)置按照一定的速率往桶里添加令牌。
  3. 設(shè)置桶最大可容納值,當桶滿時新添加的令牌就被丟棄或者拒絕。
  4. 請求達到后首先要獲取令牌桶中的令牌,拿著令牌才可以進行其他的業(yè)務(wù)邏輯,處理完業(yè)務(wù)邏輯之后,將令牌直接刪除。
  5. 如果用戶無法獲得令牌可以選擇一直阻塞等待,也可以選擇設(shè)置好timeout機制。
  6. 令牌桶有最低限額,當桶中的令牌達到最低限額的時候,請求處理完之后將不會刪除令牌,以此保證足夠的限流。

工程中一般用令牌桶算法為多,一般用Google的Guava 中 RateLimiter 即可。

//創(chuàng)建令牌桶實例
private RateLimiter rateLimiter = RateLimiter.create(20);
// 阻塞式獲得令牌才繼續(xù)往下執(zhí)行
rateLimiter.acquire();
// 就等3秒看是否可以獲得令牌,返回Boolean值。
rateLimiter.tryAcquire(3, TimeUnit.SECONDS)

6 第5版- 細節(jié)優(yōu)化

有了樂觀鎖跟限流,接下來再思考寫細節(jié)問題。

  1. 秒殺要有時間范圍限制的,不能再任意時刻都可以接受秒殺請求,要實行限時搶購。
  2. 如果有懂IT人員通過抓包獲取了秒殺接口地址,在秒殺開始時,不通過按鈕,直接通過腳本秒殺咋辦?要實行秒殺接口隱藏。
  3. 每個用戶單位時間內(nèi)訪問次數(shù)要做頻率限制。

6.1 限時搶購

很簡單,將秒殺商品放入Redis并設(shè)置超時,比如我們以kill + 商品id作為key,以商品id作為value,設(shè)置180秒超時。

127.0.0.1:6379> set kill1 1 EX 180
OK

加入時間校驗:

public Integer createOrder(Integer id) {
//redis校驗搶購時間
if(!
stringRedisTemplate.hasKey("kill" + id)){
throw new RuntimeException("秒殺超時,活動已經(jīng)結(jié)束啦!!!");
}
//校驗庫存
Stock stock = checkStock(id);
//扣庫存
updateSale(stock);
//下訂單
return createOrder(stock);
}

6.2 秒殺接口隱藏

硬核講解:秒殺設(shè)計

接口隱藏

  1. 用戶秒殺前先通過getMd5方法獲得一個請求秒殺URL的MD5值。
  2. 請求getMd5算法,Key = 商品id + 用戶id,value = 商品id + 用戶id + 鹽 。將KV存入redis并且設(shè)置過期時間,最終返回value作為md5值。
  3. 用戶請求秒殺URL的時候需攜帶MD5值,然后Service層會根據(jù)商品id + 用戶id從redis中獲取下對應(yīng)的value,看這個value跟MD5值是否一致,絕對下一步操作。

// 根據(jù)商品id 跟 用戶id生成個md5。
@Override
public String getMd5(Integer id, Integer userid) {
//檢驗用戶的合法性
User user = userDAO.findById(userid);
if(user==null)throw new RuntimeException("用戶信息不存在!");
//檢驗商品的合法行
Stock stock = stockDAO.checkStock(id);
if(stock==null) throw new RuntimeException("商品信息不合法!");
String hashKey = "KEY_" + userid + "_" + id;
//生成md5,此處的 !AW# 是一個鹽,可以跟找個Random隨機生成。
String key =
DigestUtils.md5DigestAsHex((userid + id + "!AW#").getBytes());

stringRedisTemplate.opsForValue().set(hashKey, key, 3600, TimeUnit.SECONDS);
return key;
}

此時如果用戶直接請求秒殺接口就會被限制了,但如果黑客技術(shù)升級,將請求MD5跟請求秒殺接口寫到一起,還是無法防止被薅羊毛!咋辦呢?再限制下用戶訪問頻率

6.3 訪問頻率限制

  1. 通過前面請求后根據(jù)用戶id生成個redis中的key,value為訪問次數(shù),默認為0,并且設(shè)置好該KV的過期時間。
  2. 用戶在驗證是否通過秒殺隱藏接口驗證前,先看下他的單位時間內(nèi)訪問次數(shù)是多少,如果超過閾值則直接拒絕,沒超過再進行隱藏接口的驗證。
  3. 這里只是舉例為用戶訪問次數(shù)限制,IP訪問次數(shù)限制類似。
硬核講解:秒殺設(shè)計

訪問頻率限制

7 第6版-眾多細節(jié)優(yōu)化

  1. CDN加速:為何京東物流快,因為人在全國各地配置了多個倉庫。同理,我們可以將前端的一些靜態(tài)東西配置在全國各個不同的地方,用戶請求時,直接請求距離自己最近的前端資源即可。
  2. 前端按鈕灰色化:如果參與過秒殺活動會發(fā)現(xiàn),沒到秒殺時間時秒殺按鈕是灰色狀態(tài)的,只有時間到了才是可點擊狀態(tài)。并且秒殺開始咯也不是一直可以點的,可能只允許1秒內(nèi)點10次那種的。
  3. Nginx負載均衡:一個Tomcat的QPS一般在200~1000左右,如果淘寶或京東性質(zhì)的秒殺,就需要搞個Nginx負載均衡來支持幾萬級別的并發(fā)了。
  4. 信息存儲Redis化:單獨的MySQL是無法支撐上萬的QPS的,既然Redis號稱可支持10W級的QPS,我們把數(shù)據(jù)信息存到Redis中就好咯嘛!有人可能會說MySQL有樂觀鎖跟事務(wù)性啊,Redis不是沒有事務(wù)性么,其實我們可以通過 Lua 腳本來實現(xiàn)并發(fā)情況下Redis的事務(wù)性操作。
  5. 消息中間件-流量削峰:秒殺成功后,如果秒殺的成功量過大,全部訂單直接寫入MySQL也是不太恰當?shù)模梢园衙霘⒊晒Φ挠脩粜畔懭胂⒅虚g件。比如RabbitMQ、Kafka,給用戶返回搶購成功信息,然后專門代碼消費中間件信息(生成訂單,數(shù)據(jù)持久化),因為是異步消費,為防止用戶秒殺成功后無法看到訂單信息,在訂單生成前給用戶提示訂單提交排隊中,啥時候訂單異步消費成功了再告知用戶成功。
  6. 輔助手段:秒殺前做個預(yù)演練是必須的吧,系統(tǒng)上線后QPS監(jiān)控、CPU監(jiān)控、IO監(jiān)控、緩存監(jiān)控也是必須要搞的。同時一旦服務(wù)真的扛不住了熔斷跟限流也要考慮進去。
  7. 短URL:有時你別人發(fā)給你個超短的URL你打開后就直接跳轉(zhuǎn)為日常看到的購物頁面了,這就涉及到短URL映射了,大致思路就是做個鏈接映射,在此基礎(chǔ)上也可以玩出各種花樣,反正挺有趣的。

分享到:
標簽:設(shè)計 秒殺
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定