分布式鎖的實(shí)現(xiàn)方式之二:Redis實(shí)現(xiàn)

選用Redis實(shí)現(xiàn)分布式鎖原因:
-Redis有很高的性能
-Redis命令對(duì)此支持較好,實(shí)現(xiàn)起來比較方便

使用命令介紹:
1.SETNX

SETNX key val
當(dāng)且僅當(dāng)key不存在時(shí),set一個(gè)key為val的字符串餐抢,返回1输莺;若key存在靡狞,則什么都不做蔬咬,返回0。

2.expire

expire key timeout
為key設(shè)置一個(gè)超時(shí)時(shí)間斋日,單位為second,超過這個(gè)時(shí)間鎖會(huì)自動(dòng)釋放墓陈,避免死鎖恶守。

3.delete

delete key
刪除key

實(shí)現(xiàn)思想:
1.獲取鎖的時(shí)候,使用setnx加鎖贡必,并使用expire命令為鎖添加一個(gè)超時(shí)時(shí)間兔港,超過該時(shí)間則自動(dòng)釋放鎖,鎖的value值為一個(gè)隨機(jī)生成的UUID仔拟,通過此在釋放鎖的時(shí)候進(jìn)行判斷衫樊。
2.獲取鎖的時(shí)候還設(shè)置一個(gè)獲取的超時(shí)時(shí)間,若超過這個(gè)時(shí)間則放棄獲取鎖利花。
3.釋放鎖的時(shí)候科侈,通過UUID判斷是不是該鎖,若是該鎖炒事,則執(zhí)行delete進(jìn)行鎖釋放兑徘。

分布式鎖的核心代碼如下:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisException;

import java.util.List;
import java.util.UUID;

public class DistributedLock {
    private final JedisPool jedisPool;

