redis分布式(锁)

        多个线程同时向redis中设置同一个key的值,这种情况通常是加锁,哪个线程加锁成功就能操作,如果通过java代码来加锁就是以下几个步骤,因为不是原子性存在一些问题:

        1、加锁后线程执行过程中出现异常,这个锁会一直存在,其他线程一直在等待中;

              解决这种情况是加锁的代码放在try中,解锁的代码放在finally中,无论抛异常与否解锁的代码都会执行;

        2、加锁后系统宕机,finally中的代码也不会执行

              解决这种情况是给锁设置过期时间,这个锁过期后其他线程可以正常执行;

        3、倘若加的过期时间是30S,但是当前线程执行需要50S,这种情况下当前线程没执行完成其他的线程就开始修改数据也会有问题;

              解决方法是延长过期时间(也叫锁续命),默认每次延长时间是过期时间的三分之一,这种情况务必确保无论线程是否执行成功都要释放锁(redisson客户端都已经实现);

             如果程序加锁后系统宕机然后一直续命和长久锁没有什么区别,所以要设置延长次数,超过这个延长次数后就要释放锁并回滚事物;

可参考:解决Redis分布式锁过期(续命)_redis分布式锁过期问题_神经刀子手的博客-CSDN博客

       redisson是redis的客户端,实现了加锁和解锁的原子操作,通过lua脚本实现,这种操作具有原子性(所有的命令要么全部成功要么全部失败)

       以下是redisson中解锁和解锁的源码:

              加锁和解锁的命令是发到redis执行的,所以解决了多客户端同时加锁的问题

        加锁

        1、若锁不存在:则新增锁,并设置锁重入计数为1、设置锁过期时间

         2、若锁存在,且唯一标识也匹配(不是判断重入次数是1):则表明当前加锁请求为锁重入请求,故锁重入计数+1,并再次设置锁过期时间

         3、若锁存在,但唯一标识不匹配:表明锁是被其他线程占用,当前线程无权解他人的锁,直接返回锁剩余过期时间

KEYS[1]my_first_lock_name锁名,也是redis设置的key值
ARGV[1]30000持有锁的有效时间:毫秒
ARGV[2]58c62432-bb74:1123唯一标识:获取锁时set的唯一值,实现上为redisson客户端ID(UUID)+线程ID

        解锁:

KEYS[1]my_first_lock_name锁名,也是redis设置的key值
KEYS[2]redisson_lock_channel:{my_first_lock_name}解锁消息PubSub频道
ARGV[1]0redisson定义0表示解锁消息
ARGV[2]30000锁的过期时间;默认值30秒
ARGV[3]58c62432-bb74-4d14:1123线程唯一标识;同加锁流程

// 1、锁不存在:则直接广播解锁消息,并返回1
if (redis.call('exists', KEYS[1]) == 0) then
  redis.call('publish', KEYS[2], ARGV[1]);
  return 1;
end;
//2、锁存在,但唯一标识不匹配:则表明锁被其他线程占用,当前线程不允许解锁其他线程持有的锁
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
  return nil;
end;
//3、若锁存在,且唯一标识匹配:则先将锁重入计数减1 
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);

//锁重入计数减1后还大于0:表明当前线程持有的锁还有重入,不能进行锁删除操作,但可以友好地帮忙设置下过期时期
if (counter > 0) then
  redis.call('pexpire', KEYS[1], ARGV[2]);
  return 0;
else

//锁重入计数已为0:间接表明锁已释放了。直接删除掉锁,并广播解锁消息,去唤醒那些争抢过锁但还处于阻塞中的线程
  redis.call('del', KEYS[1]);
  redis.call('publish', KEYS[2], ARGV[1]);
  return 1;
end;
return nil;

加解锁的demo参考链接:链接:https://pan.baidu.com/s/1tiII7OPg3BSijvhMxIvuDw?pwd=9jiz 
       加锁参考IndexController中的 redissonLock.lock() -->RedissonLock.lock().lockInterruptibly()-->tryAcquire()-->tryAcquireAsync()-->tryLockInnerAsync() 可以看到加锁的lua脚本

       解锁参考 IndexController中的 redissonLock.unlock();

加锁和解锁可参照:Redis应用详解(一)分布式锁_redis.call('hincrby', keys[1], argv[1], argv[2])_fedorafrog的博客-CSDN博客

        redis加锁时使用自旋锁:当一个线程对某个key加锁后另一个线程再对这个key加锁会一直自旋,直到第一个线程释放锁后然后第二个线程获得锁再进行操作

        在上面的demo中,IndexController中有两个方法deductStock、deductStock1来模拟两个线程的加锁,使用lock方法会一直自旋(自旋会一直消耗CPU,所以只适合自旋时间短的场景),也可以使用tryLock方法(指定时间内获得锁返回true 否则false)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值