'秒殺系統必須考慮的 3 個技術問題'

"
作者:不遠復
來源:http://www.cnblogs.com/dreamworlds/p/5398468.html
  • 併發隊列的選擇

Java的併發包提供了三個常用的併發隊列實現,分別是:ArrayBlockingQueue、ConcurrentLinkedQueue 和 LinkedBlockingQueue 。

ArrayBlockingQueue是初始容量固定的阻塞隊列,我們可以用來作為數據庫模塊成功競拍的隊列,比如有10個商品,那麼我們就設定一個10大小的數組隊列。

ConcurrentLinkedQueue使用的是CAS原語無鎖隊列實現,是一個異步隊列,入隊的速度很快,出隊進行了加鎖,性能稍慢。

LinkedBlockingQueue也是阻塞的隊列,入隊和出隊都用了加鎖,當隊空的時候線程會暫時阻塞。

在請求預處理階段,由於我們的系統入隊需求要遠大於出隊需求,一般不會出現隊空的情況,所以我們可以選擇ConcurrentLinkedQueue來作為我們的請求隊列實現

1. 請求接口的合理設計

一個秒殺或者搶購頁面,通常分為2個部分,一個是靜態的HTML等內容,另一個就是參與秒殺的Web後臺請求接口。

通常靜態HTML等內容,是通過CDN的部署,一般壓力不大,核心瓶頸實際上在後臺請求接口上。這個後端接口,必須能夠支持高併發請求,同時,非常重要的一點,必須儘可能“快”,在最短的時間裡返回用戶的請求結果。為了實現儘可能快這一點,接口的後端存儲使用內存級別的操作會更好一點。仍然直接面向MySQL之類的存儲是不合適的,如果有這種複雜業務的需求,都建議採用異步寫入。

"
作者:不遠復
來源:http://www.cnblogs.com/dreamworlds/p/5398468.html
  • 併發隊列的選擇

Java的併發包提供了三個常用的併發隊列實現,分別是:ArrayBlockingQueue、ConcurrentLinkedQueue 和 LinkedBlockingQueue 。

ArrayBlockingQueue是初始容量固定的阻塞隊列,我們可以用來作為數據庫模塊成功競拍的隊列,比如有10個商品,那麼我們就設定一個10大小的數組隊列。

ConcurrentLinkedQueue使用的是CAS原語無鎖隊列實現,是一個異步隊列,入隊的速度很快,出隊進行了加鎖,性能稍慢。

LinkedBlockingQueue也是阻塞的隊列,入隊和出隊都用了加鎖,當隊空的時候線程會暫時阻塞。

在請求預處理階段,由於我們的系統入隊需求要遠大於出隊需求,一般不會出現隊空的情況,所以我們可以選擇ConcurrentLinkedQueue來作為我們的請求隊列實現

1. 請求接口的合理設計

一個秒殺或者搶購頁面,通常分為2個部分,一個是靜態的HTML等內容,另一個就是參與秒殺的Web後臺請求接口。

通常靜態HTML等內容,是通過CDN的部署,一般壓力不大,核心瓶頸實際上在後臺請求接口上。這個後端接口,必須能夠支持高併發請求,同時,非常重要的一點,必須儘可能“快”,在最短的時間裡返回用戶的請求結果。為了實現儘可能快這一點,接口的後端存儲使用內存級別的操作會更好一點。仍然直接面向MySQL之類的存儲是不合適的,如果有這種複雜業務的需求,都建議採用異步寫入。

秒殺系統必須考慮的 3 個技術問題

當然,也有一些秒殺和搶購採用“滯後反饋”,就是說秒殺當下不知道結果,一段時間後才可以從頁面中看到用戶是否秒殺成功。但是,這種屬於“偷懶”行為,同時給用戶的體驗也不好,容易被用戶認為是“暗箱操作”。

高併發下的數據安全

