日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis分布式锁,看完不懂你打我

發布時間:2025/1/21 数据库 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis分布式锁,看完不懂你打我 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

簡易的redis分布式鎖

加鎖:

set key my_random_value NX PX 30000

這個命令比setnx好,因為可以同時設置過期時間。不設置過期時間,應用掛了,解不了鎖,就一直鎖住了。

解鎖:

if redis.call("get",KEYS[1])==ARGV[1] thenreturn redis.call("del",KEYS[1]) elsereturn 0 end

先比較一下值,相等才刪除。防止其他線程把鎖給解了。

以上方案在一般的場景就夠用了,但還存在一些小問題:

  • 如果設置過期時間3秒,但是業務執行需要4秒怎么辦?
  • 解決方案:參照redisson的看門狗,可以后臺起一個線程去看看業務線程執行完了沒有,如果沒有就延長過期時間。

  • redis是單點的,如果宕機了,那么整個系統就會崩潰。如果是主從結構,那么master宕機了,存儲的key還沒同步到slave,此時slave升級為新的master,客戶端2從新的master上就能拿到同一個資源的鎖。這樣客戶端1和客戶端2都拿到鎖,就不安全了。
  • 解決方案:RedLock算法。簡單說就是N個(通常是5)獨立的redis節點同時執行SETNX,如果大多數成功了,就拿到了鎖。這樣就允許少數節點不可用。

    那我們看看工業級別是怎么實現redis分布式鎖的呢?

    Redission實現的redis分布式鎖

    加鎖流程:

    解鎖流程:

    Redission加鎖使用的是redis的hash結構。

    • key :要鎖的資源名稱
    • filed :uuid+":"+線程id
    • value : 數值型,可以實現可重入鎖

    源碼里面用到了netty里面Promise的一些api,我列出來幫助理解:

    // 異步操作完成且正常終止boolean isSuccess();// 異步操作是否可以取消boolean isCancellable();// 異步操作失敗的原因Throwable cause();// 添加一個監聽者,異步操作完成時回調,類比javascript的回調函數Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);// 阻塞直到異步操作完成Future<V> await() throws InterruptedException;// 同上,但異步操作失敗時拋出異常Future<V> sync() throws InterruptedException;// 非阻塞地返回異步結果,如果尚未完成返回nullV getNow();

    源碼分析:

    加鎖:

    public RLock getLock(String name) {return new RedissonLock(connectionManager.getCommandExecutor(), name);}public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {super(commandExecutor, name);//命令執行器this.commandExecutor = commandExecutor;//uuidthis.id = commandExecutor.getConnectionManager().getId();//超時時間,默認30sthis.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();this.entryName = id + ":" + name;} public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {//獲取線程idlong threadId = Thread.currentThread().getId();//嘗試獲取鎖Long ttl = tryAcquire(leaseTime, unit, threadId);// lock acquired//ttl為空則代表加鎖成功if (ttl == null) {return;}//如果獲取鎖失敗,則訂閱到對應這個鎖的channel,等其他線程釋放鎖時,通知線程去獲取鎖RFuture<RedissonLockEntry> future = subscribe(threadId);commandExecutor.syncSubscription(future);try {while (true) {//再次嘗試獲取鎖ttl = tryAcquire(leaseTime, unit, threadId);// lock acquiredif (ttl == null) {break;}// waiting for message//ttl大于0,則等待ttl時間后繼續嘗試獲取鎖if (ttl >= 0) {getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {getEntry(threadId).getLatch().acquire();}}} finally {//取消對channel的訂閱unsubscribe(future, threadId);} // get(lockAsync(leaseTime, unit));}

    再來看看里面的嘗試獲取鎖的代碼:

    private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {return get(tryAcquireAsync(leaseTime, unit, threadId));}private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {if (leaseTime != -1) {//如果帶有過期時間,則按照普通方式獲取鎖return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);}//先按照30秒的過期時間來執行獲取鎖的方法RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);//異步執行回調監聽ttlRemainingFuture.addListener(new FutureListener<Long>() {@Override//如果還持有這個鎖,則開啟定時任務不斷刷新該鎖的過期時間public void operationComplete(Future<Long> future) throws Exception {//沒有成功執行完成if (!future.isSuccess()) {return;}//非阻塞地返回異步結果,如果尚未完成返回nullLong ttlRemaining = future.getNow();// lock acquiredif (ttlRemaining == null) {scheduleExpirationRenewal(threadId);}}});return ttlRemainingFuture;}

    看門狗邏輯:

    使用的是Netty的Timeout延遲任務做的。

    • 比如鎖過期 30 秒, 每過 1/3 時間也就是 10 秒會檢查鎖是否存在, 存在則更新鎖的超時時間

    加鎖腳本

    <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {internalLockLeaseTime = unit.toMillis(leaseTime);return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,//如果鎖不存在,則通過hset設置它的值,并設置過期時間"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hset', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +//如果鎖已存在,并且鎖的是當前線程,則通過hincrby給數值遞增1,并重新設置過期時間"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +//如果鎖已存在,但并非本線程,則返回過期時間"return redis.call('pttl', KEYS[1]);",Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));}

    解鎖:

    public RFuture<Void> unlockAsync(final long threadId) {final RPromise<Void> result = new RedissonPromise<Void>();//底層解鎖方法RFuture<Boolean> future = unlockInnerAsync(threadId);future.addListener(new FutureListener<Boolean>() {@Overridepublic void operationComplete(Future<Boolean> future) throws Exception {if (!future.isSuccess()) {cancelExpirationRenewal(threadId);result.tryFailure(future.cause());return;}Boolean opStatus = future.getNow();//如果返回空,則證明解鎖的線程和當前鎖不是同一個線程,拋出異常if (opStatus == null) {IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "+ id + " thread-id: " + threadId);result.tryFailure(cause);return;}if (opStatus) {cancelExpirationRenewal(null);}result.trySuccess(null);}});return result;}

    解鎖腳本:

    protected RFuture<Boolean> unlockInnerAsync(long threadId) {return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('publish', KEYS[2], ARGV[1]); " +"return 1; " +"end;" +//如果釋放鎖的線程和已存在鎖的線程不是同一個線程,返回null"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +"return nil;" +"end; " +//通過hincrby遞減1的方式,釋放一次鎖"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +//若剩余次數大于0 ,則刷新過期時間"if (counter > 0) then " +"redis.call('pexpire', KEYS[1], ARGV[2]); " +"return 0; " +"else " +//否則證明鎖已經釋放,刪除key并發布鎖釋放的消息"redis.call('del', KEYS[1]); " +"redis.call('publish', KEYS[2], ARGV[1]); " +"return 1; "+"end; " +"return nil;",Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));}

    書山有路勤為徑,學海無涯苦作舟

    ?

    ?

    總結

    以上是生活随笔為你收集整理的Redis分布式锁,看完不懂你打我的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 成人在线免费网站 | 中文日韩在线观看 | 中文字幕精品视频在线 | 色综合综合色 | 日韩精品1区 | jizz在亚洲 | 探花视频在线版播放免费观看 | 少妇无套内谢免费视频 | 97伊人 | 欧美自拍视频 | 欧美成人第一页 | 亚洲天堂视频在线播放 | 日韩精品乱码久久久久久 | 免费一级黄 | 国产精品欧美一区二区 | 色秀视频网 | 国产成人在线免费 | 日韩一区二区三区在线免费观看 | 午夜精品久久99蜜桃的功能介绍 | 亲切的金子片段 | 久久久久久网址 | 99香蕉视频| 欧美成人a∨高清免费观看 国产精品999视频 | 天天综合网入口 | 日韩成人综合网 | 免费成年人视频在线观看 | 国产av天堂无码一区二区三区 | 国产成人免费视频 | 无套内谢老熟女 | 美女试爆场恐怖电影在线观看 | 亚洲久久一区 | 日韩欧美在线精品 | 日本女人一区二区三区 | 麻豆福利在线观看 | av老司机久久| 欧美女同在线 | 麻豆精品在线观看 | 十大黄台在线观看 | 欧美做受高潮动漫 | 在线免费看av的网站 | 日韩一区二区免费播放 | 日本在线免费播放 | 熟妇人妻无乱码中文字幕真矢织江 | 综合久久久久久 | 致命弯道8在线观看免费高清完整 | 密乳av| 国产成人精品午夜福利Av免费 | 黄网站视频在线观看 | av免费观看在线 | 欧美一级免费观看 | 五月天婷婷久久 | 欧美1314| 日韩一区二 | 亚洲精品乱码久久久久久蜜桃图片 | 性免费视频| 特黄特色特刺激免费播放 | 国产一区二区日韩 | 先锋资源中文字幕 | 日本护士体内she精2xxx | 好男人视频www | 成人免费在线网址 | 国产一区二区三区四区视频 | 中日韩在线 | 欧美日韩午夜 | 久久精品66 | 91免费影片 | 高清不卡一区二区三区 | 91精品国产一区二区在线观看 | 天天噜天天干 | 亚洲av成人精品一区二区三区在线播放 | 无码精品人妻一区二区 | 欧美用舌头去添高潮 | 麻豆视频网页 | 一二三区不卡 | 欧美性俱乐部 | 亚洲欧美综合精品久久成人 | 欧美情爱视频 | 久久久精品电影 | 超碰人人草人人干 | 亚洲一卡二卡在线 | 国产人澡人澡澡澡人碰视频 | 亚洲国产一区在线 | 男人的天堂一区二区 | 91视频在线观看视频 | 国产在线一卡二卡 | 网站黄色在线观看 | sm国产在线调教视频 | 99国产精品久久久久久久成人 | 三级做爰在线观看视频 | 九九亚洲视频 | 欧美在线三级 | 亚洲女人视频 | 亚洲成人自拍视频 | 国产第一页av | 亚洲在线激情 | 五月开心激情网 | 欧美一区二区三区久久综合 | 激情婷婷综合网 | 九九热国产 |