redis分布式鎖實(shí)戰(zhàn)

我們設(shè)置key的時候那婉,將value設(shè)置為一個隨機(jī)值r,并且存在當(dāng)前線程ThreadLocal国觉。當(dāng)釋放鎖吧恃,也就是刪除key的時候,不是直接刪除麻诀,而是先判斷該key對應(yīng)的value是否等于先前存在當(dāng)前線程的隨機(jī)值痕寓,只有當(dāng)前當(dāng)前線程持有鎖,才刪除該key蝇闭,由于每個客戶端產(chǎn)生的隨機(jī)值是不一樣的呻率,這樣一來就不會誤釋放別的客戶端申請的鎖了

public class RedisLock {

    private static Logger logger = LoggerFactory.getLogger(RedisLock.class);

    private static OnecachePlugin oneCache;

    static {
        oneCache = SpringUtil.getBean(OnecachePlugin.class);
    }


    /**
     * 基礎(chǔ)有效時間
     */
    private static final int BASE_VAILD_TIME = 10;

    /**
     * 鎖的基本等待時間10s
     */
    private static final int BASE_WAIT_TIME = 4;

    /**
     * 隨機(jī)數(shù)
     */
    private static Random random = new Random();

    private static ThreadLocal<Set<String>> threadLocal = new ThreadLocal<>();


    private static void currentThreadSleep() throws InterruptedException {
        Thread.sleep((long) (200), random.nextInt(5000));
    }

    public static boolean easyLock(String key) throws Exception {
        return easyLock(key, BASE_WAIT_TIME, BASE_VAILD_TIME);
    }

    //加鎖
    public static boolean easyLock(String key, Integer waitTime, Integer expireTime) throws Exception {
        if (ObjectUtil.hasEmpty(waitTime)) {
            waitTime = BASE_WAIT_TIME;
        }
        if (ObjectUtil.hasEmpty(expireTime)) {
            expireTime = BASE_VAILD_TIME;
        }
        Long signTime = System.nanoTime();
        Long holdTime = TimeUnit.NANOSECONDS.convert(waitTime, TimeUnit.SECONDS);
        String state = UUIDGenerator.getUUID();

        while ((System.nanoTime() - signTime) < holdTime) {
            //從redis獲取key 如果不存在,則將key存入redis
            RString rs = oneCache.getRString(key);
            //原子性加鎖
            if (rs.setnx(state, Time.seconds(expireTime))) {
                logger.info(Thread.currentThread().getId()+" 獲取鎖成功! key = "+key);
                if(ObjectUtil.hasEmpty(threadLocal.get())){
                    threadLocal.set(Sets.newHashSet());
                }
                threadLocal.get().add(state);
                return true;
            } else {
                try {
                    currentThreadSleep();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        throw new Exception( "系統(tǒng)正忙呻引,稍后重試");
    }

    /**
     * redis簡單分布式鎖,業(yè)務(wù)執(zhí)行完畢之后必須try finally 調(diào)用釋放鎖方法easyUnLock
     *
     * @param key 鎖
     */
    public static void easyUnLock(String key) {
        logger.info(Thread.currentThread().getId()+" 準(zhǔn)備解鎖! key = "+key);

        //是否是當(dāng)前線程持有鎖礼仗,以免釋放其他線程加的鎖
        if (isHoldEasyLock(key)) {
            logger.info(Thread.currentThread().getId()+" 開始解鎖! key = "+key);
            try {
                //從redis刪除key
                RString rs = oneCache.getRString(key);
                String state = rs.get();
                rs.delete();
                threadLocal.get().remove(state);
                logger.info(Thread.currentThread().getId()+" 解鎖成功! key = "+key);
            } catch (Exception e) {
                logger.info("RedisLockUtils easyLock解鎖異常 ->" + e);
            }
        }
    }


    /**
     * redis簡單分布式鎖,判斷線程是否持有鎖
     */
    public static boolean isHoldEasyLock(String key) {
        if (ObjectUtil.hasEmpty(threadLocal.get())) {
            return false;
        }
        if (threadLocal.get().contains(oneCache.getRString(key).get())) {
            return true;
        } else {
            return false;
        }
    }

}

實(shí)際上,這樣還是有一點(diǎn)問題逻悠,釋放鎖不是原子性元践,很有可能在查詢完,redis也剛過期童谒,再刪除就把別的線程的鎖釋放了单旁。

image.png

對以上問題解決辦法,就是使用lua腳本饥伊,參考
http://www.reibang.com/p/0e5d592197c1象浑。

至此,還沒有完琅豆,就是過期時間的的問題愉豺,如果高并發(fā)下,某個線程被阻塞茫因,導(dǎo)致超時蚪拦,那么redis過期了,就導(dǎo)致并發(fā)問題了冻押,如果說過期時間設(shè)置太長驰贷,如果服務(wù)重啟了,那么key就釋放不了了翼雀,
因此饱苟,穩(wěn)妥一點(diǎn)的解決辦法,就是鎖續(xù)命狼渊。
就是在加鎖后箱熬,異步起一個線程,每隔幾秒去判斷一下狈邑,redis鎖是否還在或者過期城须,給重新設(shè)置鎖的過期時間,這樣會完美解決上述問題米苹。
具體實(shí)現(xiàn)有現(xiàn)成框架redisson
我們項(xiàng)目并發(fā)量一般糕伐,所以普通加鎖就能滿足

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蘸嘶,隨后出現(xiàn)的幾起案子良瞧,更是在濱河造成了極大的恐慌陪汽,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褥蚯,死亡現(xiàn)場離奇詭異挚冤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)赞庶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門训挡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人歧强,你說我怎么就攤上這事澜薄。” “怎么了摊册?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵肤京,是天一觀的道長。 經(jīng)常有香客問我丧靡,道長蟆沫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任温治,我火速辦了婚禮饭庞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘熬荆。我一直安慰自己舟山,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布卤恳。 她就那樣靜靜地躺著累盗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪突琳。 梳的紋絲不亂的頭發(fā)上若债,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機(jī)與錄音拆融,去河邊找鬼蠢琳。 笑死,一個胖子當(dāng)著我的面吹牛镜豹,可吹牛的內(nèi)容都是我干的傲须。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼趟脂,長吁一口氣:“原來是場噩夢啊……” “哼泰讽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤已卸,失蹤者是張志新(化名)和其女友劉穎佛玄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咬最,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翎嫡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年欠动,在試婚紗的時候發(fā)現(xiàn)自己被綠了永乌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡具伍,死狀恐怖翅雏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情人芽,我是刑警寧澤望几,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站萤厅,受9級特大地震影響橄抹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惕味,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一楼誓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧名挥,春花似錦疟羹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至救湖,卻和暖如春愧杯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鞋既。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工力九, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涛救。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓畏邢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親检吆。 傳聞我的和親對象是個殘疾皇子舒萎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

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