欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java實(shí)現(xiàn)Redisson看門狗機(jī)制

 更新時(shí)間:2024年09月27日 10:04:30   作者:雪頂貓的鱷  
redission看門狗機(jī)制是解決分布式鎖的續(xù)約問(wèn)題,本文就來(lái)詳細(xì)的介紹一下java實(shí)現(xiàn)Redisson看門狗機(jī)制,具有一定的參考價(jià)值,感興趣的可以了解一下

一、背景

網(wǎng)上redis分布式鎖的工具方法,大都滿足互斥、防止死鎖的特性,有些工具方法會(huì)滿足可重入特性。如果只滿足上述3種特性會(huì)有哪些隱患呢?redis分布式鎖無(wú)法自動(dòng)續(xù)期,比如,一個(gè)鎖設(shè)置了1分鐘超時(shí)釋放,如果拿到這個(gè)鎖的線程在一分鐘內(nèi)沒(méi)有執(zhí)行完畢,那么這個(gè)鎖就會(huì)被其他線程拿到,可能會(huì)導(dǎo)致嚴(yán)重的線上問(wèn)題。

既然存在鎖過(guò)期而任務(wù)未執(zhí)行完畢的情況,那是否有一種可以在任務(wù)未完成時(shí)自動(dòng)續(xù)期的機(jī)制呢,幾年前在redisson中找到了看門狗的自動(dòng)續(xù)期機(jī)制,就是解決這種分布式鎖自動(dòng)續(xù)期的問(wèn)題的。

在這里插入圖片描述

Redisson 鎖的加鎖機(jī)制如上圖所示,線程去獲取鎖,獲取成功則執(zhí)行l(wèi)ua腳本,保存數(shù)據(jù)到redis數(shù)據(jù)庫(kù)。如果獲取失敗: 一直通過(guò)while循環(huán)嘗試獲取鎖(可自定義等待時(shí)間,超時(shí)后返回失敗),獲取成功后,執(zhí)行l(wèi)ua腳本,保存數(shù)據(jù)到redis數(shù)據(jù)庫(kù)。Redisson提供的分布式鎖是支持鎖自動(dòng)續(xù)期的,也就是說(shuō),如果線程仍舊沒(méi)有執(zhí)行完,那么redisson會(huì)自動(dòng)給redis中的目標(biāo)key延長(zhǎng)超時(shí)時(shí)間,這在Redisson中稱之為 Watch Dog 機(jī)制

二、redisson 看門狗使用以及原理

1.redisson配置和初始化

pom.xml

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson-spring-boot-starter</artifactId>
  <version>3.16.4</version>
</dependency>

application.yaml

redis:
  host: xxxxxxx
  password: xxxxxx
  max-active: 8
  max-idle: 500
  max-wait: 1
  min-idle: 0
  port: 6379
  timeout: 1000ms
  database: 0

redisson配置類

@Configuration
public class RedisConfig {
   //最簡(jiǎn)單的redisson初始化配置
    @Bean
    public RedissonClient getRedisson() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
        return Redisson.create(config);
    }
}

2.redisson看門狗使用

使用redisson分布式鎖的目的主要是防止分布式應(yīng)用產(chǎn)生的并發(fā)問(wèn)題,所以一般會(huì)進(jìn)行一下調(diào)整改為AOP形式去進(jìn)行業(yè)務(wù)代碼解耦。這里會(huì)加入自定義注解和AOP。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
    //鎖的名稱
    String lockName();
    //鎖的失效時(shí)間
    long leaseTime() default 3;
    //是否開(kāi)啟看門狗,默認(rèn)開(kāi)啟,開(kāi)啟時(shí)鎖的失效時(shí)間不執(zhí)行。任務(wù)未完成時(shí)會(huì)自動(dòng)續(xù)期鎖時(shí)間
    //使用看門狗,鎖默認(rèn)redis失效時(shí)間未30秒。失效時(shí)間剩余1/3時(shí)進(jìn)行續(xù)期判斷,是否需要續(xù)期
    boolean watchdog() default true;
}
public class RedisLockAspect {
    @Autowired
    private RedissonClient redissonClient;
 
