Redis分布式锁机制及实例

Redis分布式锁机制及实例

最近项目中针对业务处理需要保证处理的先后顺序,同时对业务应用系统需要多实例部署,因此在针对方法加锁上需要采用分布式锁方式进行,考虑到业务规模及场景使用,最终决定使用Redis做简单的分布式锁即可,使用开源的Redisson框架实现。

废话少说,先不看代码,先看原理

Redis分布式锁机制及实例

RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();

以上是Redisson官方给出的基本使用方法,我们在使用过程中会用到不同类型的锁,Redisson也提供了可重入锁、公平锁、联锁、红锁等等锁机制的实现。

这里我们以可重入锁作为例子来分析:

1 加锁机制

从上图可以看到一个客户端请求redis锁的时候,会通过Redisson发送一条lua脚本到redis服务,下边的脚本注释基本说明了加锁机制以及争抢机制:

if (redis.call('exists', KEYS[1]) == 0) //判断键值为anyLock的锁是否存在,0表示不存在
then
redis.call('hset', KEYS[1], ARGV[2], 1); //不存在则设置键anyLock值为客户端UUID的锁
redis.call('pexpire', KEYS[1], ARGV[1]);//设置锁过期时间,默认为30s
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) //锁anyLock已经存在,再判断是否为申请锁UUID
then //如果为同一客户端加锁则hincrby将加锁次数+1
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end; //如果为另一个客户端争抢锁,调用pptl获取加锁客户端剩余锁时间再循环请求加锁

return redis.call('pttl', KEYS[1]);

解释几个参数含义:

KEYS[1]:指的 RLock lock = redisson.getLock("anyLock"); 中的加锁key:anyLock

ARGV[1]:加锁时间,默认30秒

ARGV[2]:加锁客户端id,类似 a84ee9a6-4d15-4fdc-be06-38f5902c2367:26这样的UUID

2 看门狗自动延期机制

大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。

以上是Redisson官方给出的看门狗实现避免死锁的处理机制,意思是如果客户端所在应用服务节点出现故障,那么已经加上的锁在经过30s之后就过期自动解除,而其它的客户端可以获取到锁;而如果前者所在服务器实例没有故障,30s锁到期,此时业务逻辑还没处理完,其它实例获取到了锁,就会出现业务处理问题,因此通过看门狗每隔10s监测一次,自动对持有锁的客户端续期,保证业务处理完成后释放锁。

3 代码示例

spring-redis.xml配置:


<beans> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
">
<component-scan>

<bean>
<property>
<list>
<value>classpath:redis.properties/<value>
/<list>
/<property>
/<bean>

<bean>
<property>
<property>
<property>
<property>
/<bean>

<bean> class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property>
<property>

<property>
/<bean>

<bean>
<property>

<property>
<bean>
/<property>
<property>
<bean>
/<property>
<property>
<bean>
/<property>
<property>
<bean>
/<property>

<property>
/<bean>
<bean>
<property>
<property>
<property>
/<bean>
/<beans>

测试代码RedisLockTest :

package redis;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring-config.xml"})
public class RedisLockTest {
@Resource
RedissonClient redissonClient;
@Test
public void testRedisLock(){
CountDownLatch countDownLatch = new CountDownLatch(5);
CountDownLatch latch = new CountDownLatch(1);
for (int i=0;i<countdownlatch.getcount> Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await(); //每个线程在此等待

System.out.println("==================开始竞争锁资源================"+Thread.currentThread().getName());
RLock lock = redissonClient.getLock("lockKey");
boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (res) {
System.out.println("==================竞争锁资源成功================"+Thread.currentThread().getName());
Thread.sleep(1000);//模拟执行1S
lock.unlock();
} else {
System.out.println("==================竞争锁资源失败================"+Thread.currentThread().getName());
}
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
try {
System.out.println("==================3秒后开始竞争锁资源==================");
Thread.sleep(3000);
latch.countDown(); // 触发所有线程全部开始执行
countDownLatch.await(); // 等待线程全部执行完毕
System.out.println("==================结束==================");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/<countdownlatch.getcount>

结果输出

==================3秒后开始竞争锁资源==================
==================开始竞争锁资源================Thread-4
==================开始竞争锁资源================Thread-6
==================开始竞争锁资源================Thread-5
==================开始竞争锁资源================Thread-8
==================开始竞争锁资源================Thread-7
==================竞争锁资源成功================Thread-8

==================竞争锁资源成功================Thread-5
==================竞争锁资源成功================Thread-7
==================竞争锁资源失败================Thread-6
==================竞争锁资源失败================Thread-4
==================结束==================

可以看到有的成功了有的失败了,失败是因为在尝试加锁的3S钟时间里没有获取到锁,就放弃了。


分享到:


相關文章: