日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
分布式進(jìn)階:Springboot自定義注解優(yōu)雅的實(shí)現(xiàn)Redisson分布式鎖

一、前言

在這個(gè)微服務(wù)多節(jié)點(diǎn)、多線程的環(huán)境中,多個(gè)任務(wù)可能會(huì)同時(shí)競(jìng)爭(zhēng)訪問(wèn)共享資源,從而導(dǎo)致數(shù)據(jù)錯(cuò)誤和不一致。一般的JVM層面的加鎖顯然無(wú)法滿足多個(gè)節(jié)點(diǎn)的情況!分布式鎖就出現(xiàn)了,在redis官網(wǎng)推薦Java使用Redisson去實(shí)現(xiàn)分布式鎖!

創(chuàng)新互聯(lián)-云計(jì)算及IDC服務(wù)提供商,涵蓋公有云、IDC機(jī)房租用、服務(wù)器托管、等保安全、私有云建設(shè)等企業(yè)級(jí)互聯(lián)網(wǎng)基礎(chǔ)服務(wù),咨詢熱線:18982081108

這是基本api調(diào)用,今天我們使用自定義注解來(lái)完成,一勞永逸,減少出錯(cuò)!

二、Redisson簡(jiǎn)介

Redisson是一個(gè)用于Java應(yīng)用程序的開(kāi)源的、基于Redis的分布式和高性能數(shù)據(jù)結(jié)構(gòu)服務(wù)庫(kù)。它提供了一系列的分布式對(duì)象和服務(wù),幫助開(kāi)發(fā)人員更輕松地在分布式環(huán)境中使用Java編程語(yǔ)言。Redisson通過(guò)封裝Redis的功能,使得開(kāi)發(fā)者能夠更方便地利用分布式特性,同時(shí)提供了許多額外的功能和工具。

比setnx簡(jiǎn)單的加鎖機(jī)制,Redisson會(huì)提供更完善的加鎖機(jī)制,比如:

「到期方法沒(méi)有執(zhí)行完成,引入看門(mén)狗機(jī)制自動(dòng)續(xù)期,內(nèi)部使用Lua腳本保證原子性!」

「提供眾多的鎖:」

  • 可重入鎖(Reentrant Lock)
  • 公平鎖(Fair Lock)
  • 聯(lián)鎖(MultiLock)
  • 紅鎖(RedLock)
  • 讀寫(xiě)鎖(ReadWriteLock)

對(duì)于今天的注解形式,只能實(shí)現(xiàn)可重入鎖、公平鎖兩種形式,不過(guò)也滿足大部分業(yè)務(wù)場(chǎng)景!

今天以實(shí)戰(zhàn)為主,這些信息可以去官網(wǎng)看一下詳細(xì)的文檔:

Redisson文檔:https://github.com/redisson/redisson/wiki/1.-Overview。

三、實(shí)戰(zhàn)

1、導(dǎo)入依賴


    org.springframework.boot
    spring-boot-starter-data-redis


    org.redisson
    redisson
    3.12.0

2、配置文件

server:
  port: 8087
spring:
  redis:
    password: 123456
    # 一定要加redis://
    address: redis://127.0.0.1:6379
  datasource:
    #使用阿里的Druid
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?serverTimeznotallow=UTC
    username: root
    password:

3、RedissonClient配置

/**
 * @author wangzhenjun
 * @date 2022/2/9 9:57
 */