我們知道在多線程寫入同一個文件的時候,會存現“線程安全”的問題(多個線程同時運行同一段代碼,如果每次運行結果和單線程運行的結果是一樣的,結果和預期相同,就是線程安全的)。如果是MySQL數據庫,可以使用它自帶的鎖機制很好的解決問題,但是,在大規模併發的場景中,是不推薦使用MySQL的。秒殺和搶購的場景中,還有另外一個問題,就是“超發”,如果在這方面控制不慎,會產生髮送過多的情況。我們也曾經聽說過,某些電商搞搶購活動,買家成功拍下後,商家卻不承認訂單有效,拒絕發貨。這裡的問題,也許並不一定是商家奸詐,而是系統技術層面存在超發風險導致的。

1. 超發的原因

假設某個搶購場景中,我們一共只有100個商品,在最後一刻,我們已經消耗了99個商品,僅剩最後一個。這個時候,系統發來多個併發請求,這批請求讀取到的商品餘量都是99個,然後都通過了這一個餘量判斷,最終導致超發。(同文章前面說的場景)

"
作者:不遠復
來源:http://www.cnblogs.com/dreamworlds/p/5398468.html
  • 併發隊列的選擇

Java的併發包提供了三個常用的併發隊列實現,分別是:ArrayBlockingQueue、ConcurrentLinkedQueue 和 LinkedBlockingQueue 。

ArrayBlockingQueue是初始容量固定的阻塞隊列,我們可以用來作為數據庫模塊成功競拍的隊列,比如有10個商品,那麼我們就設定一個10大小的數組隊列。

ConcurrentLinkedQueue使用的是CAS原語無鎖隊列實現,是一個異步隊列,入隊的速度很快,出隊進行了加鎖,性能稍慢。

LinkedBlockingQueue也是阻塞的隊列,入隊和出隊都用了加鎖,當隊空的時候線程會暫時阻塞。

在請求預處理階段,由於我們的系統入隊需求要遠大於出隊需求,一般不會出現隊空的情況,所以我們可以選擇ConcurrentLinkedQueue來作為我們的請求隊列實現

1. 請求接口的合理設計

一個秒殺或者搶購頁面,通常分為2個部分,一個是靜態的HTML等內容,另一個就是參與秒殺的Web後臺請求接口。

通常靜態HTML等內容,是通過CDN的部署,一般壓力不大,核心瓶頸實際上在後臺請求接口上。這個後端接口,必須能夠支持高併發請求,同時,非常重要的一點,必須儘可能“快”,在最短的時間裡返回用戶的請求結果。為了實現儘可能快這一點,接口的後端存儲使用內存級別的操作會更好一點。仍然直接面向MySQL之類的存儲是不合適的,如果有這種複雜業務的需求,都建議採用異步寫入。

秒殺系統必須考慮的 3 個技術問題

當然,也有一些秒殺和搶購採用“滯後反饋”,就是說秒殺當下不知道結果,一段時間後才可以從頁面中看到用戶是否秒殺成功。但是,這種屬於“偷懶”行為,同時給用戶的體驗也不好,容易被用戶認為是“暗箱操作”。

高併發下的數據安全

我們知道在多線程寫入同一個文件的時候,會存現“線程安全”的問題(多個線程同時運行同一段代碼,如果每次運行結果和單線程運行的結果是一樣的,結果和預期相同,就是線程安全的)。如果是MySQL數據庫,可以使用它自帶的鎖機制很好的解決問題,但是,在大規模併發的場景中,是不推薦使用MySQL的。秒殺和搶購的場景中,還有另外一個問題,就是“超發”,如果在這方面控制不慎,會產生髮送過多的情況。我們也曾經聽說過,某些電商搞搶購活動,買家成功拍下後,商家卻不承認訂單有效,拒絕發貨。這裡的問題,也許並不一定是商家奸詐,而是系統技術層面存在超發風險導致的。

1. 超發的原因

假設某個搶購場景中,我們一共只有100個商品,在最後一刻,我們已經消耗了99個商品,僅剩最後一個。這個時候,系統發來多個併發請求,這批請求讀取到的商品餘量都是99個,然後都通過了這一個餘量判斷,最終導致超發。(同文章前面說的場景)

秒殺系統必須考慮的 3 個技術問題

