redis分布式锁(手写)--尚硅谷分布式锁教程笔记

  1. 前期学的是不可重入锁的版本:

    加锁用 redisTemplate.opsForValue().setIfAbsent("lock", uuid);

    解锁用 redisTemplate.opsForValue().delete("lock");

    问题一:加锁后就宕机导致死锁

    解决一:给锁加一个过期时间

    问题二:业务还在执行但锁已经过期了,前面业务可能释放后面的锁

    解决二:解锁之前要判断是不是自己的锁

    问题三:判断锁是否是自己的和解锁非原子操作

    解决三:用Lua脚本将两步合为一步,保证原子性

    问题四:不可重入

    解决四:放弃这种原始的加锁解锁方式

    问题五:锁过期导致线程并发

    解决五:用Timer定时器实现自动续期

  2. 后期学的是可重入锁的版本:

    用 Lua脚本和redis的hash结构,基本结构为lock:uuid+Thread.currentThread().getId() :count

    1)加锁和解锁都用Lua脚本,脚本中判断各种可能的逻辑(有过期时间的设置,判断是不是自己的锁,是否是重入等),也保证了判断和加锁的原子性                             解决上面讲的问题一二三四

    2)Timer定时器 + lua脚本:实现锁的自动续期

    判断锁是否自己的锁(hexists == 1),如果是自己的锁则执行expire重置过期时间  解决了上面的问题五

总结:所谓的分布式锁,就是用一个第三方的数据库,所有线程(包括同一个进程中的和不同进程中的)都访问的是这个数据库(这对于所有线程就是唯一的),使用逻辑锁就可以使各个线程之间不会出现并发问题

同一个进程用线程id区分,不同进程用uuid区分

最终版本

工厂方法,便于后期拓展

@Component
public class DistributedLockClient {
    @Autowired
    private StringRedisTemplate redisTemplate;

    private String uuid;

    public DistributedLockClient() {
        this.uuid = UUID.randomUUID().toString(); 
        // 这个UUID在spring容器初始化时就已经创建好了,保证了一个服务器的uuid都是一样的
        // 同一个服务中用ThreadId区分
        // 不同服务中首先uuid就不同了,就区分开了
    }

    public DistributedRedisLock getRedisLock(String lockName){
        return new DistributedRedisLock(redisTemplate, lockName, uuid);
    }
}
public class DistributedRedisLock implements Lock {

    private StringRedisTemplate redisTemplate;

    private String lockName;

    private String uuid;

    private long expire = 30;  // 默认过期时间30ms

    public DistributedRedisLock(StringRedisTemplate redisTemplate, String lockName, String uuid) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuid = uuid + ":" + Thread.currentThread().getId();  // 这步关键
    }

    @Override
    public void lock() {
        this.tryLock();
    }

    @Override
    public boolean tryLock() {
        try {
            return this.tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time != -1){
            this.expire = unit.toSeconds(time);
        }
        // 还未上锁,已经上锁且是自己的
        String script = "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                "   redis.call('expire', KEYS[1], ARGV[2]) " +
                "   return 1 " +
                "else " +
                "   return 0 " +
                "end";
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))){
            Thread.sleep(50);
        }
        // 加锁成功,返回之前,开启定时器自动续期
        this.renewExpire();
        return true;
    }

    @Override
    public void unlock() {
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
                "then " +
                "   return nil " +
                "elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +
                "then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
        Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);
        if (flag == null){
            throw new IllegalMonitorStateException("this lock doesn't belong to you!");
        }
    }
    
    private void renewExpire(){
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   return redis.call('expire', KEYS[1], ARGV[2]) " +
                "else " +
                "   return 0 " +
                "end";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {  // 开启的定时器是一个子线程,不能用上面的getId方法,而是直接在主线程中拼好uuid(该uuid是生成的uuid+线程数),然后这里用
                if (redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))) {
                    renewExpire();  // 再次开启一次性任务,直到上面续期失败了就直接结束了
                }  // 另一种实现方式是用周期任务,在unlock中取消定时任务
            }
        }, this.expire * 1000 / 3);// 设置成只有延迟时间,是一次性的任务,就不需要思考结束任务的方式
    }
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redisson是基于Redis实现的Java驻留内存数据网格的开源框架,提供了丰富的分布式锁实现方式。下面介绍基于Redisson的最优实现。 1. 初始化Redisson客户端 ```java Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); ``` 2. 获取分布式锁 ```java RLock lock = redisson.getLock("myLock"); lock.lock(); try { // 业务逻辑 } finally { lock.unlock(); } ``` 3. 设置加锁超时时间和释放锁时限制 ```java RLock lock = redisson.getLock("myLock"); boolean locked = lock.tryLock(10, 60, TimeUnit.SECONDS); try { if (locked) { // 业务逻辑 } else { // 获取锁失败 } } finally { if (locked) { lock.unlock(); } } ``` 4. 实现可重入锁 ```java RLock lock = redisson.getLock("myLock"); lock.lock(); try { lock.lock(); try { // 业务逻辑 } finally { lock.unlock(); } } finally { lock.unlock(); } ``` 5. 实现公平锁 ```java RLock lock = redisson.getFairLock("myFairLock"); lock.lock(); try { // 业务逻辑 } finally { lock.unlock(); } ``` 6. 实现读写锁 ```java RReadWriteLock rwlock = redisson.getReadWriteLock("myReadWriteLock"); rwlock.readLock().lock(); try { // 读操作业务逻辑 } finally { rwlock.readLock().unlock(); } rwlock.writeLock().lock(); try { // 写操作业务逻辑 } finally { rwlock.writeLock().unlock(); } ``` 以上就是基于Redisson实现Redis分布式锁的最优实现。Redisson提供了丰富的分布式锁实现,可以根据业务需求选择合适的锁类型。同时,Redisson还提供了许多其他功能,如分布式对象、分布式限流等,可以方便地实现分布式应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值