面試官讓我5分鐘內寫一個搶紅包程序,我和他說了半小時原理!

文章每週持續更新,各位的「三連」是對我最大的確定。能夠微信搜索公衆號「 後端技術學堂 」第一時間閱讀(通常比博客早更新一到兩篇)

今年春節響應國家號召在家宅着抵抗疫情,拜年也改用微信紅包,春節發了不少也搶了不少微信紅包,也算支持了公司業務,微信支付融入生活,搶紅包已是很是日常的事情。程序員

搶紅包這一簡單的動做,每一次都是對紅包服務後臺的一次請求,在春節期間海量的服務請求下,實際上是一個很典型的高併發編程模型。後臺開發程序員都有一個共識:實現一個功能很容易,難的是大量請求下提升服務性能redis

在程序員眼裏,你們搶的不是紅包,是紅包後臺服務的 !這裏的不是咱們平常生活中的鎖,後臺服務編程中鎖的概念:數據庫

實現多個進程或線程互斥的訪問共享資源的一種機制

今天和你們聊聊後臺服務編程中的鎖。

業務模型

爲便於說明,咱們簡化模型,約定搶紅包服務是多線程服務,搶紅包操做包含如下3個步驟:編程

  1. 查詢數據庫內紅包餘額
  2. 扣除搶到的紅包金額
  3. 更新紅包餘額到數據庫

image

假設你發了100塊錢紅包,1000我的1秒內同時來搶(高併發),若是不加鎖是這樣的狀況:後端

  • 第一我的查餘額獲得100元,他在此基礎上扣除搶到的假設2元,準備步驟3更新到數據庫。
  • 在第一我的更新進去以前,此時剩下的人查到的餘額也是100,他們各自扣除搶到的金額,準備按步驟3更新。
  • 致使最後的紅包餘額只記錄了最後一次更新的數據。
  • 很明顯,這就可能出現1000我的都搶到紅包,可是紅包餘額還沒分完的狀況,這就亂了。

怎麼解決這個問題呢? 就用到咱們上面說的加鎖來解決。緩存

有哪些鎖

實現鎖的方式有不少,這裏列舉幾種常見的分類服務器

悲觀鎖

顧名思義就是悲觀的作最壞打算的鎖機制,佔有鎖期間獨佔資源。

悲觀鎖把搶紅包這三個步驟打包成一個總體作成互斥操做,「在我搶了沒更新數據以前你別來查餘額,查到也不許確」。也能夠類比數據庫的事務來理解。微信

事務必須具有如下四個屬性,簡稱ACID 屬性:
原子性(Atomicity):事務是一個完整的操做。事務的各步操做是不可分的(原子的);要麼都執 行,要麼都不執行
一致性(Consistency):當事務完成時,數據必須處於一致狀態
隔離性(Isolation):對數據進行修改的全部併發事務是彼此隔離的,這代表事務必須是獨立的,它不該以任何方式依賴於或影響其餘事務
永久性(Durability):事務完成後,它對數據庫的修改被永久保持,事務日誌可以保持事務的永久性

它悲觀的認爲你每次去搶紅包必然有其餘人也同時在搶,因此你這條線程在搶的時候要獨佔資源,其餘線程須要阻塞掛起等待你搶完才能進來搶,掛起的線程就幹不了其餘事了。數據結構

魯迅先生說過,浪費CPU資源就是浪費生命!

image

而一旦你搶完紅包釋放了鎖,其餘在等待中的線程又要搶佔資源、搶到了還要恢復線程上下文。多線程

CPU不斷的切換線程上下文很是浪費服務器資源,嚴重的會致使不能及時處理後續搶紅包請求,須要想辦法提升效率,因而有了樂觀鎖

樂觀鎖

樂觀鎖是對悲觀鎖的改進,樂觀的認爲加鎖的時候沒有競爭,樂觀鎖不阻塞線程。

一種實現樂觀鎖的方法是數據庫內紅包餘額增長版本號,初始版本號是0,每次搶完紅包版本號加1後再去更新餘額,只有更新的版本號大於數據庫內的版本號才認爲是合法的,予以更新;不然不予更新,線程不阻塞能夠稍後重試,避免頻繁切換線程上下文。

樂觀鎖在搶紅包的步驟一、2不作加鎖判斷,在步驟3的時候才作加鎖判斷版本號。

  • 第一我的搶到版本號是0的紅包,第二我的也搶到版本號是0的紅包
  • 第一我的更新紅包餘額並設置版本號爲1
  • 第二我的更新紅包餘額設置版本號爲1的時候發現餘額版本號已經爲1,更新失敗
  • 第二我的更新失敗後,線程不阻塞,繼續處理其餘搶紅包搶請求,按必定策略重試(超時重試、有限次數重試)第二我的的更新操做
  • 其餘請求以此類推

能夠看到,樂觀鎖在加鎖失敗的時候不掛起線程等待,避免了線程上下文頻繁的切換,提升紅包服務處理性能。

分佈式鎖

上面兩種鎖的形式都是基於對數據庫的更新來作的,在大請求高併發的時候,頻繁的存取數據庫,尤爲是樂觀鎖重試會對數據庫產生很大的衝擊,在實際生產環境要儘可能減小對數據庫的訪問。

Redis 是一個開源(BSD許可)的,內存中的數據結構存儲系統,它能夠用做數據庫、緩存和消息中間件。也能夠用redis實現分佈式鎖,與數據庫交互兩次:第一次獲取紅包餘額,第二次搶完更新紅包狀態。搶紅包和中間過程更新操做都在內存中進行,這可比數據庫操做快了幾個數量級,顯著改善服務併發性能。

redis分佈式鎖:

利用Redis的SET操做在內存中保存key-value鍵值對,加鎖就是獲取這個鍵值對的值,解鎖就是刪除這個鍵值對。

分佈式鎖也不阻塞線程,關於這種分佈式鎖的實現不在這裏展開說明,能夠參考我另外一篇公衆號文章: redis分佈式鎖的3種實現方式分析詳細分析了幾種分佈式鎖特色和利弊。


原創不易,看到這裏動動手指,各位的「三連」是對我持續創做的最大支持,咱們下篇文章再見。

能夠微信搜索公衆號「 後端技術學堂 」回覆「資料」有我給你準備的各類編程學習資料。文章每週持續更新,咱們下期見!
相關文章
相關標籤/搜索