SpringBoot使用分布式鎖

在項(xiàng)目中經(jīng)常會(huì)遇到并發(fā)安全問(wèn)題,這時(shí)我們可以使用鎖來(lái)進(jìn)行線程同步。于是我們可以根據(jù)具體的情況使用synchronized 關(guān)鍵字來(lái)修飾方法或者代碼塊。也可以使用 java 5以后的 Lock 來(lái)實(shí)現(xiàn),與 synchronized 關(guān)鍵字相比,Lock 的使用更靈活,可以有加鎖超時(shí)時(shí)間、公平性等優(yōu)勢(shì)。但是synchronized關(guān)鍵字和Lock作用范圍也只是當(dāng)前應(yīng)用,如果分布式部署,那無(wú)法保證某個(gè)數(shù)據(jù)在同時(shí)間只有一個(gè)線程訪問(wèn),這時(shí)我們可以考慮使用中間層

接下來(lái)簡(jiǎn)單介紹本人開(kāi)源的一個(gè)分布式鎖的用法

一. 導(dǎo)入依賴

<dependency>
    <groupId>cn.gjing</groupId>
    <artifactId>tools-redis</artifactId>
    <version>1.2.0</version>
</dependency>

二. 啟動(dòng)類標(biāo)注注解

/**
 * @author Gjing
 */
@SpringBootApplication
@EnableToolsLock
public class TestRedisApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestRedisApplication.class, args);
    }
}

三. 具體使用

1、注解方式

在需要加鎖的方法上使用@Lock注解即可

/**
 * @author Gjing
 **/
@RestController
public class TestController {
    private static int num = 20;
    
    @GetMapping("/test1")
    @Lock(key = "test1")
    public void test1() {
        System.out.println("當(dāng)前線程:" + Thread.currentThread().getName());
        if (num == 0) {
            System.out.println("賣完了");
            return;
        }
        num--;
        System.out.println("還剩余:" + num);
    }
}

參數(shù)信息

參數(shù) 描述
key 鎖的key,每個(gè)方法最好唯一
expire 鎖過(guò)期時(shí)間,單位,默認(rèn)5
timeout 嘗試獲取鎖超時(shí)時(shí)間,單位毫秒,默認(rèn)500
retry 重新獲取鎖的間隔時(shí)間,單位毫秒,默認(rèn)10

ab壓測(cè)執(zhí)行結(jié)果

lock

2、手動(dòng)控制方式

在需要使用的方法的類中通過(guò)@Resource注入

a、lock(): 加鎖

獲取鎖成功后會(huì)返回一個(gè)用于解鎖的值,失敗返回null

abstractLock.lock(key, expire, timeout, retry)
參數(shù)說(shuō)明

參數(shù) 描述
key 鎖的key,每個(gè)方法保證唯一
expire 鎖過(guò)期時(shí)間,單位,默認(rèn)5
timeout 嘗試獲取鎖超時(shí)時(shí)間,單位毫秒,默認(rèn)500
retry 重新獲取鎖的間隔時(shí)間,單位毫秒,默認(rèn)10
b、release():解鎖

釋放鎖成功返回當(dāng)前被解鎖的key,失敗返回null
abstractLock.release(key, value)
參數(shù)說(shuō)明

參數(shù) 描述
key 加鎖時(shí)對(duì)應(yīng)的key
value 獲取鎖成功后得到的值
使用案例
/**
 * @author Gjing
 **/
@RestController
public class LockController {
    @Resource
    private AbstractLock abstractLock;
    
    private static int num = 10;
    
    @GetMapping("/test2")
    public void test2() {
        String lock = null;
        try {
            lock = this.abstractLock.lock("testLock", 20, 10000, 50);
            System.out.println("當(dāng)前線程:" + Thread.currentThread().getName());
            if (num == 0) {
                System.out.println("賣完了");
                return;
            }
            num--;
            System.out.println("還剩余:" + num);
        } finally {
            this.abstractLock.release("testLock", lock);
        }
    }
}

ab壓測(cè)結(jié)果

