欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MySql實(shí)現(xiàn)分布式鎖詳解

 更新時(shí)間:2024年11月03日 09:42:31   作者:我是小趴菜  
這篇文章主要為大家詳細(xì)介紹了如何使用本地MySql實(shí)現(xiàn)一把分布式鎖,以及Mysql實(shí)現(xiàn)分布式鎖的原理是怎么樣的,有需要的小伙伴可以了解下

MySql實(shí)現(xiàn)分布式鎖有三種方式

1:基于行鎖實(shí)現(xiàn)分布式鎖

實(shí)現(xiàn)原理

首先我們的表lock要提前存好相對應(yīng)的lockName,這時(shí)候多個(gè)客戶端來執(zhí)行

  select lock_name from lock where lock_name = #{lockName} for update

由于第一個(gè)客戶端來執(zhí)行這條sql語句,給這行記錄加了行鎖,在這個(gè)客戶端沒有提交事務(wù)之前,其它客戶端就會(huì)被阻塞住。所以這時(shí)候就只能有一個(gè)客戶端去執(zhí)行我們自己的業(yè)務(wù)了,其它客戶端就只能阻塞等待,那么這個(gè)過程就是加鎖

那么釋放鎖該怎么操作呢?

其實(shí)釋放鎖就很簡單了,也就是將獲取到鎖的這個(gè)客戶端的事務(wù)提交,這樣其它客戶端就可以來獲取到這把行鎖了,所以這時(shí)候就需要我們手動(dòng)的提交事務(wù)了

代碼實(shí)現(xiàn)

首先就是編寫我們的加鎖SQL語句了

@Select("select lock_name from lock where lock_name = #{lockName} for update")
List<String> queryLockNameForUpdate(@Param("lockName") String lockName);

然后我們需要實(shí)現(xiàn)我們的加鎖解鎖

public class MySqlDistributeLock {

    //加鎖的KEY,也就是我們提前存到表lock的值
    private String lockName;

    //手動(dòng)提交事務(wù)需要的事務(wù)管理器,由外部傳入
    private DataSourceTransactionManager dataSourceTransactionManager;

    //自定義編寫的mybatis的mapper文件
    private MySqlLockMapper mySqlLockMapper;

    private TransactionStatus status;

    public MySqlDistributeLock(String lockName,DataSourceTransactionManager dataSourceTransactionManager,MySqlLockMapper mySqlLockMapper) {
        this.lockName = lockName;
        this.dataSourceTransactionManager = dataSourceTransactionManager;
        this.mySqlLockMapper = mySqlLockMapper;
    }


    
    public void lock() {
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        status = dataSourceTransactionManager.getTransaction(transactionDefinition);
        while (true) {
            try{
                mySqlLockMapper.queryLockNameForUpdate(this.lockName);
                //如果加鎖成功,就退出該循環(huán)
                break;
            }catch (Exception e) {
                //說明拋出異常了,讓線程重試
                try {
                    //讓線程休眠一會(huì)
                    Thread.sleep(100);
                } catch (InterruptedException ignored) { }
            }
        }
    }


    public void unLock() {
        //手動(dòng)提交事務(wù),也就是釋放鎖
        dataSourceTransactionManager.commit(this.status);
    }
}

最后看下業(yè)務(wù)方如何使用

@Service
public class LockService {


    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Resource
    private MySqlDistributeLock.MySqlLockMapper mySqlLockMapper;



    public String deductStockMysqlLock(String productId,Integer count) {
        MySqlDistributeLock lock = null;
        try{
            lock = new MySqlDistributeLock(productId,dataSourceTransactionManager,mySqlLockMapper);
            //加鎖
            lock.lock();

            //加鎖成功,開始執(zhí)行我們自己的業(yè)務(wù)邏輯
        }finally {
            if(lock != null) {
                lock.unLock();
            }
        }
        return "success";
    }
}

2:基于唯一索引實(shí)現(xiàn)分布式鎖

實(shí)現(xiàn)原理

首先我們的lock表要給lock_name字段建立一個(gè)唯一索引,這時(shí)候有多個(gè)客戶端來加鎖,本質(zhì)上也就是添加一條記錄,只不過lockName的值都是一樣的

這時(shí)候客戶端A成功的把lockName保存到lock表中了,那么其它客戶端要保存這個(gè)lockName的時(shí)候(也就是執(zhí)行加鎖),由于唯一索引的緣故,就會(huì)插入失敗。也就保證了同一個(gè)時(shí)間只能有一個(gè)客戶端保存成功,也就是加鎖成功了

那么如何釋放鎖呢?

在這個(gè)客戶端業(yè)務(wù)執(zhí)行完之后,手動(dòng)的把這條記錄刪除掉,那么其它客戶端就可以來繼續(xù)加鎖了

代碼實(shí)現(xiàn)

首先我們在mapper文件中編寫 加鎖解鎖 的SQL,這里為什么還要保存?zhèn)€uuid,后續(xù)會(huì)講到(主要是防止鎖被誤刪)

