命令實現(xiàn)
首先我們看下redis普通的set命令,如下圖:
我們可以看到,set命令后面出了key和value之外,還可以選擇EX或PX還有NX和XX,這分別代表什么意思呢,我們來看一下效果。
#ex和px代表過期時間,EX單位是秒,PX則是毫秒
>set test ceshi PX 1000
>set test ceshi EX 1
#NX表示互斥,鍵不存在則設(shè)置成功
>set test ceshi NX
>(nil)
#XX則是key存在則設(shè)置成功,key不存在設(shè)置失敗
>set test 1111 XX
>ok
#那么我們可以組合使用,實現(xiàn)分布式鎖的目的
>set key value EX 5 NX
>ok
#再次設(shè)置相同key時,會返回nil
>set key value EX 5 NX
>(nil)
#那么已經(jīng)設(shè)置的key是否可以追加時間呢,答案是可以的
>expire key 5
>pexpire key 5
這里需要提醒一下,redis經(jīng)常說的分布式鎖就是上面的方式實現(xiàn)的,而redis還提供了一個setnx命令,可以設(shè)置key,但是無法設(shè)置超時時間,是不推薦使用的,面試的時候也會經(jīng)常問到,需要大家避免踩坑。
#redis提供的setnx命令
>setnx test programmer
>(integer) 1
#如果key不存在會設(shè)置成功,并返回個1
>setnx test code
>(integer) 0
代碼實現(xiàn)
了解了代碼實現(xiàn)的原理,那我們來看下代碼如何實現(xiàn)分布式鎖的,話不多說上代碼。
/**
* 獲取redis鎖
* @param lockKey redis的key,就是鎖
* @param requestId redis的value,保證唯一性,例如訂單號、手機號
* @param expireTime 過期時間,
* @return
*/
public boolean getLock(String lockKey,String requestId,int expireTime) {
// ex表示過期時間是秒,nx保證key互斥,一條set命令保證原子性,防止并發(fā)
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
if("OK".equals(result)) {
return true;
}
return false;
}
獲取到鎖之后,使用完成需要釋放鎖,當然也可以等待超時時間到期,自動釋放。這其中會涉及到一個問題,就是鎖超時釋放,但業(yè)務(wù)邏輯還未執(zhí)行完成。下一個業(yè)務(wù)進程已經(jīng)獲得了鎖,如果直接執(zhí)行del操作,會造成釋放掉了別人的鎖。那么我們就需要考慮使用lua腳本,來保證釋放鎖的并發(fā)。
/**
* 使用lua腳本釋放鎖
* @param lockKey redis的key
* @param requestId redis的value
* @return
*/
public static boolean releaseLock(String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (result.equals(1L)) {
return true;
}
return false;
}
問題
redis在單機的情況下是無法保證高可用的,在生產(chǎn)環(huán)境中我們一般會搭建redis的集群,而redis是AP模型,那么就會存在主從數(shù)據(jù)不同步,導(dǎo)致redis鎖重復(fù)獲得的問題。
當然正常情況下,在不需要保證業(yè)務(wù)數(shù)據(jù)強一致性時,就可以使用redis的分布式鎖;如果需要保證強一致性,則考慮使用其它方式,比如zookeeper等。
看門狗(Redission分布式鎖)
“看門狗”基于NIO的Netty框架實現(xiàn)的分布式鎖,多在生產(chǎn)環(huán)境中使用,接下來看代碼。
1、引入jar包
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.7.0</version>
</dependency>
2、配置Redisson
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonManager {
@Bean
public RedissonClient redisson(){
Config config = new Config();
ClusterServersConfig clusterServersConfig = config.useClusterServers()
.setScanInterval(2000) // 集群狀態(tài)掃描,時間是毫秒
.addNodeAddress("redis://127.0.0.1:6379")
.addNodeAddress("redis://127.0.0.1:6380")
.addNodeAddress("redis://127.0.0.1:6381")
.addNodeAddress("redis://127.0.0.1:6382")
.addNodeAddress("redis://127.0.0.1:6383")
.addNodeAddress("redis://127.0.0.1:6384");
return Redisson.create(config);
}
}
3、使用
public void test(){
RLock key = redissonClient.getLock("key");
key.lock();
// 業(yè)務(wù)代碼
key.unlock();
}
總結(jié)
以上便是使用redis實現(xiàn)分布式鎖的過程,基本上可以滿足大部分業(yè)務(wù)需求,也希望大家了解redis分布式鎖的優(yōu)缺點。
最后,Redisson還有很多玩法,目前是一個比較成熟的redis分布式鎖框架,奉上git連接,供大家繼續(xù)學(xué)習(xí)。
https://github.com/redisson/redisson/