Redis超詳細(xì)分析分布式鎖
分布式鎖
為了保證一個方法在高并發(fā)情況下的同一時間只能被同一個線程執(zhí)行,在傳統(tǒng)單體應(yīng)用單機(jī)部署的情況下,可以使用Java并發(fā)處理相關(guān)的API(如ReentrantLcok或synchronized)進(jìn)行互斥控制。但是,隨著業(yè)務(wù)發(fā)展的需要,原單體單機(jī)部署的系統(tǒng)被演化成分布式系統(tǒng)后,由于分布式系統(tǒng)多線程、多進(jìn)程并且分布在不同機(jī)器上,這將使原單機(jī)部署情況下的并發(fā)控制鎖策略失效,為了解決這個問題就需要一種跨JVM的互斥機(jī)制來控制共享資源的訪問,這就是分布式鎖要解決的問題。
應(yīng)用場景
1、處理效率提升:應(yīng)用分布式鎖,可以減少重復(fù)任務(wù)的執(zhí)行,避免資源處理效率的浪費(fèi);
2、數(shù)據(jù)準(zhǔn)確性保障:使用分布式鎖可以放在數(shù)據(jù)資源的并發(fā)訪問,避免數(shù)據(jù)不一致情況,甚至數(shù)據(jù)損失等。
例如:
分布式任務(wù)調(diào)度平臺保證任務(wù)的冪等性。
分布式全局id的生成
使用Redis 實(shí)現(xiàn)分布式鎖
思路:Redis實(shí)現(xiàn)分布式鎖基于SetNx命令,因?yàn)樵赗edis中key是保證是唯一的。所以當(dāng)多個線程同時的創(chuàng)建setNx時,只要誰能夠創(chuàng)建成功誰就能夠獲取到鎖。
Set 命令: 每次 set 時,可以修改原來舊值;
SetNx命令:每次SetNx檢查該 key是否已經(jīng)存在,如果已經(jīng)存在的話不會執(zhí)行任何操作。返回為0 如果已經(jīng)不存在的話直接新增該key。
1:新增key成功, 0:失敗
獲取鎖的時候:當(dāng)多個線程同時創(chuàng)建SetNx k,只要誰能夠創(chuàng)建成功誰就能夠獲取到鎖。
釋放鎖:可以對該key設(shè)置一個有效期可以避免死鎖的現(xiàn)象。
單機(jī)版Redis實(shí)現(xiàn)分布式鎖
使用原生Jedis實(shí)現(xiàn)
1、增加maven依賴
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
2、編寫Jedis連接Redis工具類
public class RedisClientUtil {
//protected static Logger logger = Logger.getLogger(RedisUtil.class);
private static String IP = "www.kaicostudy.com";
//Redis的端口號
private static int PORT = 6379;
//可用連接實(shí)例的最大數(shù)目,默認(rèn)值為8;
//如果賦值為-1,則表示不限制;如果pool已經(jīng)分配了maxActive個jedis實(shí)例,則此時pool的狀態(tài)為exhausted(耗盡)。
private static int MAX_ACTIVE = 100;
//控制一個pool最多有多少個狀態(tài)為idle(空閑的)的jedis實(shí)例,默認(rèn)值也是8。
private static int MAX_IDLE = 20;
//等待可用連接的最大時間,單位毫秒,默認(rèn)值為-1,表示永不超時。如果超過等待時間,則直接拋出JedisConnectionException;
private static int MAX_WAIT = 3000;
private static int TIMEOUT = 3000;
//在borrow一個jedis實(shí)例時,是否提前進(jìn)行validate操作;如果為true,則得到的jedis實(shí)例均是可用的;
private static boolean TEST_ON_BORROW = true;
//在return給pool時,是否提前進(jìn)行validate操作;
private static boolean TEST_ON_RETURN = true;
private static JedisPool jedisPool = null;
/**
* redis過期時間,以秒為單位
*/
public final static int EXRP_HOUR = 60 * 60; //一小時
public final static int EXRP_DAY = 60 * 60 * 24; //一天
public final static int EXRP_MONTH = 60 * 60 * 24 * 30; //一個月
/**
* 初始化Redis連接池
*/
private static void initialPool() {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_ACTIVE);
config.setMaxIdle(MAX_IDLE);
config.setMaxWaitMillis(MAX_WAIT);
config.setTestOnBorrow(TEST_ON_BORROW);
jedisPool = new JedisPool(config, IP, PORT, TIMEOUT, "123456");
} catch (Exception e) {
//logger.error("First create JedisPool error : "+e);
e.getMessage();
}
}
/**
* 在多線程環(huán)境同步初始化
*/
private static synchronized void poolInit() {
if (jedisPool == null) {
initialPool();
}
}
/**
* 同步獲取Jedis實(shí)例
*
* @return Jedis
*/
public synchronized static Jedis getJedis() {
if (jedisPool == null) {
poolInit();
}
Jedis jedis = null;
try {
if (jedisPool != null) {
jedis = jedisPool.getResource();
}
} catch (Exception e) {
e.getMessage();
// logger.error("Get jedis error : "+e);
}
return jedis;
}
/**
* 釋放jedis資源
*
* @param jedis
*/
public static void returnResource(final Jedis jedis) {
if (jedis != null && jedisPool != null) {
jedisPool.returnResource(jedis);
}
}
public static Long sadd(String key, String... members) {
Jedis jedis = null;
Long res = null;
try {
jedis = getJedis();
res = jedis.sadd(key, members);
} catch (Exception e) {
//logger.error("sadd error : "+e);
e.getMessage();
}
return res;
}
}3、編寫Redis鎖的工具類
public class RedisLock {
private static final int setnxSuccss = 1;
/**
* 獲取鎖
*
* @param lockKey 定義鎖的key
* @param notLockTimeOut 沒有獲取鎖的超時時間
* @param lockTimeOut 使用鎖的超時時間
* @return
*/
public String getLock(String lockKey, int notLockTimeOut, int lockTimeOut) {
// 獲取Redis連接
Jedis jedis = RedisClientUtil.getJedis();
// 定義沒有獲取鎖的超時時間
Long endTimeOut = System.currentTimeMillis() + notLockTimeOut;
while (System.currentTimeMillis() < endTimeOut) {
String lockValue = UUID.randomUUID().toString();
// 如果在多線程情況下誰能夠setnx 成功返回0 誰就獲取到鎖
if (jedis.setnx(lockKey, lockValue) == setnxSuccss) {
jedis.expire(lockKey, lockTimeOut / 1000);
return lockValue;
}
// 否則情況下 在超時時間內(nèi)繼續(xù)循環(huán)
}
try {
if (jedis != null) {
jedis.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 釋放鎖 其實(shí)就是將該key刪除
*
* @return
*/
public Boolean unLock(String lockKey, String lockValue) {
Jedis jedis = RedisClientUtil.getJedis();
// 確定是對應(yīng)的鎖 ,才刪除
if (lockValue.equals(jedis.get(lockKey))) {
return jedis.del(lockKey) > 0 ? true : false;
}
return false;
}
}4、測試方法
private RedisLock redisLock = new RedisLock();
private String lockKey = "kaico_lock";
/**
* 測試Jedis實(shí)現(xiàn)分布式鎖
* @return
*/
@GetMapping("/restLock1")
public String restLock1(){
// 1.獲取鎖
String lockValue = redisLock.getLock(lockKey, 5000, 5000);
if (StringUtils.isEmpty(lockValue)) {
System.out.println(Thread.currentThread().getName() + ",獲取鎖失敗!");
return "獲取鎖失敗";
}
// 2.獲取鎖成功執(zhí)行業(yè)務(wù)邏輯
System.out.println(Thread.currentThread().getName() + ",獲取成功,lockValue:" + lockValue);
// 3.釋放lock鎖
redisLock.unLock(lockKey, lockValue);
return "";
}使用Springboot實(shí)現(xiàn)
依賴于之前的項(xiàng)目
1、編寫鎖的工具類方法
@Component
public class SpringbootRedisLockUtil {
@Autowired
public RedisTemplate redisTemplate;
// 解鎖原子性操作腳本
public static final String unlockScript="if redis.call(\"get\",KEYS[1]) == ARGV[1]\n"
+ "then\n"
+ " return redis.call(\"del\",KEYS[1])\n"
+ "else\n"
+ " return 0\n"
+ "end";
/**
* 加鎖,有阻塞
* @param name
* @param expire
* @param timeout
* @return
*/
public String lock(String name, long expire, long timeout) throws UnsupportedEncodingException {
long startTime=System.currentTimeMillis();
String token;
do{
token=tryLock(name,expire);
if(token==null){
//設(shè)置等待時間,若等待時間過長則獲取鎖失敗
if((System.currentTimeMillis()-startTime)>(timeout-50)){
break;
}
try {
Thread.sleep(50);//try it again per 50
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}while (token==null);
return token;
}
/**
* 解鎖
* @param name
* @param token
* @return
*/
public Boolean unlock(String name, String token) throws UnsupportedEncodingException {
byte[][] keyArgs=new byte[2][];
keyArgs[0]= name.getBytes(Charset.forName("UTF-8"));
keyArgs[1]= token.getBytes(Charset.forName("UTF-8"));
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection connection = connectionFactory.getConnection();
try{
Long result = connection.scriptingCommands().eval(unlockScript.getBytes(Charset.forName("UTF-8")), ReturnType.INTEGER, 1, keyArgs);
if(result!=null&&result>0){
return true;
}
}finally {
RedisConnectionUtils.releaseConnection(connection,connectionFactory);
}
return false;
}
/**
* 加鎖,無阻塞
* @param name
* @param expire
* @return
*/
public String tryLock(String name, long expire) throws UnsupportedEncodingException {
String token= UUID.randomUUID().toString();
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection connection = connectionFactory.getConnection();
try{
Boolean result = connection.set(name.getBytes(Charset.forName("UTF-8")), token.getBytes(Charset.forName("UTF-8")),
Expiration.from(expire, TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT);
if(result!=null&&result){
return token;
}
}
finally {
RedisConnectionUtils.releaseConnection(connection,connectionFactory);
}
return null;
}
}2、測試類
@Autowired
private SpringbootRedisLockUtil springbootRedisLockUtil;
@PostMapping("/restLock1")
public void restLock2() throws UnsupportedEncodingException {
String token;
token=springbootRedisLockUtil.lock(Thread.currentThread().getName(),1000,11000);
if(token!=null){
System.out.println("我拿到鎖了哦!");
}
else{
System.out.println("我沒有拿到鎖!");
}
springbootRedisLockUtil.unlock(Thread.currentThread().getName(),token);
}到此這篇關(guān)于Redis超詳細(xì)分析分布式鎖的文章就介紹到這了,更多相關(guān)Redis分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis發(fā)布訂閱_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了redis發(fā)布訂閱,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
Redis實(shí)現(xiàn)延遲隊(duì)列的項(xiàng)目示例
延遲隊(duì)列是Redis的一個重要應(yīng)用場景,本文主要介紹了Redis實(shí)現(xiàn)延遲隊(duì)列的項(xiàng)目示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
詳解Redis如何優(yōu)雅地實(shí)現(xiàn)接口防刷
這篇文章主要為大家詳細(xì)介紹了Redis優(yōu)雅地實(shí)現(xiàn)接口防刷的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03
在CentOS 7環(huán)境下安裝Redis數(shù)據(jù)庫詳解
Redis是一個開源的、基于BSD許可證的,基于內(nèi)存的、鍵值存儲NoSQL數(shù)據(jù)本篇文章主要介紹了在CentOS 7環(huán)境下安裝Redis數(shù)據(jù)庫詳解,有興趣的可以了解一下。2016-11-11

