千帆競發 —— 分佈式鎖

一個操做要修改用戶的狀態,修改狀態須要先讀出用戶的狀態,在內存裏進行修改,改完了再存回去。若是這樣的操做同時進行了,就會出現併發問題,由於讀取和保存狀態這兩個操做不是原子的。這個時候就要使用到分佈式鎖來限制程序的併發執行。redis

同時操做一個context,存在併發問題

分佈式鎖

通常是使用 setnx(set if not exists) 指令佔坑, 用完再調用 del 指令釋放茅坑。若是邏輯執行到中間出現異常了,可能會致使 del 指令沒有被調用,這樣就會陷入死鎖,鎖永遠得不到釋放。因而咱們在拿到鎖以後,再給鎖加上一個過時時間,好比 5s,這樣即便中間出現異常也能夠保證 5 秒以後鎖會自動釋放。算法

/**
 * @Auther: majx2
 * @Date: 2019-3-21 16:02
 * @Description:
 */
public class DistributedLockTest {

    Jedis jedis = RedisDS.create().getJedis();

    final static String KEY = "KEY";

    @Test
    public void testLock() throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                Assert.assertTrue(exec());
            }
        }).start();
        Thread.sleep(1000);
        Assert.assertFalse(exec());
        Thread.sleep(3000);

    }

    public boolean exec(){
        return new RedLock().trylock(KEY, new LockWrap() {
            @Override
            public boolean invoke() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {}
                return true;
            }
        });
    }


    public class RedLock{

        public boolean trylock(String key,LockWrap wrap){
           
            Long result = jedis.setnx(key, KEY);// 佔坑
            if(result == 1L){  
                jedis.expire(key,5000); // 避免沒有刪除
                boolean invoke = wrap.invoke();
                jedis.del(key);
                return invoke;
            }
            return false;
        }
    }

    public interface LockWrap{

        boolean invoke();
    }
}

可是以上邏輯還有問題。若是在 setnx 和 expire 之間服務器進程忽然掛掉了,多是由於機器掉電或者是被人爲殺掉的,就會致使 expire 得不到執行,也會形成死鎖。這種問題的根源就在於 setnx 和 expire 是兩條指令而不是原子指令。 解決這些問題,可使用開源分佈式組建redission。緩存

超時問題

Redis 的分佈式鎖不能解決超時問題,若是在加鎖和釋放鎖之間的邏輯執行的太長,以致於超出了鎖的超時限制,就會出現問題。由於這時候鎖過時了,第二個線程從新持有了這把鎖,第二個線程就會在第一個線程邏輯執行完之間拿到了鎖;緊接着第一個線程執行完了業務邏輯,就把鎖給釋放了,第三個線程就會在第二個線程邏輯執行完之間拿到了鎖。爲了不這個問題,Redis 分佈式鎖不要用於較長時間的任務。若是真的偶爾出現了,數據出現的小波錯亂可能須要人工介入解決。安全

集羣問題

在 Sentinel 集羣中,主節點掛掉時,從節點會取而代之,客戶端上卻並無明顯感知。原先第一個客戶端在主節點中申請成功了一把鎖,可是這把鎖尚未來得及同步到從節點,主節點忽然掛掉了。而後從節點變成了主節點,這個新的節點內部沒有這個鎖,因此當另外一個客戶端過來請求加鎖時,當即就批准了。這樣就會致使系統中一樣一把鎖被兩個客戶端同時持有,不安全性由此產生。服務器

集羣環境下,分佈式鎖存在問題

若是你很在意高可用性,但願掛了一臺 redis 徹底不受影響,那就應該考慮 redlock算法。不過代價也是有的,須要更多的 redis 實例,性能也降低了。併發

注: Redlock算法,須要提供多個 Redis 實例,這些實例以前相互獨立沒有主從關係。加鎖時,它會向過半節點發送 set(key, value, nx=True, ex=xxx) 指令,只要過半節點 set 成功,那就認爲加鎖成功。釋放鎖時,須要向全部節點發送 del 指令。不過, Redlock 算法還須要考慮出錯重試、時鐘漂移等不少細節問題,同時由於 Redlock 須要向多個節點進行讀寫,意味着相比單實例 Redis 性能會降低一些。分佈式

本文基於《Redis深度歷險:核心原理和應用實踐》一文的JAVA實踐。更多文章請參考:高性能緩存中間件Redis應用實戰(JAVA)ide

相關文章
相關標籤/搜索