分布式锁需要解决的问题

1.互斥性:任一时刻是有一个客户端获取锁,不能两个客户端获取到锁
    2.安全性:锁只能被持有该客户端的删除,不能由其他客户端删除
    3.死锁:一个客户端获取到锁,导致宕机,而其他客户端无法获取到资源
    4.容错:一些节点宕机,客户端任然能获取锁和释放锁

分布式锁思路
基于Redis实现的分布式锁,Redis单机部署的场景
(存在问题是如果处理时间长,锁自动失效可能会出现问题)

加锁

public static boolean rightGetLock(Jedis jedis, String lockKey, String requestId,
 Integer expireTime) {
    //传requestId的原因:这样可以知道这把锁是哪个请求加的,在解锁的时候就有依据,只能解锁自己加的锁,
    requestId可以使用UUID.randomUUID().toString()方法生成
    String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, 
        SET_WITH_EXPIRE_TIME, expireTime);
    if (LOCK_SUCCESS.equals(result)) {
        return true;
    }
    return false;
   }
   
String set(String key, String value, String nxxx, String expx, long time)该方法是: 存储数据到缓存中,
并制定过期时间和当Key存在时是否覆盖。
nxxx: 只能取NX或者XX,如果取NX,则只有当key不存在是才进行set,如果取XX,则只有当key已经存在时才进行set
expx: 只能取EX或者PX,代表数据过期时间的单位,EX代表秒,PX代表毫秒。
time: 过期时间,单位是expx所代表的单位。

第一个为key
第二个为value,这里传的是requestId;它的意义在于可以区分这把锁是哪个请求加的
第三个为nxxx,这里传的是NX
第四个为expx,这里传的是PX,意思是给key加一个过期设置,具体时间由第5个参数决定
第五个为time,与第四个参数相呼应,代表key的过期时间
      上面这种实现方式满足了可靠性里描述的三个条件:
首先,set加入NX参数可以保证如果key已存在则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性
其次,对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间自动解锁,不会发
生死锁
将value赋值为requestId,代表加锁的客户端请求标识,在客户端解锁的时候就可以校验是否是同一个客户端

解锁

public static boolean rightReleaseLock(Jedis jedis, String lockKey, String requestId) {
    String script = "if redis.call('get', KEY[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    if (RELEASE_SUCCESS.equals(result)) {
        return true;
    }
    return false;
   }
   
   解锁只需要两行代码就搞定了,该方式可以确保上述操作是原子性的
    一个简单的Lua脚本代码,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁
    第二行是将Lua代码传到jedis.eval的方法里,并使参数KEY[1]赋值为lockKey,ARGV[1]
    赋值为requestId,然后交给Redis服务端执行
    执行eval方法可以确保原子性,源于Redis的特性,官网对eval命令的部分解释如下:
    eval命令执行Lua代码将被当成一个命令去执行,直到eval命令执行完成Redis才会去执行其他命令

多机部署可以尝试使用Redisson实现
//工具类可以先忽略

@Configuration
@EnableConfigurationProperties(RedisModel.class)
public class RedissonConfig {

    private final RedisModel redisModel;
    
    public RedissonConfig(RedisModel redisModel) {
        this.redisModel = redisModel;
    }

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        String [] nodes = redisModel.getSentinel().getNodes().split(",");
        List newNodes = new ArrayList<>(nodes.length);
        newNodes.addAll(Arrays.asList(nodes));
        SentinelServersConfig serverConfig = config.useSentinelServers()
                .addSentinelAddress(newNodes.toArray(new String[0]))
                .setMasterName(redisModel.getSentinel().getMaster())
                .setReadMode(ReadMode.SLAVE)
                .setTimeout(redisModel.getTimeout());
        // 设置密码
        if(StringUtils.isNotBlank(redisModel.getPassword())){
            serverConfig.setPassword(redisModel.getPassword());
        }
        // 设置database
        if (redisModel.getDatabase()!=0){
            serverConfig.setDatabase(redisModel.getDatabase());
        }
        return Redisson.create(config);
    }
}

具体参考如下:

Config config1 = new Config();
config1.useSingleServer().setAddress("redis://192.168.0.1:5378")
        .setPassword("a123456").setDatabase(0);
RedissonClient redissonClient1 = Redisson.create(config1);

Config config2 = new Config();
config2.useSingleServer().setAddress("redis://192.168.0.1:5379")
        .setPassword("a123456").setDatabase(0);
RedissonClient redissonClient2 = Redisson.create(config2);

Config config3 = new Config();
config3.useSingleServer().setAddress("redis://192.168.0.1:5380")
        .setPassword("a123456").setDatabase(0);
RedissonClient redissonClient3 = Redisson.create(config3);

String resourceName = "REDLOCK_KEY";

RLock lock1 = redissonClient1.getLock(resourceName);
RLock lock2 = redissonClient2.getLock(resourceName);
RLock lock3 = redissonClient3.getLock(resourceName);
// 向3个redis实例尝试加锁
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
boolean isLock;
try {
    // isLock = redLock.tryLock();
    // 500ms拿不到锁, 就认为获取锁失败。10000ms即10s是锁失效时间。
    isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
    System.out.println("isLock = "+isLock);
    if (isLock) {
        //TODO if get lock success, do something;
    }
} catch (Exception e) {
} finally {
    // 无论如何, 最后都要解锁
    redLock.unlock();
}```