redis分布式锁
本文主要以Jedis客户端为例
pom.xml文件
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
连接池配置类
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);
}
}