    private static final String REDIS_PREFIX = "redisson_lock:";
 
    @Around("@annotation(redisLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        String lockName = redisLock.lockName();
 
        RLock rLock = redissonClient.getLock(REDIS_PREFIX + lockName);
 
        Object result = null;
        boolean isLock;
        if(redisLock.watchdog()){
            isLock =rLock.tryLock(0, TimeUnit.SECONDS);
        }else {
            isLock =rLock.tryLock(0,redisLock.leaseTime(), TimeUnit.SECONDS);
        }
        if(isLock){
            try {
                //執(zhí)行方法
                result = joinPoint.proceed();
            } finally {
                if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                    rLock.unlock();
                }
            }
        }else {
            log.warn("The lock has been taken:{}",REDIS_PREFIX + lockName);
        }
        return result;
    }
}
@Scheduled(cron = "*/10 * * * * ?")
    //使用注解進(jìn)行加鎖
    @RedisLock(lockName = "npa_lock_test",watchdog = true)
    public void redisLockTest() {
     System.out.println("get lock and perform a task");
        try {
            Thread.sleep(20000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

這里使用定時(shí)任務(wù)進(jìn)行模擬調(diào)用,10秒一次定時(shí)任務(wù)請(qǐng)求,線程執(zhí)行睡眠20秒后完成。下面看一下執(zhí)行結(jié)果。當(dāng)獲取鎖后,第二次定時(shí)任務(wù)執(zhí)行時(shí)。鎖未被釋放。所以失敗,第三次獲取時(shí)所已經(jīng)釋放,所以成功。
如果拿到分布式鎖的節(jié)點(diǎn)宕機(jī),且這個(gè)鎖正好處于鎖住的狀態(tài)時(shí),會(huì)出現(xiàn)鎖死的狀態(tài),為了避免這種情況的發(fā)生,鎖都會(huì)設(shè)置一個(gè)過(guò)期時(shí)間。這樣也存在一個(gè)問(wèn)題,加入一個(gè)線程拿到了鎖設(shè)置了30s超時(shí),在30s后這個(gè)線程還沒(méi)有執(zhí)行完畢,鎖超時(shí)釋放了,就會(huì)導(dǎo)致問(wèn)題,Redisson給出了自己的答案,就是 watch dog 自動(dòng)延期機(jī)制。
Redisson提供了一個(gè)監(jiān)控鎖的看門狗,它的作用是在Redisson實(shí)例被關(guān)閉前,不斷的延長(zhǎng)鎖的有效期,也就是說(shuō),如果一個(gè)拿到鎖的線程一直沒(méi)有完成邏輯,那么看門狗會(huì)幫助線程不斷的延長(zhǎng)鎖超時(shí)時(shí)間,鎖不會(huì)因?yàn)槌瑫r(shí)而被釋放。
默認(rèn)情況下,看門狗的續(xù)期時(shí)間是30s,也可以通過(guò)修改Config.lockWatchdogTimeout來(lái)另行指定。另外Redisson 還提供了可以指定leaseTime參數(shù)的加鎖方法來(lái)指定加鎖的時(shí)間。超過(guò)這個(gè)時(shí)間后鎖便自動(dòng)解開(kāi)了,不會(huì)延長(zhǎng)鎖的有效期。

3.redisson源碼

Redisson的源碼版本基于:3.16.4,同時(shí)需要注意的是:
watchDog 只有在未顯示指定加鎖時(shí)間(leaseTime)時(shí)才會(huì)生效。(這點(diǎn)很重要)
lockWatchdogTimeout設(shè)定的時(shí)間不要太小 ,比如我之前設(shè)置的是 100毫秒,由于網(wǎng)絡(luò)直接導(dǎo)致加鎖完后,watchdog去延期時(shí),這個(gè)key在redis中已經(jīng)被刪除了。
在調(diào)用lock方法時(shí),會(huì)最終調(diào)用到tryAcquireAsync。調(diào)用鏈為:lock()->tryAcquire->tryAcquireAsync,詳細(xì)解釋如下:

使用了RFuture(相關(guān)內(nèi)容涉及Netty異步回調(diào)模式-Future和Promise剖析)去啟動(dòng)異步線程執(zhí)行

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        RFuture<Long> ttlRemainingFuture;
        //如果指定了加鎖時(shí)間,會(huì)直接去加鎖
        if (leaseTime != -1) {
            ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            //沒(méi)有指定加鎖時(shí)間 會(huì)先進(jìn)行加鎖,并且默認(rèn)時(shí)間就是 LockWatchdogTimeout的時(shí)間
            //這個(gè)是異步操作 返回RFuture 類似netty中的future
            ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                    TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        }
 
        //這里也是類似netty Future 的addListener,在future內(nèi)容執(zhí)行完成后執(zhí)行
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            if (e != null) {
                return;
            }
 
            // lock acquired
            if (ttlRemaining == null) {
                // leaseTime不為-1時(shí),不會(huì)自動(dòng)延期
                if (leaseTime != -1) {
                    internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                    //這里是定時(shí)執(zhí)行 當(dāng)前鎖自動(dòng)延期的動(dòng)作,leaseTime為-1時(shí),才會(huì)自動(dòng)延期
                    scheduleExpirationRenewal(threadId);
                }
            }
        });
        return ttlRemainingFuture;
    }