lock2

3. 重寫(xiě)異常處理

在獲取鎖時(shí)往往會(huì)出現(xiàn)長(zhǎng)時(shí)間未獲取鎖,達(dá)到我們加鎖設(shè)置的超時(shí)時(shí)間后會(huì)拋出超時(shí)異常,如果要走自己的邏輯,可以重寫(xiě)異常處理

/**
 * @author Gjing
 **/
@Component
public class TimeoutHandler extends AbstractLockTimeoutHandler {
    @Override
    public void getLockTimeoutFallback(String s, int i, int i1, int i2) {
        // TODO: 2019/8/19 自定義邏輯 
    }
}

4. 自定義實(shí)現(xiàn)鎖

本項(xiàng)目使用Redis和lua腳本結(jié)合使用實(shí)現(xiàn)鎖,如若想使用自己的鎖,可以繼承AbstracttLock類

/**
 * @author Gjing
 **/
@Component
public class DemoLock extends AbstractLock {
    @Override
    public String lock(String s, String s1, int i, int i1, int i2) {
        return null;
    }
    @Override
    public String release(String s, String s1) {
        return null;
    }
}

四. 使用建議

該鎖建議使用單獨(dú)的單機(jī)redis,如果是在redis sentinel集群中情況就有所不同在redis sentinel集群中,我們具有多臺(tái)redis,他們之間有著主從的關(guān)系,例如一主二從。我們的set命令對(duì)應(yīng)的數(shù)據(jù)寫(xiě)到主庫(kù),然后同步到從庫(kù)。當(dāng)我們申請(qǐng)一個(gè)鎖的時(shí)候,對(duì)應(yīng)就是一條命令 setnx mykey myvalue ,在redis sentinel集群中,這條命令先是落到了主庫(kù)。假設(shè)這時(shí)主庫(kù)down了,而這條數(shù)據(jù)還沒(méi)來(lái)得及同步到從庫(kù),sentinel將從庫(kù)中的一臺(tái)選舉為主庫(kù)了。這時(shí),我們的新主庫(kù)中并沒(méi)有mykey這條數(shù)據(jù),若此時(shí)另外一個(gè)client執(zhí)行 setnx mykey hisvalue , 也會(huì)成功,即也能得到鎖。這就意味著,此時(shí)有兩個(gè)client獲得了鎖


使用中如果有任何問(wèn)題,歡迎評(píng)論留言,我會(huì)及時(shí)回復(fù)以及更新,源代碼地址:tools-redis

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 我們不生產(chǎn)代碼,我們是代碼的搬運(yùn)工 前不久,阿里大牛蝦總再次拋出了分布式鎖的討論,對(duì)照之前項(xiàng)目中實(shí)現(xiàn)的redis分...
    碼農(nóng)戲碼閱讀 685評(píng)論 0 4
  • ZooKeeper是一個(gè)分布式的,開(kāi)放源碼的分布式應(yīng)用程序協(xié)調(diào)服務(wù),是Google的Chubby一個(gè)開(kāi)源的實(shí)現(xiàn),是...
    Java架構(gòu)007閱讀 2,387評(píng)論 0 4
  • 1.1 資料 ,最好的入門(mén)小冊(cè)子,可以先于一切文檔之前看,免費(fèi)。 作者Antirez的博客,Antirez維護(hù)的R...
    JefferyLcm閱讀 17,302評(píng)論 1 51
  • 五種數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)介 Redis是使用C編寫(xiě)的,內(nèi)部實(shí)現(xiàn)了一個(gè)struct結(jié)構(gòu)體redisObject對(duì)象,通過(guò)結(jié)構(gòu)體...
    彥幀閱讀 7,154評(píng)論 0 14
  • 每周三的晚上,是陪兒子做英語(yǔ)作業(yè)的時(shí)間。昨天元旦后第一天,晚上我忘記了這茬事兒,跟孩子歲月靜好的做了一小時(shí)手工,還...
    沐榕心理閱讀 254評(píng)論 0 1

友情鏈接更多精彩內(nèi)容