redis实现分布式锁

描述

多个客户端获取锁,只有一个客户端能获取到,获取到客户端执行业务逻辑,执行之后释放锁,而未获取到锁的客户断一直等待去获取锁。

可重入锁实现

锁的实现基于lua脚本。
1、上锁:先判断这个锁是否存在,不存在直接创建并设置过期时间,存在value自增1。使用线程id作为获取到锁的key作为表示,好处是防止不同客户端的锁误删。
2、解锁:判断key是否存在,不存在直接返回nil;存在value值进行减1,自减后的值为0时则把锁给删除掉。
3、锁的过期时间续期:key存在就将过期时间就行恢复,定时任务定时执行去恢复key的过期时间。(续期操作的意义,防止业务逻辑还没执行完锁就过期)

public class DistributedRedisLock implements Lock {

    private StringRedisTemplate stringRedisTemplate;

    private String lockName;

    private String uuid;

    private long expire = 30;

    public DistributedRedisLock(StringRedisTemplate stringRedisTemplate,String lockName,String uuid){
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuid = "uuid:"+Thread.currentThread().getId();
    }
    @Override
    public void lock() {
        tryLock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @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 lua = "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 (!stringRedisTemplate.execute(new DefaultRedisScript<>(lua, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))){
            Thread.sleep(50);
        }
        //获取锁成功后,过期时间自动续期
        renewExpire();
        return true;
    }

    @Override
    public void unlock() {
        String lua = "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 = stringRedisTemplate.execute(new DefaultRedisScript<>(lua, Long.class), Arrays.asList(lockName), uuid);
        if (flag == null ){
            throw new IllegalMonitorStateException("this lock doesn't belong to you!");
        }

    }

    @Override
    public Condition newCondition() {
        return null;
    }

    public void renewExpire(){
        String lua = "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() {
                if (stringRedisTemplate.execute(new DefaultRedisScript<>(lua,Boolean.class),Arrays.asList(lockName),uuid,String.valueOf(expire))) {
                    renewExpire();
                }
            }
        },expire*1000/3);
    }
}

工厂模式调用锁

@Component
public class DistributedLockClient {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private String uuid;

    public DistributedLockClient() {
    }

    public DistributedLockClient(String uuid) {
        this.uuid = UUID.randomUUID().toString();
    }

    public DistributedRedisLock getDistributedRedisLock(String lockName){
        return  new DistributedRedisLock(stringRedisTemplate,lockName,uuid);
    }
}

使用可重入锁解决秒杀案例

public void deduct(){
        //上锁
        DistributedRedisLock lock = distributedLockClient.getDistributedRedisLock("lock");
        lock.lock();
        try {
            String stock = redisTemplate.opsForValue().get("stock");
            if (Objects.nonNull(stock)&&Integer.parseInt(stock)>0){
                int i = Integer.parseInt(stock);
                redisTemplate.opsForValue().set("stock",String.valueOf(--i));
            }
            test();
        }finally {
            lock.unlock();
        }
    }

    public void test(){
        DistributedRedisLock lock = distributedLockClient.getDistributedRedisLock("lock");
        lock.lock();
        System.out.println("获取锁成功");
        lock.unlock();
    }
lua基本语法
a = 5               -- 全局变量
local b = 5         -- 局部变量, redis只支持局部变量
a, b = 10, 2*x      -- 等价于       a=10; b=2*x

if( 布尔表达式 1)
then
   --[ 在布尔表达式 1true 时执行该语句块 --]
elseif( 布尔表达式 2)
then
   --[ 在布尔表达式 2true 时执行该语句块 --]
else 
   --[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
end

redis执行lua脚本

EVAL script numkeys key [key ...] arg [arg ...]
script:lua脚本字符串,这段Lua脚本不需要(也不应该)定义函数。
numkeys:lua脚本中KEYS数组的大小
key [key ...]KEYS数组中的元素
arg [arg ...]ARGV数组中的元素

实例

EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 5 10 20 30 40 50 60 70 80 90
# 输出:10 20 60 70
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值