Redis分布式锁的实现

场景

在抢购平台上,如何避免超买超卖的场景?
如果你是单体服务,可以使用synchronized ,但是如果是部署两台或多台服务的话,synchronized 就不起作用了,因为的作用域只是单个的JVM。这个时候就要引入分布式锁的概念。

概念

同一时间只能有一个客户端对共享资源进行操作。

实现

1.利用setnx

 @Autowired
    private RedisTemplate redisTemplate;
@RequestMapping("/getProduct")
    @RequestMapping("/getProduct")
    public String getProduct() {
        String lockKey = "product_01";
        //每个线程给一个给一个标识
        String cid = UUID.randomUUID().toString();
        //如果这个值存在返回false  如果这个值不存在插入成功返回true         setnx
        //这里设置超时时间是为了解决,如果执行是突然出现宕机情况,这个锁一直无法释放。
        boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, cid, 10, TimeUnit.SECONDS);
        if (!result) {
            return "ERROR";
        }
        try {
            /*
            一些业务代码操作,成功返回成功
            return "Success"
             */
        } finally {
            //关闭自己开启的锁 但是任然存在问题 可能在判断的那0.01s这个锁被释放掉 这样就会导致删除的任然不是自己的锁
            if (cid.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }
        return "ERROR";
    }

但是以上代码仍然存在问题,如果在业务操作中,出现一些类似慢sql、卡死等问题,导致代码无法在超时时间内完成,于是又可能出现超买超卖的现象。其实主要是对超时时间得把控,设置太短了容易,导致代码锁失效,太长了系统宕机,之后锁一直无法释放。怎么来解决这个问题呢?
锁续命,就是当系统业务逻辑代码没有执行完对代码超时时间进行续命。
Redisson(redis的儿子)很好的封装了这一机制。
大概逻辑:当一个线程如果加锁成功,那么它会启动一个后台线程,每隔一段时间检查是否还持有,如果持有那么就会延长时间。其他线程过来的时候就会一直判断能都加锁成功。
pom.xml

<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson</artifactId>
     <version>3.6.5</version>
</dependency>
配置bean
@Bean
    public Redisson redisson() {
        Config config = new Config();

        config
                .useSingleServer()//单机模式
                .setAddress("redis://127.0.0.1:6379")
                .setDatabase(0);
        return (Redisson) Redisson.create(config);
    }

@Autowired
    private Redisson redisson;
    @RequestMapping("/getProduct2")
    public String getProduct2() {
        String lockKey = "product_01";
        //拿到锁对象
        RLock lock = redisson.getLock(lockKey);
        try {
            //加锁
            lock.lock();
            /*
            一些业务代码操作,成功返回成功
            return "Success"
             */

        } finally {
            //释放锁
            lock.unlock();
        }
        return "ERROR";
    }
//加锁逻辑  lua脚本在redis执行 会保证原子性
  <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);

        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                  "if (redis.call('exists', KEYS[1]) == 0) then " +//判断我们的lockkey是否存在
                      "redis.call('hset', KEYS[1], ARGV[2], 1); " +//如果不存在 就会设置一个key
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +//设置一个超时时间 超时时间默认30s
                      "return nil; " +
                  "end; " +
                  "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                      "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                  "return redis.call('pttl', KEYS[1]);",
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }
//续命的逻辑
 private void scheduleExpirationRenewal(final long threadId) {
        if (expirationRenewalMap.containsKey(getEntryName())) {
            return;
        }

        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                //lua脚本主要判断key是否存在(lockkey),
               //然后判断锁是否还存在 存在就续命
               //如果线程结束了,就不会加锁了( ARGV[2])== 1)
                RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                            "return 1; " +
                        "end; " +
                        "return 0;",
                          Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
                
                future.addListener(new FutureListener<Boolean>() {
                    @Override
                    public void operationComplete(Future<Boolean> future) throws Exception {
                        expirationRenewalMap.remove(getEntryName());
                        if (!future.isSuccess()) {
                            log.error("Can't update lock " + getName() + " expiration", future.cause());
                            return;
                        }
                        
                        if (future.getNow()) {
                            // reschedule itself  延迟10秒调用
                            scheduleExpirationRenewal(threadId);
                        }
                    }
                });
            }
            //锁加完以后,会延迟internalLockLeaseTime / 3,(一般为10秒)
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

        if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {
            task.cancel();
        }
    }

其实性能还是一个问题,并发变成串行。

参考

分布式锁的代码实现 https://blog.csdn.net/qq_37781649/article/details/108814474
有问题及时指正啊各位!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值