日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

高并发-【抢红包案例】之二:使用悲观锁方式修复红包超发的bug

發布時間:2025/3/21 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高并发-【抢红包案例】之二:使用悲观锁方式修复红包超发的bug 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 概述
  • 超發問題分析
  • 使用數據庫鎖的解決方案
    • 使用悲觀鎖(排它鎖 for update)
    • 使用樂觀鎖(依靠表的設計和代碼)
    • 總結
  • 悲觀鎖(抽象的描述,不真實存在這個鎖)
    • 共享鎖(S鎖)
    • 排他鎖(X鎖)
  • 代碼改造
    • 分析
    • RedPacketDao新增接口方法
    • RedPacket.xml配置映射文件
    • Service層調用新的Dao方法
    • 還原數據,部署測試
    • 統計報告
    • 注意事項
  • 悲觀鎖導致性能下降的原因探究
  • 代碼

概述

高并發–【搶紅包案例分析和代碼實現以及各種方案的優缺點】之一中使用ssm+mysql實現,存在并發超發問題,這里我們使用悲觀鎖的方式來解決這個邏輯錯誤,并驗證數據一致性和性能狀況。


超發問題分析

針對這個案例,用戶搶到紅包后,紅包總量應-1,當多個用戶同時搶紅包,此時多個線程同時讀得庫存為n,相應的邏輯執行后,最后將均執update T_RED_PACKET set stock = stock - 1 where id = #{id} ,很明顯這是錯誤的。


使用數據庫鎖的解決方案