    public DistributedLock(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 加鎖
     * @param locaName  鎖的key
     * @param acquireTimeout  獲取超時(shí)時(shí)間
     * @param timeout   鎖的超時(shí)時(shí)間
     * @return 鎖標(biāo)識(shí)
     */
    public String lockWithTimeout(String locaName,
                                  long acquireTimeout, long timeout) {
        Jedis conn = null;
        String retIdentifier = null;
        try {
            // 獲取連接
            conn = jedisPool.getResource();
            // 隨機(jī)生成一個(gè)value
            String identifier = UUID.randomUUID().toString();
            // 鎖名,即key值
            String lockKey = "lock:" + locaName;
            // 超時(shí)時(shí)間羡洛,上鎖后超過此時(shí)間則自動(dòng)釋放鎖
            int lockExpire = (int)(timeout / 1000);

            // 獲取鎖的超時(shí)時(shí)間挂脑,超過這個(gè)時(shí)間則放棄獲取鎖
            long end = System.currentTimeMillis() + acquireTimeout;
            while (System.currentTimeMillis() < end) {
                if (conn.setnx(lockKey, identifier) == 1) {
                    conn.expire(lockKey, lockExpire);
                    // 返回value值,用于釋放鎖時(shí)間確認(rèn)
                    retIdentifier = identifier;
                    return retIdentifier;
                }
                // 返回-1代表key沒有設(shè)置超時(shí)時(shí)間欲侮,為key設(shè)置一個(gè)超時(shí)時(shí)間
                if (conn.ttl(lockKey) == -1) {
                    conn.expire(lockKey, lockExpire);
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        } catch (JedisException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return retIdentifier;
    }

    /**
     * 釋放鎖
     * @param lockName 鎖的key
     * @param identifier    釋放鎖的標(biāo)識(shí)
     * @return
     */
    public boolean releaseLock(String lockName, String identifier) {
        Jedis conn = null;
        String lockKey = "lock:" + lockName;
        boolean retFlag = false;
        try {
            conn = jedisPool.getResource();
            while (true) {
                // 監(jiān)視lock崭闲,準(zhǔn)備開始事務(wù)
                conn.watch(lockKey);
                // 通過前面返回的value值判斷是不是該鎖,若是該鎖威蕉,則刪除刁俭,釋放鎖
                if (identifier.equals(conn.get(lockKey))) {
                    Transaction transaction = conn.multi();
                    transaction.del(lockKey);
                    List<Object> results = transaction.exec();
                    if (results == null) {
                        continue;
                    }
                    retFlag = true;
                }
                conn.unwatch();
                break;
            }
        } catch (JedisException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        return retFlag;
    }
}

測試
下面就用一個(gè)簡單的例子測試剛才實(shí)現(xiàn)的分布式鎖。
例子中使用50個(gè)線程模擬秒殺一個(gè)商品韧涨,使用--運(yùn)算符來實(shí)現(xiàn)商品減少牍戚,從結(jié)果有序性就可以看出是否為加鎖狀態(tài)侮繁。

模擬秒殺服務(wù),在其中配置了jedis線程池如孝,在初始化的時(shí)候傳給分布式鎖宪哩,供其使用。

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class Service {
    private static JedisPool pool = null;

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        // 設(shè)置最大連接數(shù)
        config.setMaxTotal(200);
        // 設(shè)置最大空閑數(shù)
        config.setMaxIdle(8);
        // 設(shè)置最大等待時(shí)間
        config.setMaxWaitMillis(1000 * 100);
        // 在borrow一個(gè)jedis實(shí)例時(shí)第晰,是否需要驗(yàn)證锁孟,若為true,則所有jedis實(shí)例均是可用的
        config.setTestOnBorrow(true);
        pool = new JedisPool(config, "127.0.0.1", 6379, 3000);
    }

    DistributedLock lock = new DistributedLock(pool);

    int n = 500;

    public void seckill() {
        // 返回鎖的value值茁瘦,供釋放鎖時(shí)候進(jìn)行判斷
        String indentifier = lock.lockWithTimeout("resource", 5000, 1000);
        System.out.println(Thread.currentThread().getName() + "獲得了鎖");
        System.out.println(--n);
        lock.releaseLock("resource", indentifier);
    }
}

模擬線程進(jìn)行秒殺服務(wù)

public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.seckill();
    }
}

public class Test {
    public static void main(String[] args) {
        Service service = new Service();
        for (int i = 0; i < 50; i++) {
            ThreadA threadA = new ThreadA(service);
            threadA.start();
        }
    }
}

結(jié)果如下品抽,結(jié)果為有序的。


image.png

若注釋掉使用鎖的部分:

public void seckill() {
    // 返回鎖的value值甜熔,供釋放鎖時(shí)候進(jìn)行判斷
    //String indentifier = lock.lockWithTimeout("resource", 5000, 1000);
    System.out.println(Thread.currentThread().getName() + "獲得了鎖");
    System.out.println(--n);
    //lock.releaseLock("resource", indentifier);
}

從結(jié)果可以看出圆恤,有一些是異步進(jìn)行的。


image.png

總結(jié)
可以使用緩存來代替數(shù)據(jù)庫來實(shí)現(xiàn)分布式鎖腔稀,這個(gè)可以提供更好的性能哑了,同時(shí),很多緩存服務(wù)都是集群部署的烧颖,可以避免單點(diǎn)問題弱左。并且很多緩存服務(wù)都提供了可以用來實(shí)現(xiàn)分布式鎖的方法,比如Tair的put方法炕淮,redis的setnx方法等拆火。并且,這些緩存服務(wù)也都提供了對(duì)數(shù)據(jù)的過期自動(dòng)刪除的支持涂圆,可以直接設(shè)置超時(shí)時(shí)間來控制鎖的釋放们镜。

使用緩存實(shí)現(xiàn)分布式鎖的優(yōu)點(diǎn)
性能好,實(shí)現(xiàn)起來較為方便润歉。

使用緩存實(shí)現(xiàn)分布式鎖的缺點(diǎn)
通過超時(shí)時(shí)間來控制鎖的失效時(shí)間并不是十分的靠譜模狭。
如何設(shè)置的失效時(shí)間太短,方法沒等執(zhí)行完踩衩,鎖就自動(dòng)釋放了嚼鹉,那么就會(huì)產(chǎn)生并發(fā)問題。如果設(shè)置的時(shí)間太長驱富,其他獲取鎖的線程就可能要平白的多等一段時(shí)間锚赤,造成系統(tǒng)吞吐量低,容易導(dǎo)致超時(shí)褐鸥。這個(gè)問題使用數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖同樣存在线脚。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子浑侥,更是在濱河造成了極大的恐慌姊舵,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寓落,死亡現(xiàn)場離奇詭異括丁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)零如,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锄弱,“玉大人考蕾,你說我怎么就攤上這事』嵯埽” “怎么了肖卧?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長掸鹅。 經(jīng)常有香客問我塞帐,道長,這世上最難降的妖魔是什么巍沙? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任葵姥,我火速辦了婚禮,結(jié)果婚禮上句携,老公的妹妹穿的比我還像新娘榔幸。我一直安慰自己,他們只是感情好矮嫉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布削咆。 她就那樣靜靜地躺著,像睡著了一般蠢笋。 火紅的嫁衣襯著肌膚如雪拨齐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天昨寞,我揣著相機(jī)與錄音瞻惋,去河邊找鬼。 笑死援岩,一個(gè)胖子當(dāng)著我的面吹牛熟史,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窄俏,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蹂匹,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了凹蜈?” 一聲冷哼從身側(cè)響起限寞,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤忍啸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后履植,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體计雌,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年玫霎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凿滤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡庶近,死狀恐怖翁脆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鼻种,我是刑警寧澤反番,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站叉钥,受9級(jí)特大地震影響罢缸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜投队,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一枫疆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧敷鸦,春花似錦养铸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谎碍,卻和暖如春鳞滨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蟆淀。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國打工拯啦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人熔任。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓褒链,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疑苔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子甫匹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容