1. 程式人生 > >基於redis setnx的簡易分散式鎖

基於redis setnx的簡易分散式鎖

鎖的原理, 就是設定個flag,記錄下是否正被使用,可重入鎖再判斷下是否是自己在使用.

這個flag,必須保證所有資源用的是同一個.


synchronized關鍵字,lock類等, 可以保證此flag在單個jvm中唯一, 但是有多個jvm(叢集,分散式)時候,就沒辦法保證了.

這時候需要用一個 跨jvm唯一的flag.  也就是常說的分散式鎖了.

分散式鎖的實現也有很多種. 基於redis,zookeeper等等, 也可以基於資料庫.

基於redis的,也有setnx,incr等操作的.

本著簡單,實用的原則, 寫了一個簡單的類,主要支援以下功能:

1.所有加鎖操作都需要有時間限制,不能無限鎖定

2.提供獲取失敗重試機制

3.釋放鎖時,保證釋放的鎖是自己獲取到的

下面是程式碼,用到了jedis的jar, redis的連線是和spring整合配置的.這裡就不列了. 所以不能直接執行

重點是LockUtil類.(鎖存在,檢查鎖的剩餘時間,然後重試,這裡沒有扣減此過程消耗的時間.所以極端情況會出現超出tryLock的timeOut設定而獲取到鎖)

import java.util.Objects;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.zhidian.common.util.SpringContextUtil;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisException;

/**
 * 基於redis setnx的 分散式鎖 實, 前提是所有的鎖都要有鎖定時間.
 * 獲取鎖的時候,需要指定value,在unlock的時候,會根據value判斷是否remove
 * 
 * @author: qq315737546
 */
public class LockUtil {
	private static Log logger = LogFactory.getLog(LockUtil.class);
	private static final String LOCK_PREFIX = "LOCK";
	private static final Integer DEFAULT_LOCK_TIME = 20;// 預設鎖定時間秒
	private static final Long DEFAULT_SLEEP_TIME = 100L;// 預設sleep時間,100毫秒

	/**
	 * 單臺伺服器可直接用系統時間,多臺伺服器可用redis.time()
	 * 
	 * @return
	 * @Author: qq315737546
	 */
	public static String getCurTime() {
		return String.valueOf(System.currentTimeMillis());
	}

	/**
	 * 獲取鎖,如果失敗,自動重試
	 * 
	 * @param key
	 * @param value
	 * @return
	 * @Author: qq315737546
	 */
	public static void lock(String key, String value) {
		lock(key, value, DEFAULT_LOCK_TIME);
	}

	/**
	 * 獲取鎖,如果失敗,自動重試
	 * 
	 * @param key
	 * @param value
	 * @param lockTime
	 *            鎖定時間
	 * @return
	 * @Author: qq315737546
	 */
	public static void lock(String key, String value, int lockTime) {
		lock(key, value, lockTime, true);
	}

	/**
	 * 
	 * @param key
	 * @param value
	 * @param lockTime
	 *            鎖定時間
	 * @param reTry
	 *            是否重試
	 * @return
	 * @Author: wangxingfei
	 */
	private static boolean lock(String key, String value, int lockTime, boolean reTry) {
		return lock(key, value, lockTime, reTry, 0, false, 0);
	}

	/**
	 * 獲取鎖,如果失敗,直接返回false
	 * 
	 * @param key
	 * @param value
	 * @return
	 * @Author: qq315737546
	 */
	public static boolean tryLock(String key, String value) {
		return tryLock(key, value, DEFAULT_LOCK_TIME);
	}

	/**
	 * 獲取鎖,如果失敗,直接返回false
	 * 
	 * @param key
	 * @param value
	 * @param lockTime
	 *            鎖定時間
	 * @return
	 * @Author: qq315737546
	 */
	public static boolean tryLock(String key, String value, int lockTime) {
		return lock(key, value, lockTime, false);
	}

	/**
	 * 嘗試獲取鎖,如果獲取失敗,重試,直到成功或超出指定時間
	 * 
	 * @param key
	 * @param value
	 * @param lockTime
	 * @param timeOutMillis
	 *            獲取鎖超時時間 (毫秒)
	 * 
	 * @return
	 * @Author: qq315737546
	 */
	public static boolean tryLock(String key, String value, int lockTime, long timeOutMillis) {
		return lock(key, value, lockTime, true, 0, true, timeOutMillis);
	}