在上面的這個圖中,就導致了併發用戶B也“搶購成功”,多讓一個人獲得了商品。這種場景,在高併發的情況下非常容易出現。

2. 悲觀鎖思路

解決線程安全的思路很多,可以從“悲觀鎖”的方向開始討論。

悲觀鎖,也就是在修改數據的時候,採用鎖定狀態,排斥外部請求的修改。遇到加鎖的狀態,就必須等待。

"
作者:不遠復
來源:http://www.cnblogs.com/dreamworlds/p/5398468.html
  • 併發隊列的選擇

Java的併發包提供了三個常用的併發隊列實現,分別是:ArrayBlockingQueue、ConcurrentLinkedQueue 和 LinkedBlockingQueue 。

ArrayBlockingQueue是初始容量固定的阻塞隊列,我們可以用來作為數據庫模塊成功競拍的隊列,比如有10個商品,那麼我們就設定一個10大小的數組隊列。

ConcurrentLinkedQueue使用的是CAS原語無鎖隊列實現,是一個異步隊列,入隊的速度很快,出隊進行了加鎖,性能稍慢。

LinkedBlockingQueue也是阻塞的隊列,入隊和出隊都用了加鎖,當隊空的時候線程會暫時阻塞。

在請求預處理階段,由於我們的系統入隊需求要遠大於出隊需求,一般不會出現隊空的情況,所以我們可以選擇ConcurrentLinkedQueue來作為我們的請求隊列實現

1. 請求接口的合理設計

一個秒殺或者搶購頁面,通常分為2個部分,一個是靜態的HTML等內容,另一個就是參與秒殺的Web後臺請求接口。

通常靜態HTML等內容,是通過CDN的部署,一般壓力不大,核心瓶頸實際上在後臺請求接口上。這個後端接口,必須能夠支持高併發請求,同時,非常重要的一點,必須儘可能“快”,在最短的時間裡返回用戶的請求結果。為了實現儘可能快這一點,接口的後端存儲使用內存級別的操作會更好一點。仍然直接面向MySQL之類的存儲是不合適的,如果有這種複雜業務的需求,都建議採用異步寫入。

秒殺系統必須考慮的 3 個技術問題

當然,也有一些秒殺和搶購採用“滯後反饋”,就是說秒殺當下不知道結果,一段時間後才可以從頁面中看到用戶是否秒殺成功。但是,這種屬於“偷懶”行為,同時給用戶的體驗也不好,容易被用戶認為是“暗箱操作”。

高併發下的數據安全

我們知道在多線程寫入同一個文件的時候,會存現“線程安全”的問題(多個線程同時運行同一段代碼,如果每次運行結果和單線程運行的結果是一樣的,結果和預期相同,就是線程安全的)。如果是MySQL數據庫,可以使用它自帶的鎖機制很好的解決問題,但是,在大規模併發的場景中,是不推薦使用MySQL的。秒殺和搶購的場景中,還有另外一個問題,就是“超發”,如果在這方面控制不慎,會產生髮送過多的情況。我們也曾經聽說過,某些電商搞搶購活動,買家成功拍下後,商家卻不承認訂單有效,拒絕發貨。這裡的問題,也許並不一定是商家奸詐,而是系統技術層面存在超發風險導致的。

1. 超發的原因

假設某個搶購場景中,我們一共只有100個商品,在最後一刻,我們已經消耗了99個商品,僅剩最後一個。這個時候,系統發來多個併發請求,這批請求讀取到的商品餘量都是99個,然後都通過了這一個餘量判斷,最終導致超發。(同文章前面說的場景)

秒殺系統必須考慮的 3 個技術問題

在上面的這個圖中,就導致了併發用戶B也“搶購成功”,多讓一個人獲得了商品。這種場景,在高併發的情況下非常容易出現。

2. 悲觀鎖思路

解決線程安全的思路很多,可以從“悲觀鎖”的方向開始討論。

悲觀鎖,也就是在修改數據的時候,採用鎖定狀態,排斥外部請求的修改。遇到加鎖的狀態,就必須等待。

