在分布式系统的开发中,分布式锁的开发,一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁
本文讲解如何通过Redis来实现分布式锁的开发。在数据库中,我们可以利用数据库自身的锁机制;在ZooKeeper中,我们可以利用ZooKeeper节点的有序性/唯一性来实现;那么在Redis中,分布式锁该如何实现呢?
在Redis中,为我们提供了一个setnx指令。我们可以通过setnx指令,来完成分布式锁的开发。我们可以先来简单的了解一下setnx指令的用途,如下图:
在实现分布式锁的实现之前,我们先来了解一下锁的特性:
①能够获得锁,也能够释放锁;
②锁必须设置有超时时间,避免发生死锁现象
③释放锁的时候,需要判断当前释放锁的线程是不是已经获取到的锁的线程
Redis分布式锁的实现
1.获取一个Redis连接
/**
* 获取Redis连接
*/
public class JedisConnectionUtils {
private static JedisPool pool = null;
/**
* 静态块初始化
*/
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//设置最大连接数(也可以说是一种限流机制)
jedisPoolConfig.setMaxTotal(10);
pool = new JedisPool(jedisPoolConfig,"192.168.204.201",6379);
}
/**
* 通过getJedis()方法,从连接池中获取一个Redis连接
* @return
*/
public static Jedis getJedis(){
return pool.getResource();
}
}
2.获得锁/释放锁方法
遇到的问题说明:
①设置过期时间:设置过期时间,是防止拿到锁后睡着了啥也不干,其他人都得等着的情况
②上锁的同时,设置过期时间:防止上锁后突然服务器断电,导致永远不释放锁的问题
public class JedisDistributedLock {
/**
* 获得锁
*
* @param lockName 锁的名称
* @param acquireTimeout 获得锁的超时时间
* @param lockTimeout 锁本身的过期时间
* @return
*/
public String acquireLock(String lockName,long acquireTimeout,long lockTimeout){
String identifier = UUID.randomUUID().toString();//保证释放锁的时候,是同一个持有锁的人
String lockKey = "lock:"+lockName;
//超时时间
int lockExpire = (int) (lockTimeout / 1000);
Jedis jedis = null;
try {
//获取Redis连接
jedis = JedisConnectionUtils.getJedis();
long end = System.currentTimeMillis() + acquireTimeout;
//获取锁的限定时间
while(System.currentTimeMillis() < end){
if(jedis.setnx(lockKey,identifier) == 1){//设置值成功
// 设置超时时间(最好在setnx上锁时同时设置超时时间,避免刚上锁还没设置草是时间就挂掉,毕竟不是一个原子操作)
jedis.expire(lockKey,lockExpire);
return identifier;//获得锁成功
}
try{
//等待片刻后进行获取锁的重试
Thread.sleep(100);
} catch (InterruptedException e){
e.printStackTrace();
}
}
} catch (Exception e){
e.printStackTrace();
} finally {
jedis.close();
}
return null;
}
/**
* 释放锁
* @param lockName 锁的名称
* @param identifier
* @return
*/
public boolean releaseLock(String lockName,String identifier){
System.out.println(System.currentTimeMillis() + "--" + lockName + "开始释放锁:"+identifier);
String lockKey = "lock:"+lockName;
Jedis jedis = null;
boolean isRelease = false;
try{
jedis = JedisConnectionUtils.getJedis();
while(true){
//通过监听,来保证当前释放锁的线程是不是已经获得锁的线程
jedis.watch(lockKey);
//判断是否为同一把锁
if(identifier.equals(jedis.get(lockKey))){
//通过事务,来完成锁的释放
Transaction transaction = jedis.multi();
transaction.del(lockKey);
if(transaction.exec().isEmpty()){
continue;
}
isRelease = true;
}
jedis.unwatch();
break;
}
} catch (Exception e){
e.printStackTrace();
} finally {
jedis.close();
}
return isRelease;
}
}
3.测试
public class JedisDistributedLockTest implements Runnable {
@Override
public void run() {
while(true){
JedisDistributedLock distributedLock = new JedisDistributedLock();
String rs = distributedLock.acquireLock("updateOrder", 2000, 5000);
if(rs != null){
System.out.println(System.currentTimeMillis() + "--" + Thread.currentThread().getName() + "-> 成功获得锁:"+rs);
try {
Thread.sleep(1000);
distributedLock.releaseLock("updateOrder",rs);
} catch (InterruptedException e){
e.printStackTrace();
}
break;
}
}
}
public static void main(String[] args) {
JedisDistributedLockTest test = new JedisDistributedLockTest();
//开启10个线程
for (int i = 0; i < 10 ; i++) {
new Thread(test,"tName:"+i).start();
}
}
}
测试结果:
1566185177722--tName:2-> 成功获得锁:e5d88639-d266-4e45-acab-d2ef1c19f30f
1566185179222--updateOrder开始释放锁:e5d88639-d266-4e45-acab-d2ef1c19f30f
1566185179259--tName:7-> 成功获得锁:2e1a9b77-2e69-430d-8b15-ade320f911da
1566185180759--updateOrder开始释放锁:2e1a9b77-2e69-430d-8b15-ade320f911da
1566185180835--tName:6-> 成功获得锁:25bb2c0d-b64c-486f-9ebd-eb718c821953
1566185182336--updateOrder开始释放锁:25bb2c0d-b64c-486f-9ebd-eb718c821953
1566185182338--tName:8-> 成功获得锁:0c1c4f54-e2ca-4d98-ba8c-b0f8cddb8492
1566185183838--updateOrder开始释放锁:0c1c4f54-e2ca-4d98-ba8c-b0f8cddb8492
1566185183937--tName:9-> 成功获得锁:1318b8fd-dc09-454b-b44a-90f9c3999147
1566185185437--updateOrder开始释放锁:1318b8fd-dc09-454b-b44a-90f9c3999147
1566185185439--tName:5-> 成功获得锁:bdb8853b-c9ca-4138-8895-96eb08a4445f
1566185186939--updateOrder开始释放锁:bdb8853b-c9ca-4138-8895-96eb08a4445f
1566185186941--tName:3-> 成功获得锁:3b4ab6b6-7d6c-4821-a744-029f6b4c2a76
1566185188441--updateOrder开始释放锁:3b4ab6b6-7d6c-4821-a744-029f6b4c2a76
1566185188442--tName:4-> 成功获得锁:a6a14f8c-6b93-4b86-bfa4-40cd099a22ae
1566185189942--updateOrder开始释放锁:a6a14f8c-6b93-4b86-bfa4-40cd099a22ae
1566185190041--tName:1-> 成功获得锁:586e6de3-eb4f-4384-9010-357658cea5b0
1566185191541--updateOrder开始释放锁:586e6de3-eb4f-4384-9010-357658cea5b0
1566185191542--tName:0-> 成功获得锁:56d14bc0-ee90-47ba-836e-f27d8333e8b2
1566185193042--updateOrder开始释放锁:56d14bc0-ee90-47ba-836e-f27d8333e8b2
总结
如果你的项目中Redis是多机部署的,那么你可以尝试使用Redission实现分布式锁。Redission不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务相对高级的功能。Redis官方推荐我们使用Redission来完成对Redis的相关操作。如需了解Redission的使用,请参考:Redission中文使用文档
END