天天看點

SpringBoot使用分布式鎖

在項目中經常會遇到并發安全問題,這時我們可以使用鎖來進行線程同步。于是我們可以根據具體的情況使用synchronized 關鍵字來修飾方法或者代碼塊。也可以使用 java 5以後的 Lock 來實作,與 synchronized 關鍵字相比,Lock 的使用更靈活,可以有加鎖逾時時間、公平性等優勢。但是synchronized關鍵字和Lock作用範圍也隻是目前應用,如果分布式部署,那無法保證某個資料在同時間隻有一個線程通路,這時我們可以考慮使用中間層

接下來簡單介紹本人開源的一個分布式鎖的用法

一. 導入依賴

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

二. 啟動類标注注解

/**
 * @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("目前線程:" + Thread.currentThread().getName());
        if (num == 0) {
            System.out.println("賣完了");
            return;
        }
        num--;
        System.out.println("還剩餘:" + num);
    }
}           

參數資訊

參數 描述
key 鎖的key,每個方法最好

唯一

expire 鎖過期時間,機關

,預設

5

timeout 嘗試擷取鎖逾時時間,機關

毫秒

500

retry 重新擷取鎖的間隔時間,機關

毫秒

,預設10

ab壓測執行結果

SpringBoot使用分布式鎖

2、手動控制方式

在需要使用的方法的類中通過

@Resource

注入

a、lock(): 加鎖

擷取鎖成功後會傳回一個用于解鎖的值,失敗傳回null

abstractLock.lock(key, expire, timeout, retry)

參數說明

鎖的key,每個方法保證

唯一

5

毫秒

500

毫秒

b、release():解鎖

釋放鎖成功傳回目前被解鎖的key,失敗傳回null

abstractLock.release(key, value)

加鎖時對應的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("目前線程:" + Thread.currentThread().getName());
            if (num == 0) {
                System.out.println("賣完了");
                return;
            }
            num--;
            System.out.println("還剩餘:" + num);
        } finally {
            this.abstractLock.release("testLock", lock);
        }
    }
}           

ab壓測結果

SpringBoot使用分布式鎖

3. 重寫異常處理

在擷取鎖時往往會出現長時間未擷取鎖,達到我們加鎖設定的逾時時間後會抛出逾時異常,如果要走自己的邏輯,可以重寫異常處理

/**
 * @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. 自定義實作鎖

本項目使用Redis和lua腳本結合使用實作鎖,如若想使用自己的鎖,可以繼承AbstartetLock類
/**
 * @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;
    }
}           

四. 使用建議

該鎖建議使用單獨的單機redis,如果是在redis sentinel叢集中情況就有所不同在redis sentinel叢集中,我們具有多台redis,他們之間有着主從的關系,例如一主二從。我們的set指令對應的資料寫到主庫,然後同步到從庫。當我們申請一個鎖的時候,對應就是一條指令 

setnx mykey myvalue

 ,在redis sentinel叢集中,這條指令先是落到了主庫。假設這時主庫down了,而這條資料還沒來得及同步到從庫,sentinel将從庫中的一台選舉為主庫了。這時,我們的新主庫中并沒有mykey這條資料,若此時另外一個client執行 

setnx mykey hisvalue

 , 也會成功,即也能得到鎖。這就意味着,此時有兩個client獲得了鎖

使用中如果有任何問題,歡迎評論留言,我會及時回複以及更新,源代碼位址:

tools-redis