秒殺系統必須考慮的 3 個技術問題

雖然上述的方案的確解決了線程安全的問題,但是,別忘記,我們的場景是“高併發”。也就是說,會很多這樣的修改請求,每個請求都需要等待“鎖”,某些線程可能永遠都沒有機會搶到這個“鎖”,這種請求就會死在那裡。同時,這種請求會很多,瞬間增大系統的平均響應時間,結果是可用連接數被耗盡,系統陷入異常。

3. FIFO隊列思路

那好,那麼我們稍微修改一下上面的場景,我們直接將請求放入隊列中的,採用FIFO(First Input First Output,先進先出),這樣的話,我們就不會導致某些請求永遠獲取不到鎖。看到這裡,是不是有點強行將多線程變成單線程的感覺哈。

"
作者:不遠復
來源:http://www.cnblogs.com/dreamworlds/p/5398468.html
  • 併發隊列的選擇

Java的併發包提供了三個常用的併發隊列實現,分別是:ArrayBlockingQueue、ConcurrentLinkedQueue 和 LinkedBlockingQueue 。

ArrayBlockingQueue是初始容量固定的阻塞隊列,我們可以用來作為數據庫模塊成功競拍的隊列,比如有10個商品,那麼我們就設定一個10大小的數組隊列。

ConcurrentLinkedQueue使用的是CAS原語無鎖隊列實現,是一個異步隊列,入隊的速度很快,出隊進行了加鎖,性能稍慢。

LinkedBlockingQueue也是阻塞的隊列,入隊和出隊都用了加鎖,當隊空的時候線程會暫時阻塞。

在請求預處理階段,由於我們的系統入隊需求要遠大於出隊需求,一般不會出現隊空的情況,所以我們可以選擇ConcurrentLinkedQueue來作為我們的請求隊列實現

1. 請求接口的合理設計

一個秒殺或者搶購頁面,通常分為2個部分,一個是靜態的HTML等內容,另一個就是參與秒殺的Web後臺請求接口。

通常靜態HTML等內容,是通過CDN的部署,一般壓力不大,核心瓶頸實際上在後臺請求接口上。這個後端接口,必須能夠支持高併發請求,同時,非常重要的一點,必須儘可能“快”,在最短的時間裡返回用戶的請求結果。為了實現儘可能快這一點,接口的後端存儲使用內存級別的操作會更好一點。仍然直接面向MySQL之類的存儲是不合適的,如果有這種複雜業務的需求,都建議採用異步寫入。

秒殺系統必須考慮的 3 個技術問題

當然,也有一些秒殺和搶購採用“滯後反饋”,就是說秒殺當下不知道結果,一段時間後才可以從頁面中看到用戶是否秒殺成功。但是,這種屬於“偷懶”行為,同時給用戶的體驗也不好,容易被用戶認為是“暗箱操作”。

高併發下的數據安全

我們知道在多線程寫入同一個文件的時候,會存現“線程安全”的問題(多個線程同時運行同一段代碼,如果每次運行結果和單線程運行的結果是一樣的,結果和預期相同,就是線程安全的)。如果是MySQL數據庫,可以使用它自帶的鎖機制很好的解決問題,但是,在大規模併發的場景中,是不推薦使用MySQL的。秒殺和搶購的場景中,還有另外一個問題,就是“超發”,如果在這方面控制不慎,會產生髮送過多的情況。我們也曾經聽說過,某些電商搞搶購活動,買家成功拍下後,商家卻不承認訂單有效,拒絕發貨。這裡的問題,也許並不一定是商家奸詐,而是系統技術層面存在超發風險導致的。

1. 超發的原因

假設某個搶購場景中,我們一共只有100個商品,在最後一刻,我們已經消耗了99個商品,僅剩最後一個。這個時候,系統發來多個併發請求,這批請求讀取到的商品餘量都是99個,然後都通過了這一個餘量判斷,最終導致超發。(同文章前面說的場景)

秒殺系統必須考慮的 3 個技術問題

