分布式锁系列文章

分布式锁(1) ----- 介绍和基于数据库的分布式锁

分布式锁(2) ----- 基于redis的分布式锁

分布式锁(3) ----- 基于zookeeper的分布式锁

代码:https://github.com/shuo123/distributeLock

Redis单机版实现

set和lua实现

获取锁

SET resource_name my_random_value NX PX 30000

NX key不存在时才set

PX 设置过期时间

my_random_value 要保证每台客户端的每个锁请求唯一,可以使用UUID+ThreadID

该命令在Redis 2.6.12才有,网上有基于setnx、epire的实现和基于setnx、get、getset的实现,这些多多少少都有点瑕疵,大概率是旧版本的redis实现,建议高版本的redis还是使用这个实现。

释放锁

if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end

通过上述lua脚本实现,先判断锁是否该请求拥有,防止误解锁。

java代码

public boolean tryLock(long waitTime, long leaseTime) {
long end = Calendar.getInstance().getTimeInMillis() + waitTime;
Jedis jedis = jedisPool.getResource();
try {
do {
String result = jedis.set(lockName, getClientId(), "NX", "EX", leaseTime);
if ("OK".equals(result)) {
return true;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return false;
}
} while (Calendar.getInstance().getTimeInMillis() < end);
}finally {
if(jedis != null) {
jedis.close();
}
}
return false;
} public boolean unlock() {
String lua = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
Jedis jedis = jedisPool.getResource();
try {
Object obj = jedis.eval(lua, Collections.singletonList(lockName), Collections.singletonList(getClientId()));
if (obj.equals(1)) {
return true;
}
}finally {
if(jedis != null){
jedis.close();
}
}
return false;
}

测试代码

public void lockTest() {
//用线程模拟进程
ExecutorService threadPool = Executors.newFixedThreadPool(20);
CyclicBarrier barrier = new CyclicBarrier(20);
for (int i = 0; i < 20; i++) {
threadPool.execute(new RedisLockTest(barrier));
}
threadPool.shutdown();
while (!threadPool.isTerminated()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class RedisLockTest implements Runnable{ private CyclicBarrier barrier;
RedisLockTest(CyclicBarrier barrier){
this.barrier = barrier;
} @Override
public void run() {
//模拟一台客户端一个jedisPool
JedisPool jedisPool = new JedisPool("192.168.9.150", 6379);
try {
DistributeLock lock = new SingletonRedisLock(jedisPool, "lock");
barrier.await();
boolean flag = lock.tryLock(Integer.MAX_VALUE, 300);
try {
System.out.println(Thread.currentThread().getId() + "get lock:flag=" + flag);
Thread.sleep(100);
System.out.println(Thread.currentThread().getId() + "get unlock");
} finally {
lock.unlock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedisPool.close();
}
}
}

存在的问题

该实现简单,但存在以下问题:

1.没有实现可重入

2.没有锁续租,如果代码在锁的租期内没有执行完成,那么锁过期会导致另一个客户端获取锁

Redisson实现

Redisson是redis推荐的分布式锁实现开源项目。Redisson的分布式锁实现可重入,同时有LockWatchDogTimeout来实现锁续约

github:https://github.com/redisson/redisson

private static class RedissonLockTest implements Runnable{

    private CyclicBarrier barrier;
RedissonLockTest(CyclicBarrier barrier){
this.barrier = barrier;
} @Override
public void run() {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.9.150:6379");
RedissonClient client = Redisson.create(config);
try {
RLock lock = client.getLock("lock");
barrier.await();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "get lock");
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "get unlock");
} finally {
lock.unlock();
}
}catch (Exception e){
e.printStackTrace();
}finally {
client.shutdown();
}
}
}

Redis多实例版实现

单机实现在多实例下问题

如果Redis实现了集群,由于主从之间时通过异步复制的,假设客户端A在主机上获得锁,这时在未将锁数据复制到从机时,主机挂了,从机切换为主机,那么从机没有这条数据,客户端B同样可以获得锁。

RedLock算法

使用多个独立(非集群)的实例来实现分布式锁,由于实例独立不需复制同步,所以没有上述问题;而保证可用性的是靠数据冗余,将数据多存放几份在不同的实例上。算法如下:

  1. 使用相同的key和value从N个实例上获取锁;
  2. 当从大于(N/2+1)个实例获取锁的时间<锁的过期时间,才获取锁成功
  3. 如果获取失败,解锁所有实例

Redisson实现

public void redLockTest() {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.9.150:7000");
RedissonClient client = Redisson.create(config);
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://192.168.9.150:7001");
RedissonClient client1 = Redisson.create(config1);
Config config2 = new Config();
config2.useSingleServer().setAddress("redis://192.168.9.150:7002");
RedissonClient client2 = Redisson.create(config2);
try{
RLock lock = client.getLock("lock");
RLock lock1 = client1.getLock("lock");
RLock lock2 = client2.getLock("lock");
RedissonRedLock redLock = new RedissonRedLock(lock, lock1, lock2);
redLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "get lock");
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "get unlock");
} finally {
redLock.unlock();
}
}catch (Exception e){
e.printStackTrace();
}finally {
client.shutdown();
client1.shutdown();
client2.shutdown();
}
}

参考资料

https://redis.io/topics/distlock

https://github.com/redisson/redisson/wiki

05-11 22:51