JAVA基于redis实现的分布式锁

redis分布式锁

本文主要以Jedis客户端为例

pom.xml文件

        
            redis.clients
            jedis
            2.9.0
        

连接池配置类

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @author oumiga
 */
@Configuration
public class JedisConfig {

    /**
     * 地址
     */
    @Value("${spring.jedis.host}")
    private String host;

    /**
     * 端口号
     */
    @Value("${spring.jedis.port}")
    private int port;

    /**
     * 密码
     */
    @Value("${spring.jedis.password}")
    private String password;

    /**
     * 连接超时时间
     */
    @Value("${spring.jedis.timeOut}")
    private int timeOut;

    /**
     * 最大连接数
     */
    @Value("${spring.jedis.maxConnect}")
    private int maxConnect;

    /**
     * 等待获取连接最大时间
     */
    @Value("${spring.jedis.maxWaitTime}")
    private long maxWaitTime;

    /**
     * 最大空闲连接数
     */
    @Value("${spring.jedis.maxFreeConnect}")
    private int maxFreeConnect;

    /**
     * 最小空闲连接数
     */
    @Value("${spring.jedis.minFreeConnect}")
    private int minFreeConnect;

    @Bean
    public JedisPool getJedisPoolFactory(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setBlockWhenExhausted(true);
        config.setMaxIdle(maxFreeConnect);
        config.setMinIdle(minFreeConnect);
        config.setMaxWaitMillis(maxWaitTime);
        config.setMaxTotal(maxConnect);
        JedisPool jedisPool = new JedisPool(config,host,port,timeOut,password);
        return jedisPool;
    }
}

接口

/**
 * @author oumiga
 */
public interface RedisLock{

    /**
     * 获取锁
     * @throws Exception
     */
    public void lock() throws Exception;

    /**
     * 释放锁
     * @throws Exception
     */
    public void closeLock() throws Exception;
}

实现类

import com.lpl.config.JedisConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.UUID;

/**
 * @author oumiga
 */
public class RedisLockImpl implements RedisLock {

    @Value("spring.jedis.redisKey")
    private String redisKey;

    @Value("spring.jedis.redisLockTimeOut")
    private int timeOut;

    @Autowired
    private JedisPool jedisPool;

    private String uuid = UUID.randomUUID().toString();

    @Override
    public void lock() throws Exception{
        Jedis jedis = jedisPool.getResource();
        jedis.setnx(redisKey,uuid);
        jedis.expire(redisKey,timeOut);
    }

    @Override
    public void closeLock() throws Exception{
        Jedis jedis = jedisPool.getResource();
        String value = jedis.get(redisKey);
         if(uuid.equals(value)){
             jedis.del(redisKey);
         }
    }
}

工具类

/**
 * @author oumiga
 */
public class RedisLockUtils {

    private static RedisLock redisLock = new RedisLockImpl();

    private static void lock() throws Exception{
        redisLock.lock();
    }

    private static void closeLock() throws Exception{
        redisLock.closeLock();
    }
}

这种实现方式还有一种弊端,比如说当实现类的 jedis.expire(redisKey,timeOut);执行异常,但是该线程已经获取了锁,没有添加过期时间,如果程序一旦发生异常,就会导致死锁现象。所以要保证事务的原子性。解决方法可以使用以下方法

import com.lpl.config.JedisConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.UUID;

/**
 * @author oumiga
 */
public class RedisLockImpl implements RedisLock {

    @Value("spring.jedis.redisKey")
    private String redisKey;

    @Value("spring.jedis.redisLockTimeOut")
    private int timeOut;

    @Autowired
    private JedisPool jedisPool;

    private String uuid = UUID.randomUUID().toString();

    @Override
    public void lock() throws Exception{
        Jedis jedis = jedisPool.getResource();
        /*jedis.setnx(redisKey,uuid);
        jedis.expire(redisKey,timeOut);*/

        //使用该方法保证事务的原子性
        jedis.setex(redisKey,timeOut,uuid);
    }

    @Override
    public void closeLock() throws Exception{
        Jedis jedis = jedisPool.getResource();
        String value = jedis.get(redisKey);
        if(uuid.equals(value)){
            jedis.del(redisKey);
        }
    }
}

当大家读到这里的时候其实还有一个问题的存在,例如当前线程还没有执行完毕,但是key的时间已经过期,导致当前线程获取到的锁被释放。解决办法就是在当前线程中开启一个子线程,并使用定时任务每隔一段实现重置一下当前线程的过期时间。代码如下

import com.lpl.config.JedisConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

/**
 * @author oumiga
 */
public class RedisLockImpl implements RedisLock {

    @Value("spring.jedis.redisKey")
    private String redisKey;

    @Value("spring.jedis.redisLockTimeOut")
    private int timeOut;

    @Autowired
    private JedisPool jedisPool;

    private String uuid = UUID.randomUUID().toString();

    private Thread thread;

    @Override
    public void lock() throws Exception{
        Jedis jedis = jedisPool.getResource();
        /*jedis.setnx(redisKey,uuid);
        jedis.expire(redisKey,timeOut);*/

        //使用该方法保证事务的原子性
        jedis.setex(redisKey,timeOut,uuid);

        //创建一个子线程,来重置过期实现,在正式开发中建议使用Executors或者ThreadPoolExecutor线程池创建线程
        thread = new Thread(()->{
            startTimeTask();
        });
        thread.start();
    }

    @Override
    public void closeLock() throws Exception{
        Jedis jedis = jedisPool.getResource();
        String value = jedis.get(redisKey);
        if(uuid.equals(value)){
            thread.interrupt();
            jedis.del(redisKey);
        }
    }

    /**
     * 定时任务
     */
    public void startTimeTask(){
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                Jedis jedis = jedisPool.getResource();
                jedis.expire(redisKey,timeOut);
            }
        },(timeOut - 1) * 1000);
    }
}

你可能感兴趣的:(JAVA基于redis实现的分布式锁)