分布式锁的原理和实现

四种分布式锁

redis原生分布式锁。

/**
没有对于锁失效和锁续命进行解决,需要的可以根据下面的说明和思路,自行解决。
*/
@Override
    public boolean deduckStore(Long goodId, Integer num) {
        String key = "goods:deduck:" + goodId;
        //setIfAbsent 对应redis原生的setnx
        //锁失效
        //锁续命 ttl 30 10 30 
        boolean lock = redisTemplate.opsForValue().setIfAbsent(key,goodId,30L, TimeUnit.SECONDS);
        while (lock == false){
 			//自旋,等待再次获取锁。
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock = redisTemplate.opsForValue().setIfAbsent(key,goodId,30L, TimeUnit.SECONDS);
        }
        try {
            //45s
            Goods goods = this.baseMapper.selectById(goodId);
            if(goods.getStokeNum() >= num){//判断是否可以进去扣减库存
                goods.setStokeNum(goods.getStokeNum() - num);
                this.updateById(goods);
            }
        }finally {
            redisTemplate.delete(key);
        }
        return true;
    }

redis加锁:

​ 使用setnx保证数据不会更改的原理,当key值相同时,如果线程A写入数据后,其他线程在继续对该key写入数据时就会失败,只有等该key删除或失效后,其他线程才能写入。这样就保证了同一时刻只有一个线程获取到锁。

redis死锁:

​ 当线程A得到锁后,线程A所在的服务器宕机,或者线程A运行出错,没有释放分布式锁,此时,其他服务器上的线程就会进入无限等待状态,导致死锁。解决方法,在使用setnx写入锁时指定锁(key)的失效时间。在到达失效时间后redis自动清除锁。其他的服务器就能正常写入锁执行业务。

锁失效:

​ 由于服务器的性能和业务的复杂程度不同,我们不能明确执行业务的具体时间,所以当失效时间过短时,会出现锁失效问题。假设线程A拿到锁后由于所在的服务器性能低,执行业务需要40s,而锁的失效时间指定的是30秒。当线程A还在执行业务时,锁失效了,此时线程B就能加锁成功,进性业务的执行,而10秒后线程A业务执行完毕,释放锁时,删除的是线程B写入的锁,又导致线程C可以加锁成功。依次循环往复,从而产生锁失效的问题。解决方法:进行锁续命,在业务还未执行完,但是锁快要失效时,重新设置锁的失效时间。

锁续命:

​ 在锁还没有用完,但是又快要过期时,重置锁的过期时间。可以对一个已经带有生存时间的 key 执行 EXPIRE 命令,新指定的生存时间会取代旧的生存时间。例如锁的过期时间是30秒,可以通过TTL监听锁的剩余生存时间,当还剩10秒时,使用EXPIRE 命令将锁的生存时间重新设置为30秒。从而达到锁续命的目的。当线程死掉或者所在的服务器宕机时,不会执行锁续命的代码,只有代码正常运行时才会定时执行锁续命代码,所以不会出现死锁现象。

redisson框架分布式锁。

使用步骤

1.引入redisson框架
<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.11.5</version>
        </dependency>
2.配置类

@Configuration
public class RedissonConfig {

    @Resource
    private RedisProperties redisProperties;
    
    @Bean
    public RedissonClient redissonClient(){
       RedissonClient redissonClient;
        Config config = new Config();
        String url = "redis://"+redisProperties.getHost()+":"+redisProperties.getPort();
        //单机redis,如果是redis集群使用config.useClusterServers().addNodeAddress(...)
        config.useSingleServer().setAddress(url)
            .setPassword(null)
            .setDatabase(redisProperties.getDatabase());
        try{
            redissonClient = Redisson.create(config);
            return redissonClient;
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }
    }
}

3.分布式锁实现
	@Resource
    private RedissonClient redissonClient;

    /**
     * redisson框架实现分布式锁
     * */
    @Override
    public boolean deduckStock(Long goodsId, Integer num) {
        try {
            //获取锁
           RLock lock = redissonClient.getLock("/myLock");
            //设置超时时间
            lock.lock(10L,TimeUnit.SECONDS);
            //获取锁成功后开始执行业务
            Goods goods = this.getById(goodsId);
            if(goods == null){
                return false;
            }
            if(goods.getStock() >= num){
                goods.setStock(goods.getStock() - num);
            }else{
                throw new RuntimeException("商品库存不足!");
            }
            return this.updateById(goods);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //释放锁资源
            lock.unlock();
        }
        return false;
    }

