1.前言
1.现在互联网发展的越来的越迅速,架构也有传统的单体架构,向分布式架构发展,涉及到分布式,就难免会遇到使用分布式锁的场景,分布式锁的实现目前使用比较多的是依赖于redis,或zk的实现。
2.redis分布式锁需要注意的点。
1.这里只讨论独占锁的情况,即同一个时间段,同一个=把锁只能被一个实例获取。
2.锁用完后需要释放
3.锁需要设置超时时间,防止实例中途挂了,却没有释放锁,导致死锁的情况
4.锁只能由加锁者释放,不能释放他人的锁
5.如果锁已经超时,但是程序还未执行完,应该能给锁提前修改锁的过期时间,避免出现锁失效的情况。
3.实现
借助redis的set命令,的NX表示不存在就设置,同时set的时候设置其过期时间,操作需要保持原子性, value可以用订单号,或者uuid,一个唯一标识符,和线程绑定在一块。
释放锁,即删除key,判断当前是否是锁的拥有者,是则删除,否则删除失败。操作使用lua脚本,保证操作的原子性。
package com.redis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.io.*;
public class RedisLockUtil {
private final static Logger LOGGER = LoggerFactory.getLogger(RedisLockUtil.class);
private JedisPool jedisPool = null;
/**
* 不存在才设置
*/
public static final String NX = "NX";
/**
* 存在才设置
*/
private final String XX = "XX";
//秒
private final String EX = "EX";
//毫秒
private final String PX = "PX";
private final String SUCCESS_REPLY="OK";
public static final String LUA_DEL_KEY_IFXX="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
public RedisLockUtil(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
public JedisPool getJedisPool() {
return jedisPool;
}
public void setJedisPool(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
private Jedis getJedis(){
return jedisPool.getResource();
}
public boolean lock(byte[] key, byte[] value,long time){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String resultStr =jedis.set(key,value,NX.getBytes("utf-8"),EX.getBytes("utf-8"),time);
if(resultStr != null && resultStr.equals(SUCCESS_REPLY)){
return true;
}else{
return false;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
returnObject(jedis);
}
return false;
}
public boolean releaseLock(byte[] key,byte[] value){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
Object result = jedis.eval(LUA_DEL_KEY_IFXX.getBytes("utf-8"),1,key,value);
if(result.toString().equals("1")){
return true;
}else{
return false;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
returnObject(jedis);
}
return false;
}
}
测试
import com.redis.JedisUtil;
import com.redis.RedisLockUtil;
import org.junit.Test;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class LockTest {
@Test
public void distributeLockTest() throws IOException {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(1000);
config.setMaxIdle(10);
config.setMaxWaitMillis(10000);
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
JedisPool jedisPool = new JedisPool(config,"127.0.0.1",6379);
RedisLockUtil redisLockUtil = new RedisLockUtil(jedisPool);
redisLockUtil.setJedisPool(jedisPool);
String distributeTestKey = "dis_test_key";
Thread t = new Thread(new ConsumerLockTest(redisLockUtil,distributeTestKey));
t.setName("thread 1");
t.start();
while (true){
}
}
@Test
public void distributeLockTest2() throws IOException {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(1000);
config.setMaxIdle(10);
config.setMaxWaitMillis(10000);
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
JedisPool jedisPool = new JedisPool(config,"127.0.0.1",6379);
RedisLockUtil redisLockUtil = new RedisLockUtil(jedisPool);
redisLockUtil.setJedisPool(jedisPool);
String distributeTestKey = "dis_test_key";
Thread t = new Thread(new ConsumerLockTest(redisLockUtil,distributeTestKey));
t.setName("thread 2");
t.start();
while (true){
}
}
@Test
public void distributeLockTest3() throws IOException {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(1000);
config.setMaxIdle(10);
config.setMaxWaitMillis(10000);
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
JedisPool jedisPool = new JedisPool(config,"127.0.0.1",6379);
RedisLockUtil redisLockUtil = new RedisLockUtil(jedisPool);
redisLockUtil.setJedisPool(jedisPool);
String distributeTestKey = "dis_test_key";
Thread t = new Thread(new ConsumerLockTest(redisLockUtil,distributeTestKey));
t.setName("thread 3");
t.start();
while (true){
}
}
public class ConsumerLockTest implements Runnable{
private RedisLockUtil redisLockUtil;
private String distributeTestKey;
public ConsumerLockTest(RedisLockUtil redisLockUtil,String distributeTestKey) {
this.redisLockUtil = redisLockUtil;
this.distributeTestKey = distributeTestKey;
}
@Override
public void run() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
while(!redisLockUtil.lock(distributeTestKey.getBytes(),
String.valueOf(Thread.currentThread().getName()).getBytes(),28000)){
}
System.out.println(format.format(new Date())+","+Thread.currentThread().getName()+":获取了锁");
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
boolean result =redisLockUtil.releaseLock(distributeTestKey.getBytes(),
String.valueOf(Thread.currentThread().getName()).getBytes());
if(result){
System.out.println(format.format(new Date())+","+Thread.currentThread().getName()+":成功释放了锁");
}else{
System.out.println(format.format(new Date())+","+Thread.currentThread().getName()+":释放锁失败");
}
}
}
}
}
同时运行3个junit方法即可,如上并未考虑到锁超时,但线程并未执行完毕的情况,可以开启一个线程,检测锁快要超时了,为其重新设置过期时间,类似的已有框架redisson实现了高性能的分布式锁。