Redis分布式锁

45 篇文章 0 订阅

手写分布式锁

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.3.6.RELEASE</version>
     </dependency>
package com.dmg.demo.config;


import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

//实现可重入锁
public class RedisLock implements Lock {

    private StringRedisTemplate redisTemplate;
    private String lockName;
    private String uuid;
    //默认过期时间
    private long expire=60;

    //有参构造注入
    public RedisLock(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 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 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 ";
       //uuid 默认传当前线程的id+uuid 看是不是当前线程的uuid 当前的uuid可能重复 多个线程进来 所以保证唯一就是加线程id
       while (!redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName)
               ,uuid,String.valueOf(expire))){
           //如果加锁的结果为0 就是false 那么等待重试 存在锁 但是 是别的线程的锁 不是我的线程的锁 所以等待重试 阻塞 等其他线程走完了
           //我才能加锁成功   就是uuid不一样 不能重入 如果一样就是重入了一次 又更新了下过期时间
           Thread.sleep(100);
       }
       //最后加锁成功
       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 res=redisTemplate.execute(new DefaultRedisScript<>(script,Long.class),Arrays.asList(lockName),uuid);
        if(res==null){
            //抛出异常 异常解锁
            throw new IllegalMonitorStateException("异常解锁");
        }
    }

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



    public void autoRenew(){
        //自动续期
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
               String script="if redis.call('hexists',KEYS[1],ARGV[1])==1" +
                       " then  return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end";
               boolean flag= redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class)
                       ,Arrays.asList(lockName),uuid,String.valueOf(expire));
               if(flag){
                   //如果续期成功 就一直续期 如果续期失败 那么这个方法就是结束了 不需要在续期了
                   autoRenew();
               }
               //当前过期时间到了3分之1的时候 在进行续期
            }
        }, expire*1000/3);

    }
}
//单例工厂
@Component
public class RedisLockClient {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private String uuid;
    private RedisLockClient(){
        //在无参方法里面初始化uuid 多个对象调用 只返回一个一样的uuid
        uuid= UUID.randomUUID().toString();
    }

    //这里传入之后 对方就不用在加注解了
    public RedisLock getRedisLock(String lockName){
        return new RedisLock(redisTemplate,lockName, uuid);
    }

}
//单例工厂
@Autowired
private RedisLockClient redisLockClient;

@Autowired
private StringRedisTemplate redisTemplate;

public void aaa(String 商品id){
        //注意 这里一定要结合商品id去加锁,如果不加,别的商品进来的 你也给人家锁住了
        String lockName="lock1:"+商品id;
        RedisLock redisLock=redisLockClient.getRedisLock(lockName);
        //加锁
        redisLock.lock();
        try {
            //自动续期 防止 方法还没有走完 锁就过期了
            redisLock.autoRenew();

            //查询库存
            String stock=redisTemplate.opsForValue().get("stock").toString();
            //是否够用
            if(StringUtils.isEmpty(stock)){
                return;
            }
            int num=Integer.parseInt(stock);
            if(num>0){
                //减去库存
                num--;
                redisTemplate.opsForValue().set("stock",String.valueOf(num));
            }
            
        }finally {
            //释放锁
            redisLock.unlock();
        }
 }

配置文件

spring.redis.host=192.168.184.145
spring.redis.port=6379

使用Redisson实现分布式锁


        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.17.1</version>
        </dependency>
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient(){
        // 配置对象
        Config config = new Config();
        //单节点 地址 redis://ip:6379
        config.useSingleServer().setAddress("redis://192.168.184.132:6379");
        //创建配置
        return Redisson.create(config);
    }
}
 @Autowired
    private RedissonClient redissonClient;

    public void aa(String 商品id){

        //注意 这里一定要结合商品id去加锁,如果不加,别的商品进来的 你也给人家锁住了
        String lockName="lock1:"+商品id;
        RLock rLock=redissonClient.getLock(lockName);
        //加锁  默认有自动续期的功能,默认时间30秒,当前时间达到3分之1的时候,自动续期
        //底层就是继承了juc的Lock类,也实现了可重入锁
        rLock.lock();
        try {
            //查询库存
            String stock=redisTemplate.opsForValue().get("stock").toString();
            //是否够用
            if(StringUtils.isEmpty(stock)){
                return;
            }
            int num=Integer.parseInt(stock);
            if(num>0){
                //减去库存
                num--;
                redisTemplate.opsForValue().set("stock",String.valueOf(num));
            }


        }finally {
            //释放锁
            rLock.unlock();
        }
    }

在使用分布式锁的时候,要吧事务放在外面,不能在分布式锁哪个方法加事务,例如分布式锁在内掉了一个修改的方法,在哪个修改的方法里面去加事务,如果在分布式锁加了事务,会造成可重复读的现象

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值