scheduleExpirationRenewal 中會(huì)調(diào)用renewExpiration。 這里我們可以看到是啟用一個(gè)timeout定時(shí),去執(zhí)行延期動(dòng)作,

private void renewExpiration() {
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (ee == null) {
            return;
        }
 
        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                if (ent == null) {
                    return;
                }
                Long threadId = ent.getFirstThreadId();
                if (threadId == null) {
                    return;
                }
 
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.onComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock " + getRawName() + " expiration", e);
                        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                        return;
                    }
 
                    if (res) {
                        //如果 沒(méi)有報(bào)錯(cuò),就再次定時(shí)延期
                        // reschedule itself
                        renewExpiration();
                    } else {
                        cancelExpirationRenewal(null);
                    }
                });
            }
            // 這里我們可以看到定時(shí)任務(wù) 是 lockWatchdogTimeout 的1/3時(shí)間去執(zhí)行 renewExpirationAsync
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
 
        ee.setTimeout(task);
    }
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return this.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;", Collections.singletonList(this.getRawName()), this.internalLockLeaseTime, this.getLockName(threadId));
    }

結(jié)論

  • watch dog 在當(dāng)前節(jié)點(diǎn)存活時(shí)每10s給分布式鎖的key續(xù)期 30s;
  • watch dog 機(jī)制啟動(dòng),且代碼中沒(méi)有釋放鎖操作時(shí),watch dog 會(huì)不斷的給鎖續(xù)期;
  • 如果程序釋放鎖操作時(shí)因?yàn)楫惓](méi)有被執(zhí)行,那么鎖無(wú)法被釋放,所以釋放鎖操作一定要放到 finally {} 中;
  • 要使 watchLog機(jī)制生效 ,lock時(shí) 不要設(shè)置 過(guò)期時(shí)間
  • watchlog的延時(shí)時(shí)間 可以由 lockWatchdogTimeout指定默認(rèn)延時(shí)時(shí)間,但是不要設(shè)置太小。如100
  • watchdog 會(huì)每 lockWatchdogTimeout/3時(shí)間,去延時(shí)。
  • watchdog 通過(guò) 類似netty的 Future功能來(lái)實(shí)現(xiàn)異步延時(shí)
  • watchdog 最終還是通過(guò) lua腳本來(lái)進(jìn)行延時(shí)

