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鍾時間裡沒有獲取到鎖,就放棄了。


分享到:


相關文章: