赞
踩
我想我们用到 Redisson 最多的场景一定是分布式锁,一个基础的分布式锁具有三个特性:
互斥:在分布式高并发的条件下,需要保证,同一时刻只有有一个线程获得锁,这是最基本的一点。
防止死锁:在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来的及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。
可重入:我们知道 ReentrantLock 是可重入锁,那它的特点就是同一个线程可以重复拿到同一个资源的锁。
- <dependency>
- <groupId>org.redisson</groupId>
- <artifactId>redisson-spring-boot-starter</artifactId>
- <version>3.14.0</version>
- </dependency>
spring: redis: host: 127.0.0.1 port: 6379 timeout: 10000 password: lettuce: pool: #最大连接数据库连接数,设 0 为没有限制 max-active: 8 #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。 max-wait: 1000 #最大等待连接中的数量,设 0 为没有限制 max-idle: 500 #最小等待连接中的数量,设 0 为没有限制 min-idle: 300 jedis: pool: max-active: 8 max-wait: 1000 max-idle: 500 min-idle: 300
- @Configuration
- public class RedissonConfig {
-
- @Value("${spring.redis.password}")
- private String password;
- @Value("${spring.redis.host}")
- private String host;
- @Value("${spring.redis.port}")
- private String port;
-
- /**
- * 对 Redisson 的使用都是通过 RedissonClient 对象
- * @return
- */
- @Bean(name = "redissonClient", destroyMethod = "shutdown") // 服务停止后调用 shutdown 方法
- public RedissonClient redissonClient() {
- // 1、创建配置
- Config config = new Config();
-
- // 2、集群模式
- // config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
- // 根据 Config 创建出 RedissonClient 示例
- config.useSingleServer()
- .setPassword(StringUtils.isEmpty(password) ? null : password)
- .setAddress(host.contains("://") ? "" : "redis://" + host + ":" + port);
- return Redisson.create(config);
- }
-
- }
- @Autowired
- private RedissonClient redissonClient;
-
- @Test
- public void test() {
- System.out.println(redissonClient); // org.redisson.Redisson@40a8a26f
- }
用锁的一般步骤:
Redisson 提供很多种类型的锁,其中最常用的就是可重入锁(Reentrant Lock)了。
RLock lock = redissonClient.getLock(String lockName);
获取的锁实例实现了 RLock 接口,而该接口拓展了 JUC 包中的 Lock 接口,以及异步锁接口 RLockAsync。
从同步与异步特性来区分,加锁方法可分为同步加锁和异步加锁两类。异步加锁方法的名称一般是在相应的同步加锁方法后加上“Async”后缀。
从阻塞与非阻塞特性来区分,加锁方法可分为阻塞加锁和非阻塞加锁两类。非阻塞加锁方法的名称一般是“try”开头。
下面以比较常用的同步加锁方法来说明加锁的一些细节。
- //创建锁
- RLock helloLock = redissonClient.getLock("hello");
-
- //加锁
- helloLock.lock();
- try {
- log.info("locked");
- Thread.sleep(1000 * 10);
- } finally {
- //释放锁
- helloLock.unlock();
- }
- log.info("finished");
- String key ="product:001";
- RLock lock = redisson.getLock(key);
- try {
- boolean res = lock.tryLock(10,TimeUnit.SECONDS);
- if ( res){
- System.out.println("这里是你的业务代码");
- } else {
- System.out.println("系统繁忙");
- }
- } catch (Exception e){
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
解读:尝试加锁,最多等待 100 秒,上锁以后 10 s自动解锁,没有 watch dog 机制。
void unlock():释放锁。如果当前线程是锁的持有者(即在该锁实例上加锁成功的线程),则会释放成功,否则会抛出异常。
- /**
- * 异步锁
- */
- lock = redissonClient.getLock("erbadagang-lock");
- Future<Boolean> res = null;
- try {
- // lock.lockAsync();
- // lock.lockAsync(100, TimeUnit.SECONDS);
- res = lock.tryLockAsync(3, 100, TimeUnit.SECONDS);
- if (res.get()) {
- System.out.println("这里是你的Async业务代码");
- } else {
- System.out.println("系统繁忙Async");
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (res.get()) {
- lock.unlock();
- }
- }
- log.info("finished");
- String lockName = ...
- RLock lock = redissonClient.getLock(lockName);
- // 阻塞式加锁
- lock.lock();
- try {
- // 操作受锁保护的资源
-
- } finally {
- // 释放锁
- lock.unlock();
- }
- String lockName = ...
- RLock lock = redissonClient.getLock(lockName);
- if (lock.tryLock()) {
- try {
- // 操作受锁保护的资源
- } finally {
- lock.unlock();
- }
- } else {
- // 执行其他业务操作
- }
优秀的分布式锁需要具备以下特性:
Redisson的分布式锁除了实现上述几个特性外,还具有锁的自动续期功能。即当我们加锁而未指定锁的有效时长时,Redisson会按一定的周期,定时检查当前线程是否活跃,若是则自动为锁续期,这一特性称为watchdog(看门狗)机制。
有了这个特性,我们就可以不必为设定锁的有效时间而纠结了(设得太长,则会在客户端崩溃后仍长时间占有锁;设得太短,则可能在业务逻辑执行完成前,锁自动释放),Redisson分布式锁可以在客户端崩坏时自动释放,业务逻辑未执行完时自动续期。
- // 拿锁失败时,会不停的重试
- RLock lock = redissonClient.getLock("guodong");
- // 具有 watch Dog 自动延期机制,默认续 30 s
- lock.lock();
-
- // 尝试拿锁10s后停止重试,返回false,具有 watch dog 自动延期机制默认续30s
- boolean res1 = lock.tryLock(10, TimeUnit.SECONDS);
- // 没有 watch dog,10s后自动释放
- lock.lock(10, TimeUnit.SECONDS);
- // 尝试拿锁100s后停止重试,返回false,没有 watch Dog,10s后自动释放
- boolean res2 = lock.tryLock(100, 10, TimeUnit.SECONDS);
- Thread.sleep(40000L);
- lock.unlock();
如果拿到分布式锁的节点宕机,且这个锁正好处于锁住的状态时,会出现锁死的状态,为了避免这种情况的发生,锁都会设置一个过期时间。这样也存在一个问题,加入一个线程拿到了锁设置了30s超时,在30s后这个线程还没有执行完毕,锁超时释放了,就会导致问题,Redisson 给出了自己的答案,就是 watch dog 自动延期机制。
Redisson 提供了一个监控锁的看门狗,它的作用是在 Redisson实例被关闭前,不断地延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断地延长锁超时时间,锁不会因为超时而被释放。
默认情况下,看门狗的续期时间是30s,也可以通过修改 Config.lockWatchdogTimeout 来另行指定。另外 Redisson 还提供了可以指定 leaseTime 参数的加锁方法来指定加锁的时间。超过这个时间后锁便自动解开了,不会延长锁的有效期。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。