Redis分布式锁 Redis怎样实现分布式锁详解

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Redis分布式锁 Redis怎样实现分布式锁详解

kusedexingfu   2021-06-01 我要评论
想了解Redis怎样实现分布式锁详解的相关内容吗,kusedexingfu在本文为您仔细讲解Redis分布式锁的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:Redis分布式锁实现原理,Redis实现分布式锁,下面大家一起来学习吧。

一、前言

在Java的并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题。通常,我们以synchronized 、Lock来使用它。

但是Java中的锁,只能保证在同一个JVM进程内中执行。如果在分布式集群环境下,就需要分布式锁了。

通常的分布式锁的实现方式有redis,zookeeper,但是一般我们的程序中都会用到redis,用redis做分布式锁,也能够降低成本。

二、实现原理

2.1 加锁

加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。

在Redis 2.6.12以及之前,可以通过setnx key value (key不存在才设置成功)设置值,通过expire key seconds设置过期时间。但是由于是两个命令,不是原子的。如果在设置值之后还没有来得及设置过期时间,程序挂掉了,那么这个key就永远的存在redis中了。

在Redis 2.6.12之后,redis提供了set key value EX seconds NX 和set key value PX millisecond NX  来原子性的设置值和设置过期时间。

2.2 解锁

解锁的过程就是将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉,在删除前需要判断是不是设置的value,如果是才删除。

为了保证解锁操作的原子性,我们用LUA脚本完成这一操作。先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功。LUA脚本如下:

if redis.call('get',KEYS[1]) == ARGV[1] then
   return redis.call('del',KEYS[1])
else
   return 0
end

三、通过RedisTemplate实现分布式锁

我们通过SpringBoot构建的应用程序一般都是用RedisTemplate来操作缓存redis。下面介绍基于RedisTemplate来实现分布式锁。

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
 
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
 
@Service
@Slf4j
public class RedisLockUtil {
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    //获取锁超时时间
    private long timeout = 60000;
 
    /**
     * 获取锁
     * @param key
     * @param value
     * @param ms
     * @return
     */
    public Boolean getLock(String key, String value, Long ms) {
        long startTime = System.currentTimeMillis();
        while (true) {
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, ms, TimeUnit.MILLISECONDS);
            if (flag) {
                return true;
            }
 
            //避免一直无限获取锁
            if (System.currentTimeMillis() - startTime > timeout) {
                return false;
            }
 
            try {
                log.info("{}重试锁", key);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
    /**
     * 解锁
     * @param key
     * @param value
     * @return
     */
    public Boolean unLock(String key, String value) {
        String script =
                "if redis.call('get',KEYS[1]) == ARGV[1] then" +
                        "   return redis.call('del',KEYS[1]) " +
                        "else" +
                        "   return 0 " +
                        "end";
 
        // 构造RedisScript并指定返回类类型
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        // 参数一:redisScript,参数二:key列表,参数三:arg(可多个)
        Object result = redisTemplate.execute(redisScript, Arrays.asList(key), value);
 
        return "1".equals(result.toString());
    }
}

四、通过Redisson实现

Redisson为我们封装了细节,可以开箱即用。

引入maven依赖:

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.14.0</version>
</dependency>

配置类:

import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * redisson 配置类
 */
@Configuration
@Data
public class RedissonConfig {
 
    @Value("${spring.redis.host}")
    private String host;
 
    @Value("${spring.redis.port}")
    private String port;
 
    @Value("${spring.redis.password}")
    private String password;
 
    @Bean
    public RedissonClient getRedisson(){
 
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
        //添加主从配置
//        config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
 
        return Redisson.create(config);
    }
}

redis属性配置:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: root

封装的加锁解锁工具类:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.util.concurrent.TimeUnit;
 
/**
 * redis分布式锁帮助类
 */
@Component
public class RedissLockUtil {
 
    @Autowired
    private RedissonClient redissonClient;
    
 
    /**
     * 加锁
     * @param lockKey
     * @return
     */
    public RLock lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }
 
    /**
     * 释放锁
     * @param lockKey
     */
    public  void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }
    
    /**
     * 释放锁
     * @param lock
     */
    public void unlock(RLock lock) {
        lock.unlock();
    }
 
    /**
     * 带超时的锁
     * @param lockKey
     * @param timeout 超时时间   单位:秒
     */
    public RLock lock(String lockKey, int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, TimeUnit.SECONDS);
        return lock;
    }
    
    /**
     * 带超时的锁
     * @param lockKey
     * @param unit 时间单位
     * @param timeout 超时时间
     */
    public RLock lock(String lockKey, TimeUnit unit , int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }
    
    /**
     * 尝试获取锁
     * @param lockKey
     * @param waitTime 最多等待时间
     * @param leaseTime 上锁后自动释放锁时间
     * @return
     */
    public boolean tryLock(String lockKey, int waitTime, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            return false;
        }
    }
    
    /**
     * 尝试获取锁
     * @param lockKey
     * @param unit 时间单位
     * @param waitTime 最多等待时间
     * @param leaseTime 上锁后自动释放锁时间
     * @return
     */
    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;
        }
    }
}

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们