redis分布式锁的相关问题

1.什么是分布式锁

分布式锁,即分布式系统中的锁。在单体应用中我们使用锁解决的是控制共享资源访问的问题,通常使用的是synchronized关键字;而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程

在这里插入图片描述

 2.redis如何实现分布式锁?

 2.1 加锁

命令: setnx(key,value);
在java代码中,当一个线程执行 setnx 返回 ture,说明加锁成功,key原本不存在在redis中;当一个线程执行 setnx 返回 false,说明 key 已经存在,该线程抢锁失败。

String lockKey = "product_001";//lockKey为需要抢占的资源
String clientId = UUID.randomUUID().toString();//解决 高并发情况下 redis分布式锁 永久失效 的问题
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId);

2.2 解锁

有加锁就得有解锁。当得到锁的线程执行完任务后,需要释放锁,以便其他线程可以进入。释放锁的最简单方式是执行 del 指令,伪代码:del(key) ,释放锁之后其他线程就可以通过执行setnx,来获取锁。

if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {

                stringRedisTemplate.delete(lockKey);//释放锁
  }

2.3 死锁的问题

假如一个得到锁的线程在执行任务的过程中挂掉,来不及释放锁,这块资源将会永远被锁住(死锁),别的线程再也别想进来。所以,setnx的 key 必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。

Boolean result =
             stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10,TimeUnit.SECONDS);

3.实现redis分布式锁中可能出现的问题

3.1 setnx 和 expire 命令组合的非原子性 

当某线程执行 setnx,成功得到了锁,setnx刚执行成功,还未来得及执行 expire 指令,节点 可能 挂掉了。这样一来,这把锁就没有设置过期时间,变成死锁,别的线程再也无法获得锁了。
怎么解决呢?setnx 指令本身是不支持传入超时时间的,set 指令增加了可选参数,伪代码如下:
 setnx set(key,1,30,NX),java中就比较简单:

Boolean result =
            stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);

3.2 高并发情况下redis分布式锁永久失效 的问题(一个线程可能删除了别的线程的锁)

假如线程A成功得到了锁,并且设置的超时时间是 30 秒;

可能某些原因导致线程 A 执行的很慢很慢,过了 30 秒都没执行完,这时候锁过期自动释放,此时线程 B 得到了锁;

随后,线程 A 执行完了任务,线程 A 随之释放锁。但这时候线程 B 还没执行完,线程A实际上 删除的是线程 B加的锁。

解决方案:可以在 释放锁之前做一个判断,验证当前的锁是不是自己加的锁。

String clientId = UUID.randomUUID().toString();//解决 高并发情况下 redis分布式锁 永久失效 的问题
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
、、、、、、
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
         stringRedisTemplate.delete(lockKey);//释放锁
}

3.3 出现并发的可能性

如同上述描述:当线程 A 执行的很慢很慢,过了 30 秒都没执行完,这时候锁过期自动释放,线程 B得到了锁。此时就有多个线程在访问同步代码块
解决:我们可以让获得锁的线程同步开启一个守护线程,用来给快要过期的锁“续命”。

使用redisson:Redisson - 是一个高级的分布式协调Redis客户端。

4.完整代码如下:

package com.huawei.distributedlock;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁介绍
 */
@RestController
public class IndexController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private Redisson redisson;

    @RequestMapping("/deduct_stock")
    public String deductStock() {

        String lockKey = "product_001";
        String clientId = UUID.randomUUID().toString();//解决 高并发情况下 redis分布式锁 永久失效 的问题

        RLock redissonLock = redisson.getLock(lockKey);
        try {
            //Boolean result =
            //        stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
            //if (!result) {
            //    return "error";
            //}
            redissonLock.lock(30, TimeUnit.SECONDS);

            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣减成功,剩余库存:" + realStock + "");
            } else {
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            redissonLock.unlock();
            //if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
            //    stringRedisTemplate.delete(lockKey);//释放锁
            //}
        }
        return "end";
    }
}

视频介绍icon-default.png?t=M85Bhttps://www.bilibili.com/video/BV1d4411y79Y?p=1&vd_source=78546917c5323450fe30994834259fd3

你可能感兴趣的:(java面试详细总结,分布式,redis,数据库)