使用redisson做分布式锁

使用redisson

1.pom依赖

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>${version}</version>
</dependency>

2.配置对应redisson

@Configuration
public class RedissonConfig {
 
    @Autowired(required = false)
    RedissonProperties redissonProperties;
 
    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(RedissonClient.class)
    public RedissonClient redissonClient() {
        if (Objects.nonNull(redissonProperties)) {
 
            Config config = new Config();
            String[] nodes = redissonProperties.getSentinelNodes().split(",");
            SentinelServersConfig sentinelServersConfig = config.useSentinelServers()
                    .setMasterName(redissonProperties.getMasterName())
                    .setDatabase(redissonProperties.getDatabase())
                    .setConnectTimeout(redissonProperties.getConnectTimeout())
                    .setPassword(redissonProperties.getPassword());
            for (String node : nodes) {
                sentinelServersConfig = sentinelServersConfig.addSentinelAddress("redis://" + node);
            }
 
            return Redisson.create(config);
        } else {
            return null;
        }
    }
}

3.使用

这里是官方文档

1) 使用普通锁(可重入锁)

注意:可重入锁lock()几次,就要对应的unlock()几次

public static void main(String[] args) {
	RedissonClient redisson = new RedissonClient;
	RLock lock = redisson.getLock("lock");
	lock.lock(2, TimeUnit.SECONDS);
	Thread t = new Thread(() -> {
	    RLock lock1 = redisson.getLock("lock");
	    lock1.lock();
	    lock1.unlock();
	});
 	t.start();
	t.join();
	lock.unlock();
}
2) 使用公平锁

公平锁秉持先到先得原则,先请求获取锁的线程先获取到锁,后来的线程等待。

@Autowired
private RedissonClient redisson;
 
public void demo() {
 
    RLock lock = redisson.getLock("lock");
    lock.lock(2, TimeUnit.SECONDS);
 
    Thread t = new Thread(() -> {
        RLock lock1 = redisson.getLock("lock");
        lock1.lock();
        lock1.unlock();
    });
    t.start();
    t.join();
    lock.unlock();
}
3) 使用读写锁
@Autowired
private RedissonClient redisson;
public void demo() throws InterruptedException {
    final RReadWriteLock lock = redisson.getReadWriteLock("lock");
    lock.writeLock().tryLock(); 
    Thread t = new Thread() {
        public void run() {
             RLock r = lock.readLock();
             r.lock(2, TimeUnit.SECONDS);
             try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r.unlock();
        };
    };
    t.start();
    t.join();
    lock.writeLock().unlock(); 
    t.join(); 
}
4) 使用红锁

使用redlock需要多个redissonClient,多个redissonClient需要多个互相独立的哨兵,我们的项目里目前只有一个哨兵,所以暂不推荐使用红锁

上面是4种比较常用的分布式锁机制,我们针对不同业务作出不同选型。

redisson将各种锁的概念与java的各种锁完美的结合在一起,封装做的非常巧妙,甚至对于CountDownLatch、Semaphore、Atomic等等也有很多精妙绝伦的封装,感兴趣的同学可以研究一下官方文档。

注意:1. 不管使用哪一种锁,都需要设置锁的过期时间。
     2. 使用lock()、tryLock()时,一定要放在try{}catch(){}中,且unlock一定要放在finally{}里
     3. 所有需要使用分布式锁场景的锁名,都要使用枚举、常量统一管理

redisson锁原理

1) tryLock()表示尝试加锁,加锁成功返回true,加锁失败返回false。使用tryLock()加锁,线程没有获取到锁不会阻塞。

使用tryLock()加锁流程:

2) lock() 加锁,此锁已被持有则等待,没有被持有则获取这把锁。使用lock()加锁,线程没有获取到锁会一直阻塞直到获取到锁。

使用lock()加锁流程:

3) 使用可重入锁、公平锁、读写锁时,执行的lua脚本各不相同。
类型 加锁lua 解锁lua
可重入锁 红锁
公平锁
读锁
写锁
4)tryLock()与lock()的使用场景
  • 场景一: 库存抢占。有效库存只有100,多个应用来抢占这100个库存总数。这时使用:lock()
  • 场景二: 退款申请。网络卡顿或其他原因同时段提交多次退款申请,实际只生成一条退款单。这时使用:tryLock()
5)tryLock()与lcok()使用的注意事项
  • tryLock() 尝试获取锁,不等待,返回获取结果。
  • tryLock(long waitTime, TimeUnit timeUnit) 尝试获取锁,并等待waitTime个时间单位,之后返回获取结果。
  • tryLock(long waitTime, long leaseTime, TimeUnit timeUnit) 尝试获取锁,并等待waitTIme个时间单位,并将此锁持有leaseTime个时间单位,之后返回获取结果。
  • lock() 获取锁,线程阻塞,直到获取到锁为止,锁不过期,直到解锁或者被内部机制释放。
  • lock(long leaseTime, TimeUnit timeUnit) 获取锁,直到获取到锁为止,持有锁leaseTime个时间单位,之后自动释放锁,也可以提前手动释放。
6)公平锁、可重入锁、读写锁、红锁的使用场景
  • 场景一: 门票只有100张。先到先得。这时使用公平锁

  • 场景二: 1. 门票只有100张,需要抢票。2. 需要把下一张门票修改成vip票。 (可重入锁是在一个线程中多次获取同一把锁。一个线程在执行一个带锁的方法时,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁)

  • 场景三: 所有人都能看到库存信息,但同时只有一个人来修改库存。所有的人都能看到库存信息,使用读锁。同时只有一个人来修改库存,使用写锁。多个读锁不互斥,读锁与写锁互斥

  • 场景四: 当需要超级高可用锁的场景时,使用红锁。

    综上所述,使用tryLock还是lock,使用公平锁还是读写锁都是看具体的业务和场景而言。