//加鎖語句
@Insert("insert into record_lock (lock_name, uuid) values (#{lockName}, #{uuid})")
Integer insert(@Param("lockName") String lockName, @Param("uuid") String uuid);

//解鎖語句
@Delete("delete from record_lock where lock_name = #{lockName} and uuid = #{uuid}")
Integer delete(@Param("lockName") String lockName,  @Param("uuid") String uuid);

然后我們需要實(shí)現(xiàn)我們的加鎖解鎖

public class MySqlDistributeLock {


    private String lockName;

    //自定義編寫的mybatis的mapper文件
    private MySqlLockMapper mySqlLockMapper;

    private String uuid;


    public MySqlDistributeLock(String lockName,MySqlLockMapper mySqlLockMapper,String uuid) {
        this.lockName = lockName;
        this.mySqlLockMapper = mySqlLockMapper;
        this.uuid = uuid;
    }


    
    public void lock() {
        while (true) {
              try{
                  int result = mySqlLockMapper.insert(this.lockName, this.uuid);
                  if(result > 0) {
                      //代表加鎖成功
                      break;
                  }
              } catch (Exception e) {
              }

            //唯一索引加鎖失敗
            try {
                Thread.sleep(100);
            } catch (InterruptedException interruptedException) {
                throw new RuntimeException();
            }
        }
    }


    public void unLock() {
        mySqlLockMapper.delete(this.lockName,this.uuid);
    }
}

最后看下業(yè)務(wù)方如何使用

@Service
public class LockService {
    
    @Resource
    private MySqlDistributeLock.MySqlLockMapper mySqlLockMapper;



    public String deductStockMysqlLock(String productId,Integer count) {
        MySqlDistributeLock lock = null;
        try{
            lock = new MySqlDistributeLock(productId, mySqlLockMapper,UUID.randomUUID().toString());
            //加鎖
            lock.lock();

            //加鎖成功,開始執(zhí)行我們自己的業(yè)務(wù)邏輯
        }finally {
            if(lock != null) {
                lock.unLock();
            }
        }
        return "success";
    }
}

基于唯一索引實(shí)現(xiàn)的分布式鎖有沒有什么問題呢??

死鎖問題

我們試想一下,如果客戶端A來加鎖成功了,業(yè)務(wù)也執(zhí)行完了,但是這時(shí)候釋放鎖的時(shí)候,也就是執(zhí)行刪除語句的時(shí)候因?yàn)橐恍┰驅(qū)е聞h除失敗了,那么這條記錄一直存在,后續(xù)的線程就沒辦法再獲取到鎖了,這就是所謂的死鎖

所以這時(shí)候我們還需要另外一個(gè)服務(wù)來定時(shí)掃描這些記錄,如果這個(gè)記錄超過了10分鐘,或者20分鐘還沒有被刪除掉,那么大概率是釋放鎖的時(shí)候失敗了,所以需要再次刪除這條記錄

鎖誤刪

為什么鎖會(huì)誤刪呢? 為了防止死鎖,我們會(huì)有一個(gè)單獨(dú)的定時(shí)任務(wù)來掃描,假設(shè)我們判斷一把鎖超過10分鐘就認(rèn)為是釋放鎖失敗了,這時(shí)候定時(shí)任務(wù)就會(huì)把這條記錄刪除掉,但是這時(shí)候就會(huì)有問題了,舉個(gè)例子

客戶端A首先獲取到鎖了,然后開始執(zhí)行業(yè)務(wù),但是因?yàn)闃I(yè)務(wù)比較復(fù)雜,執(zhí)行完業(yè)務(wù)可能需要15分鐘,這時(shí)候到第10分鐘的時(shí)候,定時(shí)任務(wù)就會(huì)把這條記錄給刪除掉了

這時(shí)候因?yàn)橛涗洓]有了,客戶端B來獲取鎖是能成功獲取到的,所以這時(shí)候這把鎖的持有者應(yīng)該是客戶端B的

到第15分鐘的時(shí)候,客戶端A業(yè)務(wù)執(zhí)行完了,就是執(zhí)行釋放鎖的邏輯,那么客戶端A就會(huì)把這條記錄給刪除掉了,也就導(dǎo)致客戶端A把客戶端B的鎖給釋放掉了

所以在開頭的時(shí)候,我們加鎖除了要保存lockName,還要保存一個(gè)uuid,在釋放鎖的時(shí)候,判斷一下uuid是否相等,如果不相等,那就不能刪除這條記錄了,因?yàn)檫@時(shí)候這把鎖已經(jīng)不是當(dāng)前客戶端持有的了

鎖續(xù)期

大家可以想一下,分布式鎖的主要目的就是同一個(gè)時(shí)間點(diǎn)只能有一個(gè)線程去執(zhí)行業(yè)務(wù),但是在上面我們可以看到,即使加了uuid來保證了鎖誤刪,但是在同一個(gè)時(shí)間點(diǎn)可能是有多個(gè)線程在一起執(zhí)行業(yè)務(wù)的,為了避免這種情況,就需要保證一個(gè)客戶端在沒有執(zhí)行完業(yè)務(wù)以前,是不允許其它客戶端執(zhí)行業(yè)務(wù)的