@Configuration
public class MyRedissonConfig {

    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.address}")
    private String address;

    /**
     * 所有對(duì)redisson的使用都是通過(guò)RedissonClient來(lái)操作的
     * @return
     */
    @Bean(destroyMethod="shutdown")
    public RedissonClient redissonClient(){
        // 1. 創(chuàng)建配置
        Config config = new Config();
        // 一定要加redis://
        config.useSingleServer().setAddress(address);
        config.useSingleServer().setPassword(password);
        // 2. 根據(jù)config創(chuàng)建出redissonClient實(shí)例
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

4、Redis序列化配置

/**
 * @author wangzhenjun
 * @date 2022/11/17 15:20
 */
@Configuration
public class RedisConfig {

    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer來(lái)序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}

5、自定義注解

我們自定義注解,key支持el表達(dá)式!這里的參數(shù)可以再加一個(gè)key的前綴或者鎖的類型,根據(jù)類型判斷:可重入鎖(RLock getLock(String name))、公平鎖(RLock getFairLock(String name);)這兩種的加鎖!等待鎖超時(shí)時(shí)間、自動(dòng)解鎖時(shí)間、時(shí)間單位這是可選擇的,大家按需,需要看門(mén)狗的有的就不需要,現(xiàn)在是有兩種加鎖機(jī)制,后面也是看大家的選擇!

/**
 * @author wangzhenjun
 * @date 2023/8/30 10:45
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RedisLock {

    /**
     * 分布式鎖的 key,必須:請(qǐng)保持唯一性,支持 spring el表達(dá)式
     *
     */
    String value();

    /**
     * 等待鎖超時(shí)時(shí)間,默認(rèn)30
     *
     */
    long waitTime() default 30;

    /**
     * 自動(dòng)解鎖時(shí)間,自動(dòng)解鎖時(shí)間一定得大于方法執(zhí)行時(shí)間,否則會(huì)導(dǎo)致鎖提前釋放,默認(rèn)100(根據(jù)場(chǎng)景配置)
     * 對(duì)時(shí)間沒(méi)有把握可以使用默認(rèn)的看門(mén)狗會(huì)自動(dòng)續(xù)期
     */
    long leaseTime() default 100;

    /**
     * 時(shí)間單位,默認(rèn)為秒
     *
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

6、定義切片

現(xiàn)在有兩種加鎖方式,我們來(lái)詳細(xì)說(shuō)一下區(qū)別,大家按需選擇:

「lock.tryLock():」

這是一個(gè)非阻塞的方法。如果獲取鎖成功,會(huì)立即返回 true,如果獲取鎖失敗,會(huì)立即返回 false。

當(dāng)然你可以添加等待時(shí)間,超過(guò)這個(gè)時(shí)間仍然沒(méi)有獲取到鎖才會(huì)返回false。tryLock(long time, TimeUnit unit)tryLock(long waitTime, long leaseTime, TimeUnit unit)

如果你想嘗試獲取鎖,但「不希望在獲取失敗時(shí)被阻塞」,可以使用這個(gè)方法。

這個(gè)方法通常用于獲取鎖后執(zhí)行一個(gè)短時(shí)間的任務(wù),避免長(zhǎng)時(shí)間的等待。

「lock.lock():」

這是一個(gè)阻塞的方法,如果獲取鎖失敗,它會(huì)阻塞當(dāng)前線程,直到獲取到鎖或超時(shí)。因此要確保你的鎖的使用不會(huì)導(dǎo)致長(zhǎng)時(shí)間的等待,避免影響系統(tǒng)性能。

也可以添加鎖的過(guò)期時(shí)間,一旦獲取鎖成功,鎖會(huì)在指定的時(shí)間后自動(dòng)釋放。如果在這段時(shí)間內(nèi)任務(wù)未完成,鎖會(huì)自動(dòng)釋放,避免長(zhǎng)時(shí)間的占用。這個(gè)時(shí)間要考慮清除,如果執(zhí)行時(shí)間不可控建議還是不要傳過(guò)期時(shí)間,默認(rèn)會(huì)有看門(mén)狗來(lái)自動(dòng)續(xù)期,防止方法執(zhí)行中鎖被釋放了!

lock(long leaseTime, TimeUnit unit)

如果你希望一定能夠獲取鎖,而且「不希望在獲取失敗時(shí)立即返回」,可以使用這個(gè)方法。

這個(gè)方法通常用于獲取鎖后需要執(zhí)行一個(gè)相對(duì)耗時(shí)的任務(wù),以及希望避免鎖被長(zhǎng)時(shí)間占用而引發(fā)的問(wèn)題。

小編這里建議使用第一種,有鎖正在執(zhí)行,應(yīng)該返回信息給用戶,不應(yīng)該讓用戶長(zhǎng)時(shí)間等待造成不好的影響!

如果是第一個(gè)方法,我們需要判斷返回值,加鎖失敗返回給用戶!異常大家可以專門(mén)定義一個(gè)加鎖失敗異常,小編這里就使用業(yè)務(wù)異常了!

/**
 * 分布式鎖切片
 * @author wangzhenjun
 * @date 2023/8/31 9:28
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
@Component
public class RedisLockAspect {

    private final RedissonClient redissonClient;
    private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();


    /**
     * 環(huán)繞切片
     */
    @Around("@annotation(redisLock)")
    public Object aroundRedisLock(ProceedingJoinPoint point, RedisLock redisLock) throws Throwable {
        log.info("=====請(qǐng)求來(lái)排隊(duì)嘗試獲取鎖=====");
        String value = redisLock.value();
        Assert.hasText(value, "@RedisLock key不能為空!");
        boolean el = redisLock.isEl();
        String lockKey;
        if (el) {
            lockKey = evaluateExpression(value, point);
        } else {
            lockKey = value;
        }
        log.info("========解析后的lockKey :{}", lockKey);
        long waitTime = redisLock.waitTime();
        long leaseTime = redisLock.leaseTime();
        TimeUnit timeUnit = redisLock.timeUnit();

        RLock lock = redissonClient.getLock(lockKey);
//        lock.tryLock(waitTime, leaseTime, timeUnit);
//        lock.lock(leaseTime, timeUnit);
//        lock.lock();
        boolean tryLock = lock.tryLock();
        if (!tryLock) {
            throw new ServiceException("鎖被占用,請(qǐng)稍后提交!");
        }
        try {
            return point.proceed();
        } catch (Throwable throwable) {
            log.error("方法執(zhí)行失敗:", throwable.getMessage());
            throw throwable;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 解析el表達(dá)式
     * @param expression
     * @param point
     * @return
     */
    private String evaluateExpression(String expression, ProceedingJoinPoint point) {
        // 獲取目標(biāo)對(duì)象
        Object target = point.getTarget();
        // 獲取方法參數(shù)
        Object[] args = point.getArgs();
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();

        EvaluationContext context = new MethodBasedEvaluationContext(target, method, args, parameterNameDiscoverer);
        Expression exp = spelExpressionParser.parseExpression(expression);
        return exp.getValue(context, String.class);
    }
}

7、測(cè)試

我們測(cè)試一個(gè)el表達(dá)式的,模擬方法執(zhí)行15s,方便我們測(cè)試!

@SneakyThrows
@RedisLock("#id")
@GetMapping("/listTest")
public Result listTest(@RequestParam("id") Long id){
    System.out.println("=====方法執(zhí)行中");
    Thread.sleep(150000);
    System.out.println("=====方法執(zhí)行完成");
    return Result.success("成功");
}

我們調(diào)用兩次這個(gè)方法,看到控制臺(tái)有報(bào)錯(cuò)信息,返回結(jié)果也是沒(méi)有問(wèn)題的!

四、總結(jié)

在本篇博客中,我們深入探討了如何在Spring Boot應(yīng)用中借助自定義注解來(lái)實(shí)現(xiàn)分布式鎖,為分布式環(huán)境下的并發(fā)問(wèn)題提供了優(yōu)雅且高效的解決方案。通過(guò)自定義注解,我們成功地將分布式鎖的復(fù)雜邏輯進(jìn)行了封裝,使得在業(yè)務(wù)代碼中只需簡(jiǎn)單地使用注解,便能實(shí)現(xiàn)分布式鎖的獲取和釋放。這不僅讓代碼更具可讀性,還提升了開(kāi)發(fā)效率,讓開(kāi)發(fā)人員能夠更專注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。

相信大家已經(jīng)能夠?qū)pring Boot中使用自定義注解實(shí)現(xiàn)分布式鎖有一個(gè)清晰的理解,加鎖的方式大家可以按需選擇!


當(dāng)前名稱:分布式進(jìn)階:Springboot自定義注解優(yōu)雅的實(shí)現(xiàn)Redisson分布式鎖
標(biāo)題來(lái)源:http://www.dlmjj.cn/article/cojsidi.html