分布式锁讲解

什么是分布式锁?

在单体式架构中我们常用到锁,悲观锁乐观锁,但是在分布式中,因为会有很多系统,多进程,这些锁只能在自己的进程中生效,其他服务就不可以了,即他们只能实现线程并发问题的解决,而多进程的并发问题就没办法解决了,这个时候就出现了分布式锁,常用的有以下几种方式:

1.基于数据库实现分布式锁

2.基于缓存(redis等)

3.基于zookeeper

本章主要讲解基于redis的实现!

常用命令:

setnx users 10  设置一个key,值为10,此时,别的操作都不可以修改或者操作

del 设置的锁key 解锁

expire users seconds 设置过期时间

set users 10 nx ex 10 不仅设置了key还上了锁还设置了过期时间

java操作redis使用uuid防止误删:

 有这么一种情况就是比如你两个操作并发,第一次设置了锁,过期时间为12秒,但是操作中卡顿了,导致13秒还没执行完,那此时就会出现一个问题,那就是设置的锁过期了,第二次操作可能就抢到了锁,但是你第一次还没执行完,它还没释放锁,等它反应过来不卡的时候,就会释放锁,那此时它释放的就是第二次的锁,这肯定不对了,那我么就可以在上锁的时候给每个锁设置自己的uuid,如果解锁的时候发现不是自己的就不解了

 LUA脚本解决非原子操作:

 

 

uuid基本上可以解决大部分问题了,但是还是会出现一个问题,比如你已经判断完是自己的锁了,可以解锁,但是还没解锁,此时锁的时间到了自动过期了,那并发的操作还是会抢到锁,此时就需要,使用lua脚本把判断和解锁的过程绑定为一个原子性操作,这两个操作不做完,其他操作都进不来

可以看到有一个excute方法,这个方法就是执行lua脚本的,第一个参数就是一个存放lua字符串和返回值的接口实例,注意这个必须设置两个参数,一个是lua字符串,一个是返回值,否则会报错,然后第二个参数就是keys,第二个是avgv参数

除了锁里边的操作和上边不一样以外,其他都一样

                 上边的代码已经实现了基本的分布式锁的功能,但是还有一个巨大的漏洞就是,假如我在这个锁里去递归调用当前锁的方法,就会出现,死锁的问题,因为我们的锁是不可重入的,想要实现可重入锁,就要使用hash+lua脚本去实现

package sca.pro.core.threadtask;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import sca.pro.common.exception.BusinessException;
import sca.pro.core.utils.string.Convert;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

@Component
@RequiredArgsConstructor
public class ReantrLock implements Lock {

    @Autowired
    StringRedisTemplate stringRedisTemplate;


    private   String  lockName;

    private   String  uuId;

    private   String  expire="3000";

    public ReantrLock(String lockName) {
        this.lockName = lockName;
    }

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

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        try {
          return   tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }

   //加锁的逻辑,首先判断锁是否存在,不存在,则直接获取锁 ,如果锁存在则判断是不是自己的锁,如果是自己的锁则重入:就是在uuid基础上加一,否则重试,递归循环
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time!=-1){
          this.expire= Convert.toStr(unit.toSeconds(time));
        }
        String script=" if redis.call('exists','lock')== 0 or redis.call('hexists','lock',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";
        Boolean lock = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuId,expire);
        while (!lock){
            Thread.sleep(80);
        }
    return true;
    }

 //判断自己的锁是否存在(hexists),不存在则返回nil,如果自己的锁存在,则减一,判断减一后的值是否为0,为0则释放锁并返回1,不为0,则返回0
    @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 execute = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuId);
       if (execute==null){
         throw   new BusinessException(1111,"这个锁不属于你");
       }
    }

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EntyIU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值