	/**
	 * 釋放鎖,key對應的value於引數value一致,才刪除key
	 * 
	 * @param key
	 * @param value
	 * @Author: qq315737546
	 */
	public static void unlock(String key, String value) {
		String fullKey = getFullKey(key);
		String existValue = JedisUtil.getObject(fullKey);
		if (Objects.equals(value, existValue)) {
			logger.info("unlock success ; key:" + key + ",value:" + value);
			JedisUtil.remove(fullKey);
		} else {
			logger.info("unlock failed ; key:" + key + ",value:" + value + ",existValue:" + existValue);
		}
	}

	/**
	 * 獲取鎖
	 * 
	 * @param key
	 * @param value
	 * @param lockTime
	 *            鎖定時間
	 * @param reTry
	 *            失敗是否重試
	 * @param curTryTime
	 *            當前嘗試次數
	 * @param needTimeOut
	 *            是否需要判斷超時時間
	 * @param timeOutMillis
	 *            嘗試超時時間(毫秒)
	 * @return
	 * @Author: qq315737546
	 */
	private static boolean lock(String key, String value, int lockTime, boolean reTry, int curTryTime,
			boolean needTimeOut, long timeOutMillis) {
		logger.info(Thread.currentThread().getName() + ",lock come in ; key:" + key + ",value:" + value + ",lockTime:"
				+ lockTime + ",reTry:" + reTry + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut
				+ ",timeOutMillis:" + timeOutMillis);
		curTryTime++;
		String fullKey = getFullKey(key);
		long result = JedisUtil.setnx(fullKey, value);
		// 獲取成功,直接返回
		if (result > 0) {
			logger.info("lock success ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
					+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
			JedisUtil.expire(fullKey, lockTime);
			return true;
		}

		// 如果不成功,判斷現在的key的有效期,如果是無限期,則刪除key,並重試
		long expire = JedisUtil.ttl(fullKey);
		if (expire < 0) {
			if (expire == -1) {
				JedisUtil.remove(fullKey);
				logger.info("remove key ; key:" + key + ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut
						+ ",timeOutMillis:" + timeOutMillis);
			} else {
				logger.info("ttl key ; key:" + key + " is return " + expire);
			}
			return lock(key, value, lockTime, reTry, curTryTime, needTimeOut, timeOutMillis);
		}

		// 獲取失敗,不需要重試,直接返回
		if (!reTry) {
			logger.info("lock failed ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
					+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
			return false;
		}

		// 獲取失敗, 且已超時,返回
		if (needTimeOut && timeOutMillis <= 0) {
			logger.info("lock failed ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
					+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
			return false;
		}

		// 獲取sleep時間
		long sleepMillis = DEFAULT_SLEEP_TIME;
		if (needTimeOut) {
			timeOutMillis = timeOutMillis - DEFAULT_SLEEP_TIME;
			if (timeOutMillis < DEFAULT_SLEEP_TIME) {
				sleepMillis = timeOutMillis;
			}
		}

		// sleep後重新獲取鎖
		try {
			Thread.sleep(sleepMillis);
		} catch (InterruptedException e) {
			logger.error("lock sleep errro ; key:" + key + ",value:" + value, e);
		}

		if (curTryTime > 100) {
			logger.warn("lock warning ; key:" + key + ",value:" + value + ",lockTime:" + lockTime + ",reTry:" + reTry
					+ ",curTryTime:" + curTryTime + ",needTimeOut:" + needTimeOut + ",timeOutMillis:" + timeOutMillis);
		}

		return lock(key, value, lockTime, reTry, curTryTime, needTimeOut, timeOutMillis);
	}

	private static String getFullKey(String key) {
		return LOCK_PREFIX + ":" + key;
	}

}

class JedisUtil {

	private static Log logger = LogFactory.getLog(JedisUtil.class);

	protected static JedisPool jedisPool = (JedisPool) SpringContextUtil.getBean("jedisPool");;

	/**
	 * 獲取redis操作例項(不必加鎖)
	 * 
	 * @return jedis
	 */
	protected static Jedis getJedis() throws JedisException {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
		} catch (JedisException e) {
			logger.warn("failed:jedisPool getResource.", e);
			if (jedis != null) {
				jedisPool.returnBrokenResource(jedis);
			}
			throw e;
		}
		return jedis;
	}

	protected static void release(Jedis jedis, boolean isBroken) {
		if (jedis != null) {
			if (isBroken) {
				jedisPool.returnBrokenResource(jedis);
			} else {
				jedisPool.returnResource(jedis);
			}
		}
	}

	/**
	 * set if not exists
	 */
	public static long setnx(String key, String value) {
		Jedis jedis = null;
		boolean isBroken = false;
		long result = 0L;
		try {
			jedis = getJedis();
			result = jedis.setnx(key, value);
		} catch (Exception e) {
			isBroken = true;
			logger.error("JedisDao::setnx: key: " + key + ",value:" + value + " message: " + e.getMessage(), e);
		} finally {
			release(jedis, isBroken);
		}
		return result;
	}

	/**
	 * 設定過期時間
	 */
	public static void expire(String fullKey, int lockTime) {
		Jedis jedis = null;
		boolean isBroken = false;
		try {
			jedis = getJedis();
			jedis.expire(fullKey, lockTime);
		} catch (Exception e) {
			isBroken = true;
			logger.error("JedisDao:expire: key: " + fullKey + ",lockTime:" + lockTime + " message: " + e.getMessage(),e);
		} finally {
			release(jedis, isBroken);
		}
	}

	/**
	 * 檢視key的有效期
	 * 
	 * @param fullKey
	 * @return 如果key不存在或者已過期,返回 -2 ;如果key沒有設定過期時間(永久有效),返回 -1 ,否則返回有效期(單位秒)
	 */
	public static long ttl(String fullKey) {
		Jedis jedis = null;
		boolean isBroken = false;
		Long result = 0L;
		try {
			jedis = getJedis();
			result = jedis.ttl(fullKey);
		} catch (Exception e) {
			isBroken = true;
			logger.error("JedisDao: ttl : key: " + fullKey + " message: " + e.getMessage(), e);
		} finally {
			release(jedis, isBroken);
		}
		return result;
	}

	public static void remove(String key) {
		Jedis jedis = null;
		boolean isBroken = false;
		try {
			jedis = getJedis();
			jedis.del(key);
		} catch (Exception e) {
			isBroken = true;
			logger.error("JedisDao::remove: key: " + key + " message: " + e.getMessage(), e);
		} finally {
			release(jedis, isBroken);
		}
	}
	
	public static String getObject(String key) {
		Jedis jedis = null;
		boolean isBroken = false;
		try {
			jedis = getJedis();
			String value = jedis.get(key);
			return value;
		} catch (Exception e) {
			isBroken = true;
			logger.error("JedisDao::getObject: key: " + key + " message: " + e.getMessage(), e);
		} finally {
			release(jedis, isBroken);
		}
		return null;

	}

}


使用示例如下

		String key = "testLock";
		String value = LockUtil.getCurTime();
		
		try{
			LockUtil.lock(key, value);
		}finally{
			LockUtil.unlock(key, value);
		}
		
		boolean lock = LockUtil.tryLock(key, value);
		if(lock){
			try{
				//TODO 
			}finally{
				LockUtil.unlock(key, value);
			}
		}


歡迎討論拍磚交流

相關推薦

基於Redis的簡單分散式

前言 在單應用的情況下,需要對某個資源進行加鎖經常會用到 synchronized 關鍵字。但是在叢集的環境下,synchronized 只能進行單臺機器的資源鎖定。舉例一個場景,賬戶表,該賬戶不斷有人往裡面轉錢,賬戶餘額需要不斷的累加,表裡有version欄位。在高併發情況下,多個

基於redis實現的分散式

RedisLockHelper.java /** * Created by BingZhong on 2017/7/29. * * 基於Redis實現的分散式鎖 */ public final class RedisLockHelper { private static Logger

redis setnx 實現分散式和單機

對應給定的keys到他們相應的values上。只要有一個key已經存在,MSETNX一個操作都不會執行。由於這種特性,MSETNX可以實現要麼所有的操作都成功,要麼一個都不執行,這樣可以用來設定不同的key,來表示一個唯一的物件的不同欄位。 在 Redis 裡,所謂

基於redis setnx簡易分散式

鎖的原理, 就是設定個flag,記錄下是否正被使用,可重入鎖再判斷下是否是自己在使用.這個flag,必須保證所有資源用的是同一個.synchronized關鍵字,lock類等, 可以保證此flag在單個jvm中唯一, 但是有多個jvm(叢集,分散式)時候,就沒辦法保證了.這時

Redis例項實現分散式 基於Lua指令碼

前言 多執行緒併發執行情況下如何保證一個程式碼塊在同一時間只能由同一執行緒執行(同一個JVM中多執行緒同步執行)? 可以使用執行緒鎖的機制(如synchronized,ReentrantLock類) synchronized(obj){ ...... } Ree

基於Redis RedLock的分散式同步

本文采用Redis官網提供的RedLock來實現分散式同步鎖,實現了單機模式和哨兵叢集模式兩種。 安全和可靠性保證 在描述我們的設計之前,我們想先提出三個屬性,這三個屬性在我們看來,是實現高效分散式鎖的基礎。 安全屬性:互斥,不管任何時候,只有一個客戶端能持有

基於redis分布式實現“秒殺”

購物車 串行 and 本質 希望 ide stack 失敗 業務場景 最近在項目中遇到了類似“秒殺”的業務場景,在本篇博客中,我將用一個非常簡單的demo,闡述實現所謂“秒殺”的基本思路。 業務場景 所謂秒殺,從業務角度看,是短時間內多個用戶“爭搶”資源,這裏的資源在大部分

springboot2結合redis,實現分散式

1.新增Maven依賴 <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId>

Redis做為分散式理解

轉自:https://www.cnblogs.com/0201zcr/p/5942748.html ; 一、使用分散式鎖要滿足的幾個條件: 系統是一個分散式系統(關鍵是分散式,單機的可以使用ReentrantLock或者synchronized程式碼塊來實現) 共享資源(各個系統

Redis事務和分散式

Redis事務   Redis中的事務(transaction)是一組命令的集合。事務同命令一樣都是Redis最小的執行單位,一個事務中的命令要麼都執行,要麼都不執行。Redis事務的實現需要用到 MULTI 和 EXEC 兩個命令,事務開始的時候先向Re

基於ZooKeeper Curator實現分散式

基於ZooKeeper分散式鎖的流程 1. 客戶端連線上zookeeper,並在指定節點(locks)下建立臨時順序節點node_n 2. 客戶端獲取locks目錄下所有children節點 3. 客戶端對子節點按節點自增序號從小到大排序,並判斷自己建立的節

redis併發問題 && 分散式

redis中的併發問題 使用redis作為快取已經很久了,redis是以單程序的形式執行的,命令是一個接著一個執行的,一直以為不會存在併發的問題,直到今天看到相關的資料,才恍然大悟~~ 具體問題例項 有個鍵,假設名稱為myNum,裡面儲存的是阿拉伯數字,假設現在值為

利用redis實現的分散式

假設一個場景: 在分散式系統中,通常會遇到多個伺服器處理同一個業務的場景,我們需要利用某種機制避免併發問題。Java語言中,我們可以通過鎖的方式避免單個服務的多執行緒併發問題,而分散式系統中的併發問題用Java的鎖機制是很難解決的。 分散式鎖也有類似地“首先獲取鎖, 然後

Redis形式的分散式的實現

Redis形式的分散式鎖 1:為什麼要有分散式鎖 1.1:鎖介紹 執行緒鎖:主要用來給方法、程式碼塊加鎖。當某個方法或程式碼使用鎖,在同一時刻僅有一個執行緒執行該方法或該程式碼段。執行緒鎖只在同一JVM中有效果,因為執行緒鎖的實現在根本上是依靠執行

Golang基於redis實現的分散式訊號量(semaphore)

前言 Semaphore是訊號量,作用? 我想大家都知道。semaphore跟mutex的區別我想大家也知道了,我這裡就不老生常談,重複講解了,有興趣的朋友可以自行google相關的同步鎖文章。 像semaphore,mutex 可以支援多執行緒,也可以是多程序。但如

淺談分散式--基於快取(Redis,memcached,tair)實現篇

淺談分散式鎖--基於快取(Redis,memcached,tair)實現篇: 一、Redis分散式鎖 1、Redis實現分散式鎖的原理:     1.利用setnx命令,即只有在某個key不存在情況才能set成功該key,這樣就達到了多個程序併發去set

實現基於redis分散式並整合spring-boot-starter

文章目錄 概述 使用 1.導包 2.寫一個實現鎖功能的service 3.檢查redis的key 4.呼叫(鎖成功) 5.呼叫(鎖失敗) 實現

分散式(三)__基於redis實現

本文參考借鑑了論壇大佬的一篇很詳細的博文並在此基礎上加以實現,在此謝謝此位博主!,博文連線: https://www.cnblogs.com/linjiqin/p/8003838.html 前言 首先,為了確保分散式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件: 互斥

基於redis分散式(轉)

基於redis的分散式鎖 1 介紹 這篇博文講介紹如何一步步構建一個基於Redis的分散式鎖。會從最原始的版本開始,然後根據問題進行調整,最後完成一個較為合理的分散式鎖。 本篇文章會將分散式鎖的實現分為兩部分,一個是單機環境,另一個是叢集環境下的Redis鎖實現。在介紹分散式鎖的實

基於Redis實現分散式

背景 在很多網際網路產品應用中,有些場景需要加鎖處理,比如:秒殺,全域性遞增ID,樓層生成等等。大部分的解決方案是基於DB實現的,Redis為單程序單執行緒模式,採用佇列模式將併發訪問變成序列訪問,且多客戶端對Redis的連線並不存在競爭關係。其次Redis提供一些命令SETNX,GETSET,可以方便