到此這篇關(guān)于java實(shí)現(xiàn)Redisson看門狗機(jī)制的文章就介紹到這了,更多相關(guān)java Redisson看門狗內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • java分類樹,我從2s優(yōu)化到0.1s

    java分類樹,我從2s優(yōu)化到0.1s

    這篇文章主要介紹了java分類樹,我從2s優(yōu)化到0.1s的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • SpringBoot整合WebSocket實(shí)現(xiàn)實(shí)時(shí)通信功能

    SpringBoot整合WebSocket實(shí)現(xiàn)實(shí)時(shí)通信功能

    在當(dāng)今互聯(lián)網(wǎng)時(shí)代,實(shí)時(shí)通信已經(jīng)成為了許多應(yīng)用程序的基本需求,而WebSocket作為一種全雙工通信協(xié)議,為開(kāi)發(fā)者提供了一種簡(jiǎn)單、高效的實(shí)時(shí)通信解決方案,本文將介紹如何使用SpringBoot框架來(lái)實(shí)現(xiàn)WebSocket的集成,快速搭建實(shí)時(shí)通信功能,感興趣的朋友可以參考下
    2023-11-11
  • 異常排查記錄amqp協(xié)議鏈接陷阱

    異常排查記錄amqp協(xié)議鏈接陷阱

    這篇文章主要介紹了一次關(guān)于amqp協(xié)議鏈接陷阱-An?unexpected?connection?driver?error?occured的異常排查記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-02-02
  • SpringBoot thymeleaf的使用方法解析

    SpringBoot thymeleaf的使用方法解析

    這篇文章主要介紹了SpringBoot thymeleaf的使用方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Java 十大排序算法之冒泡排序刨析

    Java 十大排序算法之冒泡排序刨析

    冒泡排序是一種簡(jiǎn)單的排序算法,它也是一種穩(wěn)定排序算法。其實(shí)現(xiàn)原理是重復(fù)掃描待排序序列,并比較每一對(duì)相鄰的元素,當(dāng)該對(duì)元素順序不正確時(shí)進(jìn)行交換。一直重復(fù)這個(gè)過(guò)程,直到?jīng)]有任何兩個(gè)相鄰元素可以交換,就表明完成了排序
    2021-11-11
  • java jni調(diào)用c函數(shù)實(shí)例分享(java調(diào)用c函數(shù))

    java jni調(diào)用c函數(shù)實(shí)例分享(java調(diào)用c函數(shù))

    Java代碼中調(diào)用C/C++代碼,當(dāng)然是使用JNI,JNI是Java native interface的簡(jiǎn)寫,可以譯作Java原生接口,下面看實(shí)例吧
    2013-12-12
  • springboot使用redis對(duì)單個(gè)對(duì)象進(jìn)行自動(dòng)緩存更新刪除的實(shí)現(xiàn)

    springboot使用redis對(duì)單個(gè)對(duì)象進(jìn)行自動(dòng)緩存更新刪除的實(shí)現(xiàn)

    本文主要介紹了springboot使用redis對(duì)單個(gè)對(duì)象進(jìn)行自動(dòng)緩存更新刪除的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Spring中基于xml的AOP的詳細(xì)步驟

    Spring中基于xml的AOP的詳細(xì)步驟

    這篇文章主要介紹了Spring中基于xml的AOP的詳細(xì)步驟,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-06-06
  • Java中的多態(tài)用法實(shí)例分析

    Java中的多態(tài)用法實(shí)例分析

    這篇文章主要介紹了Java中的多態(tài)用法,較為詳細(xì)的分析了java中多態(tài)的概念與相關(guān)的實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-04-04
  • java返回集合為null還是空集合及空集合的三種寫法小結(jié)

    java返回集合為null還是空集合及空集合的三種寫法小結(jié)

    這篇文章主要介紹了java返回集合為null還是空集合及空集合的三種寫法小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11

最新評(píng)論