在上面的這個圖中,就導致了併發用戶B也“搶購成功”,多讓一個人獲得了商品。這種場景,在高併發的情況下非常容易出現。

2. 悲觀鎖思路

解決線程安全的思路很多,可以從“悲觀鎖”的方向開始討論。

悲觀鎖,也就是在修改數據的時候,採用鎖定狀態,排斥外部請求的修改。遇到加鎖的狀態,就必須等待。

秒殺系統必須考慮的 3 個技術問題

雖然上述的方案的確解決了線程安全的問題,但是,別忘記,我們的場景是“高併發”。也就是說,會很多這樣的修改請求,每個請求都需要等待“鎖”,某些線程可能永遠都沒有機會搶到這個“鎖”,這種請求就會死在那裡。同時,這種請求會很多,瞬間增大系統的平均響應時間,結果是可用連接數被耗盡,系統陷入異常。

3. FIFO隊列思路

那好,那麼我們稍微修改一下上面的場景,我們直接將請求放入隊列中的,採用FIFO(First Input First Output,先進先出),這樣的話,我們就不會導致某些請求永遠獲取不到鎖。看到這裡,是不是有點強行將多線程變成單線程的感覺哈。

秒殺系統必須考慮的 3 個技術問題

然後,我們現在解決了鎖的問題,全部請求採用“先進先出”的隊列方式來處理。那麼新的問題來了,高併發的場景下,因為請求很多,很可能一瞬間將隊列內存“撐爆”,然後系統又陷入到了異常狀態。或者設計一個極大的內存隊列,也是一種方案,但是,系統處理完一個隊列內請求的速度根本無法和瘋狂湧入隊列中的數目相比。也就是說,隊列內的請求會越積累越多,最終Web系統平均響應時候還是會大幅下降,系統還是陷入異常。

4. 樂觀鎖思路

這個時候,我們就可以討論一下“樂觀鎖”的思路了。樂觀鎖,是相對於“悲觀鎖”採用更為寬鬆的加鎖機制,大都是採用帶版本號(Version)更新。實現就是,這個數據所有請求都有資格去修改,但會獲得一個該數據的版本號,只有版本號符合的才能更新成功,其他的返回搶購失敗。這樣的話,我們就不需要考慮隊列的問題,不過,它會增大CPU的計算開銷。但是,綜合來說,這是一個比較好的解決方案。

"
作者:不遠復
來源:http://www.cnblogs.com/dreamworlds/p/5398468.html
  • 併發隊列的選擇

Java的併發包提供了三個常用的併發隊列實現,分別是:ArrayBlockingQueue、ConcurrentLinkedQueue 和 LinkedBlockingQueue 。

ArrayBlockingQueue是初始容量固定的阻塞隊列,我們可以用來作為數據庫模塊成功競拍的隊列,比如有10個商品,那麼我們就設定一個10大小的數組隊列。

ConcurrentLinkedQueue使用的是CAS原語無鎖隊列實現,是一個異步隊列,入隊的速度很快,出隊進行了加鎖,性能稍慢。

LinkedBlockingQueue也是阻塞的隊列,入隊和出隊都用了加鎖,當隊空的時候線程會暫時阻塞。

在請求預處理階段,由於我們的系統入隊需求要遠大於出隊需求,一般不會出現隊空的情況,所以我們可以選擇ConcurrentLinkedQueue來作為我們的請求隊列實現

1. 請求接口的合理設計

一個秒殺或者搶購頁面,通常分為2個部分,一個是靜態的HTML等內容,另一個就是參與秒殺的Web後臺請求接口。

通常靜態HTML等內容,是通過CDN的部署,一般壓力不大,核心瓶頸實際上在後臺請求接口上。這個後端接口,必須能夠支持高併發請求,同時,非常重要的一點,必須儘可能“快”,在最短的時間裡返回用戶的請求結果。為了實現儘可能快這一點,接口的後端存儲使用內存級別的操作會更好一點。仍然直接面向MySQL之類的存儲是不合適的,如果有這種複雜業務的需求,都建議採用異步寫入。

秒殺系統必須考慮的 3 個技術問題

