数据库
首页 > 数据库> > 微服务-分布式锁(二)-Redis方案

微服务-分布式锁(二)-Redis方案

作者:互联网

1 LUA+SETNX+EXPIRE

先用setnx来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记了释放。

1.1 使用Lua脚本(SETNX+EXPIRE)

可以使用Lua脚本来保证原子性(包含setnx和expire两条指令),加解锁代码如下:

/**
 * 使用Lua脚本,脚本中使用setnex+expire命令进行加锁操作
 */
public boolean lock(Jedis jedis, String key, String uniqueId, int seconds) {
    String luaScript = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
            		    "redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";
    Object result = jedis.eval(luaScript, Collections.singletonList(key),
            Arrays.asList(uniqueId, String.valueOf(seconds)));
    return result.equals(1L);
}

/**
 * 使用Lua脚本进行解锁操纵,解锁的时候验证value值
 */
public boolean unlock(Jedis jedis, String key, String value) {
    String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
           			    "return redis.call('del',KEYS[1]) else return 0 end";
    return jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(value)).equals(1L);
}

1.2 STW

如果在写文件过程中,发生了 FullGC,并且其时间跨度较长, 超过了锁超时的时间, 那么分布式就自动释放了。在此过程中,client2 抢到锁,写了文件。client1 的FullGC完成后,也继续写文件,注意,此时 client1 的并没有占用锁,此时写入会导致文件数据错乱,发生线程安全问题。这就是STW导致的锁过期问题。STW导致的锁过期问题,如下图所示:

STW导致的锁过期问题

STW导致的锁过期问题,大概的解决方案有:

模拟CAS乐观锁的方式-增加版本号

​ 此方案如果要实现,需要调整业务逻辑,与之配合,所以会入侵代码。

2 SET-NX-EX

方案SET key value [EX seconds] [PX milliseconds] [NX|XX]

客户端执行以上的命令:

2.1 加锁
使用redis命令 set key value NX EX max-lock-time 实现加锁。

Jedis jedis = new Jedis("127.0.0.1", 6379);
private static final String SUCCESS = "OK";

 /**
  * 加锁操作
  * @param key 锁标识
  * @param value 客户端标识
  * @param timeOut 过期时间
  */
 public Boolean lock(String key,String value,Long timeOut){
     String var1 = jedis.set(key,value,"NX","EX",timeOut);
     if(LOCK_SUCCESS.equals(var1)){
         return true;
     }
     return false;
 }

2.2 解锁
使用redis命令 EVAL 实现解锁。

Jedis jedis = new Jedis("127.0.0.1", 6379);
private static final String SUCCESS = "OK";

 /**
  * 加锁操作
  * @param key 锁标识
  * @param value 客户端标识
  * @param timeOut 过期时间
  */
 public Boolean lock(String key,String value,Long timeOut){
     String var1 = jedis.set(key,value,"NX","EX",timeOut);
     if(LOCK_SUCCESS.equals(var1)){
         return true;
     }
     return false;
 }

2.3 重试

如果在业务中去拿锁如果没有拿到是应该阻塞着一直等待还是直接返回,这个问题其实可以写一个重试机制,根据重试次数和重试时间做一个循环去拿锁,当然这个重试的次数和时间设多少合适,是需要根据自身业务去衡量的。

/**
 * 重试机制
 * @param key 锁标识
 * @param value 客户端标识
 * @param timeOut 过期时间
 * @param retry 重试次数
 * @param sleepTime 重试间隔时间
 * @return
 */
public Boolean lockRetry(String key,String value,Long timeOut,Integer retry,Long sleepTime){
    Boolean flag = false;
    try {
        for (int i=0;i<retry;i++){
            flag = lock(key,value,timeOut); 
            if(flag){
                break; 
            } 
            Thread.sleep(sleepTime); 
        } 
    }catch (Exception e){ 
        e.printStackTrace(); 
    } 
    return flag; 
}

3 Redisson

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock、联锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。

Redisson

3.1 特性功能

3.2 Watch dog
Redisson分布式锁

总体的Redisson框架的分布式锁类型大致如下:

3.3 实现方案

添加依赖

<!-- 方式一:redisson-java -->
<dependency>	
    <groupId>org.redisson</groupId>	
    <artifactId>redisson</artifactId>	
    <version>3.11.4</version>	
</dependency>

<!-- 方式二:redisson-springboot -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.11.4</version>
</dependency>

定义接口

import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;

public interface DistributedLocker {

    RLock lock(String lockKey);

    RLock lock(String lockKey, int timeout);

    RLock lock(String lockKey, TimeUnit unit, int timeout);

    boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);

    void unlock(String lockKey);

    void unlock(RLock lock);
    
}

实现分布式锁

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;

public class RedissonDistributedLocker implements DistributedLocker{

    private RedissonClient redissonClient;

    @Override
    public RLock lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }

    @Override
    public RLock lock(String lockKey, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        return lock;
    }

    @Override
    public RLock lock(String lockKey, TimeUnit unit ,int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

    @Override
    public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }

    @Override
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    @Override
    public void unlock(RLock lock) {
        lock.unlock();
    }

    public void setRedissonClient(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
    
}

3.4 高可用的RedLock(红锁)原理

RedLock算法思想是不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,n / 2 + 1,必须在大多数redis节点上都成功创建锁,才能算这个整体的RedLock加锁成功,避免说仅仅在一个redis实例上加锁而带来的问题。

更多JAVA、高并发、微服务、架构、解决方案、中间件的总结在:https://github.com/yu120/lemon-guide

标签:方案,return,String,lock,Redis,value,key,lockKey,分布式
来源: https://www.cnblogs.com/yu120/p/15057178.html