分布式锁实现方案 - Lock4j 使用

文章介绍了Lock4j分布式锁工具,它提供简单易用的API,支持Redisson和Zookeeper,并演示了如何在SpringBoot应用中集成和使用,包括配置、自定义和限流功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Lock4j 分布式锁工具

你是不是在使用分布式锁的时候,还在自己用 AOP 封装框架?那么 Lock4j 你可以考虑一下。

Lock4j 是一个分布式锁组件,其提供了多种不同的支持以满足不同性能和环境的需求。

立志打造一个简单但富有内涵的分布式锁组件。

并且支持redission,redisTemplate,zookeeper。可混用,支持扩展。

Giee地址:https://gitee.com/baomidou/lock4j

二、使用方式

这里我以 redisson 作为分布式锁的底层。

添加依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
    <version>2.2.5</version>
</dependency>

然后再配置中增加 redis 的配置:

spring:
  redis:
    timeout: 6000
    password:
    cluster:
      max-redirects:
      nodes:
        - 192.168.40.120:6379
        - 192.168.40.121:6379
        - 192.168.40.122:6379

声明 RedissonClient

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient getRedisson(RedisProperties redisProperties) {
        Config config = new Config();
        String[] nodes = redisProperties.getCluster().getNodes().stream().filter(StringUtils::isNotBlank).map(node -> "redis://" + node).collect(Collectors.toList()).toArray(new String[]{});
        ClusterServersConfig clusterServersConfig = config.useClusterServers().addNodeAddress(nodes);
        if (StringUtils.isNotBlank(redisProperties.getPassword())) {
            clusterServersConfig.setPassword(redisProperties.getPassword());
        }
        clusterServersConfig.setConnectTimeout((int) (redisProperties.getTimeout().getSeconds() * 1000));
        clusterServersConfig.setScanInterval(2000);
        return Redisson.create(config);
    }
}

然后只需在需要分布式锁的地方加 @Lock4j 即可:

@RestController
@RequestMapping("/lock")
public class Lock4jController {

    //不指定,默认获取锁超时3秒,30秒锁过期
    @Lock4j
    @GetMapping("/test")
    public String test() {
        return "success";
    }

    @Lock4j(keys = {"#id", "#name"}, expire = 60000, acquireTimeout = 10000)
    @GetMapping("/test1")
    public String test1(Long id, String name) {
    	Thread.sleep(5000);
        return "success";
    }

}

如果同时两次访问 /test1,可以感觉出第二次没有获得锁的请求等待的时间更长,因为要等待锁的释放:

在这里插入图片描述

获取锁超时时间和锁过期时间,可以通过配置在配置文件中全局生效:

lock4j:
  acquire-timeout: 3000 #默认值3s,可不设置
  expire: 30000 #默认值30s,可不设置
  primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor #默认redisson>redisTemplate>zookeeper,可不设置
  lock-key-prefix: lock4j #锁key前缀, 默认值lock4j,可不设置

acquire-timeout 等待锁的时长,超过这个时间会默认抛出 com.baomidou.lock.exception.LockFailureException 异常。

在这里插入图片描述

也可以自定义异常捕获,需要实现 LockFailureStrategy 接口:

@Slf4j
@Component
public class MyLockFailureStrategy implements LockFailureStrategy {

    @Override
    public void onLockFailure(String key, Method method, Object[] arguments) {
        log.error("key: {} , method: {} ,arguments: {} ", key, method.getName(), Arrays.asList(arguments).toString());
    }
}

如果获取锁超时,则可以看到打印的日志:

在这里插入图片描述

锁的获取逻辑也可以自定义,如果是 Redisson 依赖下,可以继承 AbstractLockExecutor<RLock> 抽象类,例如:

@Slf4j
@Component
public class MyLockExecutor extends AbstractLockExecutor<RLock> {

    @Resource
    RedissonClient redissonClient;
    /**
     * 尝试获取锁
     */
    @Override
    public RLock acquire(String lockKey, String lockValue, long expire, long acquireTimeout) {
        log.info("key: {} 尝试获取锁", lockKey);
        try {
            RLock lockInstance = this.redissonClient.getLock(lockKey);
            boolean locked = lockInstance.tryLock(acquireTimeout, expire, TimeUnit.MILLISECONDS);
            return (RLock)this.obtainLockInstance(locked, lockInstance);
        } catch (InterruptedException var9) {
            return null;
        }
    }

    /**
     * 释放锁
     */
    @Override
    public boolean releaseLock(String key, String value, RLock lockInstance) {
        log.info("key: {} 释放锁", key);
        if (lockInstance.isHeldByCurrentThread()) {
            try {
                return (Boolean)lockInstance.forceUnlockAsync().get();
            } catch (InterruptedException | ExecutionException var5) {
                return false;
            }
        } else {
            return false;
        }
    }
}

然后在使用时指定执行器:

@Lock4j(keys = {"#id", "#name"}, expire = 60000, acquireTimeout = 1000, executor = MyLockExecutor.class)
@GetMapping("/test2")
public String test2(Long id, String name) throws InterruptedException {
    Thread.sleep(5000);
    return "success";
}

请求 test2 接口可以看到打印的日志:

在这里插入图片描述

Key 的生成也可以自定义,只需继承 DefaultLockKeyBuilder 抽象类,例如:

@Slf4j
@Component
public class MyLockKeyBuilder extends DefaultLockKeyBuilder {

    public MyLockKeyBuilder(BeanFactory beanFactory) {
        super(beanFactory);
    }

    @Override
    public String buildKey(MethodInvocation invocation, String[] definitionKeys) {
        String key = super.buildKey(invocation, definitionKeys);
        log.info("生成的key:{} ", key);
        return key;
    }
}

运行后可以观察日志:

在这里插入图片描述

上面都是通过注解的方式,同样也可以手动控制锁的获取和释放,只需要引入 LockTemplate ,例如:

@RestController
@RequestMapping("/lock")
public class Lock4j2Controller {
    @Resource
    private LockTemplate lockTemplate;

    @GetMapping("/test3")
    public String test1(Long id, String name) {
        // 获取锁
        final LockInfo lockInfo = lockTemplate.lock(id + name, 30000L, 5000L, RedissonLockExecutor.class);
        try {
            Thread.sleep(5000);
            return "success";
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }
    }
}

三、通过锁实现限流

在注解中 autoRelease 控制着是否自动在方法执行结束后释放锁,如果为 false 则是在 expire 时间到的时候移除锁,实际是通过 Redis 的过期机制,通过这个机制可以限制某个 key 的访问频次,例如:

@Lock4j(keys = {"#id", "#name"}, expire = 3000, acquireTimeout = 10000, autoRelease = false)
@GetMapping("/test4")
public String test4(Long id, String name) throws InterruptedException {
    return "success";
}

当同一个 idname 第一次访问的时候速度会很快:

在这里插入图片描述

如果频繁访问则会被限流:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小毕超

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值