當然,也有一些秒殺和搶購採用“滯後反饋”,就是說秒殺當下不知道結果,一段時間後才可以從頁面中看到用戶是否秒殺成功。但是,這種屬於“偷懶”行為,同時給用戶的體驗也不好,容易被用戶認為是“暗箱操作”。

高併發下的數據安全

我們知道在多線程寫入同一個文件的時候,會存現“線程安全”的問題(多個線程同時運行同一段代碼,如果每次運行結果和單線程運行的結果是一樣的,結果和預期相同,就是線程安全的)。如果是MySQL數據庫,可以使用它自帶的鎖機制很好的解決問題,但是,在大規模併發的場景中,是不推薦使用MySQL的。秒殺和搶購的場景中,還有另外一個問題,就是“超發”,如果在這方面控制不慎,會產生髮送過多的情況。我們也曾經聽說過,某些電商搞搶購活動,買家成功拍下後,商家卻不承認訂單有效,拒絕發貨。這裡的問題,也許並不一定是商家奸詐,而是系統技術層面存在超發風險導致的。

1. 超發的原因

假設某個搶購場景中,我們一共只有100個商品,在最後一刻,我們已經消耗了99個商品,僅剩最後一個。這個時候,系統發來多個併發請求,這批請求讀取到的商品餘量都是99個,然後都通過了這一個餘量判斷,最終導致超發。(同文章前面說的場景)

秒殺系統必須考慮的 3 個技術問題

在上面的這個圖中,就導致了併發用戶B也“搶購成功”,多讓一個人獲得了商品。這種場景,在高併發的情況下非常容易出現。

2. 悲觀鎖思路

解決線程安全的思路很多,可以從“悲觀鎖”的方向開始討論。

悲觀鎖,也就是在修改數據的時候,採用鎖定狀態,排斥外部請求的修改。遇到加鎖的狀態,就必須等待。

秒殺系統必須考慮的 3 個技術問題

雖然上述的方案的確解決了線程安全的問題,但是,別忘記,我們的場景是“高併發”。也就是說,會很多這樣的修改請求,每個請求都需要等待“鎖”,某些線程可能永遠都沒有機會搶到這個“鎖”,這種請求就會死在那裡。同時,這種請求會很多,瞬間增大系統的平均響應時間,結果是可用連接數被耗盡,系統陷入異常。

3. FIFO隊列思路

那好,那麼我們稍微修改一下上面的場景,我們直接將請求放入隊列中的,採用FIFO(First Input First Output,先進先出),這樣的話,我們就不會導致某些請求永遠獲取不到鎖。看到這裡,是不是有點強行將多線程變成單線程的感覺哈。

秒殺系統必須考慮的 3 個技術問題

然後,我們現在解決了鎖的問題,全部請求採用“先進先出”的隊列方式來處理。那麼新的問題來了,高併發的場景下,因為請求很多,很可能一瞬間將隊列內存“撐爆”,然後系統又陷入到了異常狀態。或者設計一個極大的內存隊列,也是一種方案,但是,系統處理完一個隊列內請求的速度根本無法和瘋狂湧入隊列中的數目相比。也就是說,隊列內的請求會越積累越多,最終Web系統平均響應時候還是會大幅下降,系統還是陷入異常。

4. 樂觀鎖思路

這個時候,我們就可以討論一下“樂觀鎖”的思路了。樂觀鎖,是相對於“悲觀鎖”採用更為寬鬆的加鎖機制,大都是採用帶版本號(Version)更新。實現就是,這個數據所有請求都有資格去修改,但會獲得一個該數據的版本號,只有版本號符合的才能更新成功,其他的返回搶購失敗。這樣的話,我們就不需要考慮隊列的問題,不過,它會增大CPU的計算開銷。但是,綜合來說,這是一個比較好的解決方案。

秒殺系統必須考慮的 3 個技術問題

有很多軟件和服務都“樂觀鎖”功能的支持,例如Redis中的watch就是其中之一。通過這個實現,我們保證了數據的安全。

"

相關推薦

推薦中...