使用悲觀鎖(排它鎖 for update)

  • 線程1在查詢紅包數時使用排他鎖 select id, user_id as userId, amount, send_date as sendDate, total, unit_amount as unitAmount, stock, version, note from T_RED_PACKET where id = #{id} for update
  • 然后進行后續的操作(redPacketDao.decreaseRedPacket 和 userRedPacketDao.grapRedPacket),更新紅包數量,最后提交事務。
  • 線程2在查詢紅包數時,如果線程1還未釋放排他鎖,它將等待
  • 線程3同線程2,依次類推

  • 使用樂觀鎖(依靠表的設計和代碼)

  • 在紅包表添加version版本字段或者timestamp時間戳字段,這里我們使用version
  • 線程1查詢后,執行更新變成了update T_RED_PACKET set stock = stock - 1, version = version + 1 where id = #{id} and version = #{version}
  • 這樣,保證了修改的數據是和它查詢出來的數據是一致的,而其他線程并未進行修改。當然,如果更新失敗,表示在更新操作之前有其他線程已經更新了該紅包數,那么就可以嘗試重入機制來保證更新成功。


    總結

    • 1.悲觀鎖使用了排他鎖,當程序獨占鎖時,其他程序就連查詢都是不允許的,導致吞吐較低。如果在查詢較多的情況下,可使用樂觀鎖。
    • 2.樂觀鎖更新有可能會失敗,甚至是更新幾次都失敗,這是有風險的。所以如果寫入較頻繁,對吞吐要求不高,可使用悲觀鎖。

    悲觀鎖(抽象的描述,不真實存在這個鎖)

    悲觀鎖是在操作數據時,認為此操作會出現數據沖突,所以在進行每次操作時都要通過獲取鎖才能進行對相同數據的操作,所以悲觀鎖需要耗費較多的時間。另悲觀鎖是由數據庫自己實現了的,使用的時候,直接調用數據庫的相關語句即可。

    由悲觀鎖涉及到的另外兩個鎖概念就出來了,它們就是共享鎖與排它鎖。共享鎖和排它鎖是悲觀鎖的不同的實現,它倆都屬于悲觀鎖的范疇。

    數據庫的增刪改操作默認都會加排他鎖,而查詢不會加任何鎖。


    共享鎖(S鎖)

    共享鎖指的就是對于多個不同的事務,對同一個資源共享同一個鎖.

    對某一資源加共享鎖,自身可以讀該資源,其他人也可以讀該資源(也可以再繼續加共享鎖,即 共享鎖可多個共存),但無法修改。要想修改就必須等所有共享鎖都釋放完之后.

    語法:

    select * from table lock in share mode ;

    排他鎖(X鎖)

    排它鎖與共享鎖相對應,就是指對于多個不同的事務,對同一個資源只能有一把鎖。對某一資源加排他鎖,自身可以進行增刪改查,其他人無法進行任何操作。

    與共享鎖類型,在需要執行的語句后面加上for update就可以了

    語法:

    select * from table for update

    代碼改造

    分析

    為了不影響上個版本,我們新加個接口方法和Mapper映射。 因為悲觀鎖是數據庫提供的功能,所以僅僅在Dao層修改Sql,Service層無需新增新的接口,只需要切換下調用的Dao層的方法即可。


    RedPacketDao新增接口方法

    /*** 獲取紅包信息. 悲觀鎖的實現方式* * @param id* --紅包id* @return 紅包具體信息*/public RedPacket getRedPacketForUpdate(Long id);

    RedPacket.xml配置映射文件

    <!-- 查詢紅包具體信息 悲觀鎖的實現方式for update --><select id="getRedPacketForUpdate" parameterType="long"resultType="com.artisan.redpacket.pojo.RedPacket">select id, user_id as userId, amount, send_date as sendDate, total, unit_amount as unitAmount, stock, version, notefrom T_RED_PACKET where id = #{id} for update</select>

    悲觀鎖是一種利用數據庫內部機制提供的鎖的方法,也就是對更新的數據加鎖,這樣在并發期間一旦有一個事務持有了數據庫記錄的鎖,其他的線程將不能再對數據進行更新.

    在 SQL 中加入的 for update 語句,意味著將持有對數據庫記錄的行更新鎖(因為這里使用主鍵查詢,所以只會對行加鎖。如果使用的是非主鍵查詢,要考慮是否對全表加鎖的問題,加鎖后可能引發其他查詢的阻塞〉,那就意味著在高并發的場景下 , 當一條事務持有了這個更新鎖才能往下操作,其他的線程如果要更新這條記錄,都需要等待,這樣就不會出現超發現象引發的數據一致性問題了.


    Service層調用新的Dao方法


    還原數據,部署測試

    將T_RED_PACKET和T_USER_RED_PACKET中的數據還原為初始數據后,啟動應用,通過FireFox 訪問 http://localhost:8080/ssm_redpacket/grap.jsp


    統計報告

    一致性數據統計:

    SELECTa.id,a.amount,a.stock FROMT_RED_PACKET a WHEREa.id = 1 UNION ALLSELECTmax(b.user_id),sum(b.amount),count(*)FROMT_USER_RED_PACKET bWHEREb.red_packet_id = 1;

    這里已經解決了超發的問題,所以結果是正確的,最起碼邏輯是正確的了。除了結果正確,我們還需要考慮性能問題,統計來看下

    性能數據統計:

    SELECT(UNIX_TIMESTAMP(max(a.grab_time)) - UNIX_TIMESTAMP(min(a.grab_time)) ) AS lastTime FROMT_USER_RED_PACKET a;


    注意事項

    不使用悲觀鎖時,2萬個紅包190秒【主機配置很低】搶完(但存在超發現象),現在是275秒。 目前只是對數據庫加了一個鎖,當加的鎖比較多的時候,數據庫的性能還會持續下降,所以要區分不同的業務場景,慎重使用。


    悲觀鎖導致性能下降的原因探究

    對于悲觀鎖來說,當一條線程搶占了資源后,其他的線程將得不到資源,那么這個時, CPU 就會將這些得不到資源的線程掛起,掛起的線程也會消耗 CPU 的資源尤其是在高并發的請求中。

    只能有一個事務占據資源,其他事務被掛起等待持有資源的事務提交并釋放資源。當此時就進入了線程 2 , 線程 3……線程n,開始搶奪資源的步驟了,這里假設線程 3 搶到資源

    一旦線程1 提交了事務,那么鎖就會被釋放,這個時候被掛起的線程就會開始競爭紅包資源,那么競爭到的線程就會被 CPU 恢復到運行狀態,繼續運行。

    于是頻繁掛起,等待持有鎖線程釋放資源, 一旦釋放資源后,就開始搶奪,恢復線程,直至所有紅包資源搶完。

    在高并發的過程中,使用悲觀鎖就會造成大量的線程被掛起和恢復,這將十分消耗資源,這就是為什么使用悲觀鎖性能不佳的原因。

    有些時候,我們也會把悲觀鎖稱為獨占鎖,畢竟只有一個線程可以獨占這個資源,或者稱為阻塞鎖,因為它會造成其他線程的阻塞。無論如何它都會造成并發能力的下降,從而導致 CPU頻繁切換線程上下文,造成性能低下。

    為了克服這個問題,提高并發的能力,避免大量線程因為阻塞導致 CPU 進行大量的上下文切換,目前比較普遍的是樂觀鎖機制。


    代碼

    https://github.com/yangshangwei/ssm_redpacket

    總結

    以上是生活随笔為你收集整理的高并发-【抢红包案例】之二:使用悲观锁方式修复红包超发的bug的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。