分布式锁_Redis分布式锁实践
环境说明
Ubuntu: 20.04
Redis: 5.0.7
JDK: 1.8
JAVA代码开发
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.quange</groupId>
<artifactId>distributelock</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.1.4.RELEASE</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
接口类
DistributeLock.java
public interface DistributeLock {
boolean lock(String resource);
boolean unLock(String resource);
}
基于Redis的分布式锁实现类
RedisDistributeLock.java
import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.StatefulRedisConnection;
import java.util.UUID;
public class RedisDistributeLock implements DistributeLock {
// 锁释放的脚本
public static String unLockScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
// 续期Lua脚本
public static String expireScript = "if redis.call('get',KEYS[1]) == ARGV[1] then\n" +
" local t = redis.call('ttl', KEYS[1]);\n" +
" if t > -1 then\n" +
" return redis.call('expire', KEYS[1], ARGV[2])\n" +
" end;\n" +
"end;\n" +
"return 0";
ThreadLocal<String> cache = new ThreadLocal<>();
private static String unLockScriptDigest;
private static String expireScriptDigest;
public RedisDistributeLock() {
unLockScriptDigest = RedisUtil.getConnection().sync().scriptLoad(unLockScript);
expireScriptDigest = RedisUtil.getConnection().sync().scriptLoad(expireScript);
}
public boolean lock(String resource, int expireTime) {
if (expireTime < 10) {
expireTime = 30;
}
System.out.println(resource);
StatefulRedisConnection<String, String> connection = RedisUtil.getConnection();
SetArgs setArgs = new SetArgs();
setArgs.nx();
setArgs.px(expireTime * 1000L);
String value = UUID.randomUUID().toString();
String result = connection.sync().set(resource, value, setArgs);
cache.set(value);
new Thread(new WatchDog(resource, value, expireTime - 5, expireTime)).start();
return result != null;
}
@Override
public boolean lock(String resource) {
return lock(resource, 30);
}
@Override
public boolean unLock(String resource) {
boolean result = RedisUtil.getConnection().sync().evalsha(unLockScriptDigest, ScriptOutputType.BOOLEAN, new String[]{resource}, cache.get());
System.out.println(result);
cache.remove();
return result;
}
public static class WatchDog implements Runnable{
private String key;
private String value;
private int period;
private int px;
public WatchDog(String key, String value, int period, int px) {
this.key = key;
this.value = value;
this.period = period;
this.px = px;
}
@Override
public void run() {
boolean result;
do {
System.out.println("do start");
try {
Thread.sleep(period * 1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = RedisUtil.getConnection().sync().evalsha(expireScriptDigest, ScriptOutputType.BOOLEAN, new String[]{key}, value, px + "");
System.out.println("expire result = " + result);
} while(result);
}
}
}
工具类,简单的获取Redis的Connection
RedisUtil.java
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
public class RedisUtil {
public static StatefulRedisConnection<String, String> getConnection() {
RedisClient redisClient = RedisClient.create("redis://123456@localhost/0");
StatefulRedisConnection<String, String> connection = redisClient.connect();
return connection;
}
}
其他不重要类
点击查看
public enum DistributeLockType {
DB,
REDIS,
ZOOKEEPER
}
public class DistributeLockFactory {
public static DistributeLock getDistributeLock() {
return new DbDistributeLock();
}
public static DistributeLock getDistributeLock(DistributeLockType type) {
switch (type) {
case REDIS:
return new RedisDistributeLock();
case ZOOKEEPER:
return new ZkDistributeLock();
default:
return new DbDistributeLock();
}
}
}
public class Log {
public static void d(String msg) {
System.out.println(System.currentTimeMillis() + " - " + Thread.currentThread().getName() + " - " + msg );
}
}
测试类:
public class RedisDistributeLockTest {
@Test
public void testDistributeLock() {
DistributeLock distributeLock = DistributeLockFactory.getDistributeLock(DistributeLockType.REDIS);
int threadCount = 5;
CyclicBarrier cyclicBarrier = new CyclicBarrier(threadCount);
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
cyclicBarrier.await();
// Thread.sleep((j++)*1000);
String resource = "testcode22";
Log.d("to get lock: " + resource);
boolean locked = distributeLock.lock(resource);
if (locked) {
Log.d("has get lock: " + resource);
Thread.sleep(1000);
distributeLock.unLock(resource);
Log.d("release lock: " + resource);
} else {
Log.d("not get lock: " + resource);
}
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}