但是定時(shí)任務(wù)判斷的時(shí)間我們沒辦法預(yù)估,可能業(yè)務(wù)需要10分鐘,也有可能是20分鐘,我們沒辦法準(zhǔn)確預(yù)估這個(gè)時(shí)間

所以我們在一個(gè)客戶端加鎖成功之后,可以起一個(gè)額外的線程,時(shí)時(shí)的更新加鎖的時(shí)間,這就類似Redisson的看門狗機(jī)制了,那么如何去做呢??

  • 1:加鎖的時(shí)候,除了保存lockName,uuid,額外保存一個(gè)加鎖時(shí)間lockTime
  • 2:加鎖成功之后,額外開啟一個(gè)線程,每過10秒就更新lockTime為當(dāng)前時(shí)間
  • 3:定時(shí)任務(wù)掃描到lcokTime距離當(dāng)前時(shí)間超過10分鐘或者5分鐘的記錄就刪除掉這條記錄

3:基于樂觀鎖實(shí)現(xiàn)分布式鎖

基于樂觀鎖機(jī)制就是依靠版本機(jī)制來實(shí)現(xiàn),我們一般在數(shù)據(jù)庫會(huì)保存version,或者是時(shí)間戳,至于實(shí)現(xiàn)方式大家可以自己實(shí)現(xiàn)一下,這里就不做贅述了

以上就是MySql實(shí)現(xiàn)分布式鎖詳解的詳細(xì)內(nèi)容,更多關(guān)于MySql分布式鎖的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 全面分析MySQL?ERROR?1045出現(xiàn)的原因及解決

    全面分析MySQL?ERROR?1045出現(xiàn)的原因及解決

    這篇文章主要介紹了全面分析MySQL?ERROR?1045出現(xiàn)的原因及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • Mysql?COUNT()函數(shù)基本用法及應(yīng)用詳解

    Mysql?COUNT()函數(shù)基本用法及應(yīng)用詳解

    這篇文章主要介紹了Mysql?COUNT()函數(shù)基本用法及應(yīng)用的相關(guān)資料,COUNT()函數(shù)是SQL中常用的聚合函數(shù),用于統(tǒng)計(jì)滿足特定條件的記錄數(shù),它可以靈活地應(yīng)用于各種查詢場景,幫助用戶快速獲取所需的數(shù)據(jù)統(tǒng)計(jì)信息,需要的朋友可以參考下
    2024-12-12
  • MySQL中的ALTER EVENT語句的具體使用

    MySQL中的ALTER EVENT語句的具體使用

    EVENT?是一種特殊的數(shù)據(jù)庫對象,它允許你在指定的時(shí)間間隔或特定的時(shí)間自動(dòng)執(zhí)行SQL語句或語句集,本文主要介紹了MySQL中的ALTER EVENT語句的具體使用,感興趣的可以了解一下
    2024-07-07
  • 自學(xué)MySql內(nèi)置函數(shù)知識(shí)點(diǎn)總結(jié)

    自學(xué)MySql內(nèi)置函數(shù)知識(shí)點(diǎn)總結(jié)

    在本篇文章里小編給大家整理的是關(guān)于MySql內(nèi)置函數(shù)的知識(shí)點(diǎn)總結(jié)內(nèi)容,需要的朋友們可以學(xué)習(xí)參考下。
    2020-01-01
  • 詳解MySQL數(shù)據(jù)備份之mysqldump使用方法

    詳解MySQL數(shù)據(jù)備份之mysqldump使用方法

    本篇文章主要介紹了MySQL數(shù)據(jù)備份,詳細(xì)的介紹了mysqldump的各種用法,具有一定的參考價(jià)值,有需要的可以了解一下。
    2016-11-11
  • MySQL生成日期維度表的sql語句

    MySQL生成日期維度表的sql語句

    這篇文章主要介紹了MySQL生成日期維度表的sql語句,通過存儲(chǔ)過程生成,其次是通過遞歸的公用表表達(dá)式生成,需要的朋友可以參考下
    2024-07-07
  • Mysql中的默認(rèn)存儲(chǔ)引擎

    Mysql中的默認(rèn)存儲(chǔ)引擎

    這篇文章主要介紹了Mysql中的默認(rèn)存儲(chǔ)引擎方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • MySQL limit性能分析與優(yōu)化

    MySQL limit性能分析與優(yōu)化

    今天小編就為大家分享一篇關(guān)于MySQL limit性能分析與優(yōu)化,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • MySQL空間函數(shù)ST_Distance_Sphere()的使用方式

    MySQL空間函數(shù)ST_Distance_Sphere()的使用方式

    這篇文章主要介紹了MySQL空間函數(shù)ST_Distance_Sphere()的使用方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • mysql的集群模式 galera-cluster部署詳解

    mysql的集群模式 galera-cluster部署詳解

    這篇文章主要介紹了mysql的集群模式 galera-cluster部署詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-02-02

最新評論