spring integration redis

官网参考:https://docs.spring.io/spring-integration/docs/current/reference/html/redis.html#redis-lock-registry

原理图:

在这里插入图片描述
在这里插入图片描述

使用步骤

1.引入依赖
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.redis.util.RedisLockRegistry;

@Configuration
public class RedisLockConfig {

    @Bean
    public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
        //registryKey全局分布式锁key前缀
        return new RedisLockRegistry(redisConnectionFactory, "mylock");
    }

}

3.分布式锁实现
	@Resource
    private RedisLockRegistry redisLockRegistry;
    
    /**
     * spring integration redis分布式锁实现
     * */
    @Override
    public boolean deduckStock(Long goodsId, Integer num) {
        Lock lock = redisLockRegistry.obtain("lock:" + goodsId);
        try {
            boolean hasLock = lock.tryLock(10, TimeUnit.SECONDS);//尝试获取锁
            if(hasLock){
                //获取锁成功后开始执行业务
                Goods goods = this.getById(goodsId);
                if(goods == null){
                    return false;
                }
                if(goods.getStock() >= num){
                    goods.setStock(goods.getStock() - num);
                }else{
                    throw new RuntimeException("商品库存不足!");
                }
                return this.updateById(goods);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //释放锁
            lock.unlock();
        }
        return false;
    }

​ redis集群做分布式锁的问题:数据一致性问题。单个redis不存在这种问题。当线程A在主redis中写入锁后,主redis还未来得及将数据同步到从redis中,线程B在从redis中写入锁会成功,因为从redis中还没有最新数据,这样就会因为数据不一致导致的锁失效问题。但是这种情况很极端,如果对业务的数据要求不是很严格,可以忽略该情况。

curator

curator官网地址:http://curator.apache.org/

原理图:

在这里插入图片描述

使用步骤

1.引入curator框架
<dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>5.1.0</version>
        </dependency>
2.配置类

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CuratorLockConfig {

    @Bean(initMethod = "start")
    public CuratorFramework curatorFramework(){
        //重试策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        //connectString使用逗号分隔如: localhost:2181,localhost:2182
        CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy);
        return client;
    }

}

3.分布式锁实现
	@Resource
    private CuratorFramework curatorFramework;

    /**
     * curator框架实现分布式锁
     * */
    @Override
    public boolean deduckStock(Long goodsId, Integer num) {
        InterProcessMutex lock = new InterProcessMutex(curatorFramework,"/mylock");
        try {
            //获取锁
            boolean hasLock = lock.acquire(10, TimeUnit.SECONDS);
            if(hasLock){
                //获取锁成功后开始执行业务
                Goods goods = this.getById(goodsId);
                if(goods == null){
                    return false;
                }
                if(goods.getStock() >= num){
                    goods.setStock(goods.getStock() - num);
                }else{
                    throw new RuntimeException("商品库存不足!");
                }
                return this.updateById(goods);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                //释放锁资源
                lock.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return false;
    }

​ curator集群做分布式锁:没有数据一致性问题,因为curator具有数据强一致性。当有节点建立时,集群中大多数的zookeeper成功写入节点后才会返回建立成功。这样当领导zookeeper掉线时,会暂停服务,等新的领导选举成功后才会继续服务,新的领导具有最新的数据。

如何选择分布式锁

​ 基于redis的分布式锁在redis集群架构时会有数据一致性问题,但是该问题比较极端,不易发生,如果业务的严谨性不是很高,有一定的容错,可以优先选择使用spring integration redis作为分布式锁。redis原生太麻烦,redisson框架比较重。

​ 基于zookeeper的curator由于强一致性的特点,不会有数据一致性问题,但是强一致性也带来,性能不如redis,所以,如果业务的容错率很低,但是性能的要求不是很高,可以选择curator作为分布式锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值