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;
/**
* 端口號(hào)
*/
@Value("${spring.jedis.port}")
private int port;
/**
* 密碼
*/
@Value("${spring.jedis.password}")
private String password;
/**
* 連接超時(shí)時(shí)間
*/
@Value("${spring.jedis.timeOut}")
private int timeOut;
/**
* 最大連接數(shù)
*/
@Value("${spring.jedis.maxConnect}")
private int maxConnect;
/**
* 等待獲取連接最大時(shí)間
*/
@Value("${spring.jedis.maxWaitTime}")
private long maxWaitTime;
/**
* 最大空閑連接數(shù)
*/
@Value("${spring.jedis.maxFreeConnect}")
private int maxFreeConnect;
/**
* 最小空閑連接數(shù)
*/
@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;
}
實(shí)現(xiàn)類
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();
}
}
這種實(shí)現(xiàn)方式還有一種弊端,比如說(shuō)當(dāng)實(shí)現(xiàn)類的 jedis.expire(redisKey,timeOut);執(zhí)行異常,但是該線程已經(jīng)獲取了鎖,沒(méi)有添加過(guò)期時(shí)間,如果程序一旦發(fā)生異常,就會(huì)導(dǎo)致死鎖現(xiàn)象。所以要保證事務(wù)的原子性。解決方法可以使用以下方法
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);*/
//使用該方法保證事務(wù)的原子性
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);
}
}
}
當(dāng)大家讀到這里的時(shí)候其實(shí)還有一個(gè)問(wèn)題的存在,例如當(dāng)前線程還沒(méi)有執(zhí)行完畢,但是key的時(shí)間已經(jīng)過(guò)期,導(dǎo)致當(dāng)前線程獲取到的鎖被釋放。解決辦法就是在當(dāng)前線程中開啟一個(gè)子線程,并使用定時(shí)任務(wù)每隔一段實(shí)現(xiàn)重置一下當(dāng)前線程的過(guò)期時(shí)間。代碼如下
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);*/
//使用該方法保證事務(wù)的原子性
jedis.setex(redisKey,timeOut,uuid);
//創(chuàng)建一個(gè)子線程,來(lái)重置過(guò)期實(shí)現(xiàn),在正式開發(fā)中建議使用Executors或者ThreadPoolExecutor線程池創(chuàng)建線程
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);
}
}
/**
* 定時(shí)任務(wù)
*/
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);
}
}