關於redis中使用鎖機制,( 實現分散式鎖和任務佇列)
場景:
電商網站上有很多秒殺活動,會迎來一個使用者請求的高峰期,可能會有幾十萬幾百萬的併發量,來搶這個手機,在高併發的情形下會對資料庫伺服器或者是檔案伺服器應用伺服器造成巨大的壓力,嚴重時說不定就宕機了;
另一個問題是,秒殺的東西都是有量的,一款手機只有10臺的量秒殺,在高併發的情況下,成千上萬條資料更新資料庫(例如10臺的量被人搶一臺就會在資料集某些記錄下 減1),那次這個時候的先後順序是很亂的,很容易出現10臺的量,搶到的人就不止10個這種嚴重的問題。
對於redis的併發的處理:
a)Redis為單程序單執行緒模式,Redis本身沒有鎖的概念,Redis對於多個客戶端連線並不存在競爭,
但是在Jedis客戶端對Redis進行併發訪問時會發生 連線超時、連線阻塞、客戶端關閉連線等問題,對於這些問題對此有2種解決方法:
1.客戶端角度,為保證每個客戶端間正常有序與Redis進行通訊,對連線進行池化,同時對客戶端讀寫Redis操作採用內部鎖synchronized。
2..伺服器角度,利用setnx實現鎖。
Redis分散式鎖的實現:
思路很簡單,主要用到的redis函式是setnx(),首先是將某一任務標識名(這裡用Lock:order作為標識名的例子)作為鍵存到redis裡,併為其設個過期時間。
對於再次Lock:order請求過來,先是通過setnx()看看是否能將Lock:order插入到redis裡,可以的話就返回true,不可以就返回false。當然,在我的程式碼裡會比這個思路複雜一些,我會在分析程式碼時進一步說明。
Redis實現任務佇列:
實現會用到上面的Redis分散式的鎖機制,主要是用到了Redis裡的有序集合這一資料結構,例如入隊時,通過zset的add()函式進行入隊,而出對時,可以用到zset的getScore()函式。另外還可以彈出頂部的幾個任務。
Redis實現分散式鎖程式碼:需要注意的問題
1:為避免特殊原因導致鎖無法釋放,在加鎖成功後,鎖會被賦予一個生存時間(通過lock方法的引數設定或者使用預設值),超出生存時間鎖會被自動釋放鎖;如果需要長時間加鎖,可以通過expire方法延長鎖的生存時間。
2:系統級的鎖在程序無論何種原因時出現崩潰時,作業系統會自己回收鎖,所以不會出現資源丟失;但是分布式鎖則不同,如果設定的鎖生成時間過長,一旦由於某個原因出現系統崩潰的時候,其他程序就會獲取不到鎖,
這個鎖就會變成垃圾鎖,其他程序也用不到這個鎖,進不到加鎖區。
3:加鎖程式碼中主要的兩個引數,一個是$timeout,這個是迴圈獲取鎖的等待時間,在這個時間內會一直嘗試獲取鎖知道超時,如果為0,則表示獲取鎖失敗後直接返回而不再等待;另一個重要引數的$expire,這個引數指當前鎖的最大生存時間,以秒為單位的,它必須大於0,如果超過生存時間鎖仍未被釋放,則系統會自動強制釋放
程式碼實現過程:先取得當前時間,然後再獲取到鎖失敗時的等待超時的時刻(是個時間戳),再獲取到鎖的最大生存時間。
key用這種格式:”Lock:鎖的標識名”,進入迴圈了,先是插入資料到redis裡,使用setnx()函式,key鍵不存在則插入資料,如果插入成功,則對該鍵進行失效時間的設定,並將該key鍵放在$lockedName數組裡,返回true,也就是上鎖成功
如果該key鍵存在,則不會插入操作了。
public class RedisBillLockHandler implements IBatchBillLockHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisBillLockHandler.class);
private static final int DEFAULT_SINGLE_EXPIRE_TIME = 3;
private static final int DEFAULT_BATCH_EXPIRE_TIME = 6;
private final JedisPool jedisPool;
public RedisBillLockHandler(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 獲取鎖 如果鎖可用 立即返回true, 否則返回false
* @see com.fx.platform.components.lock.IBillLockHandler#tryLock(com.fx.platform.components.lock.IBillIdentify)
* @param billIdentify
* @return
*/
public boolean tryLock(IBillIdentify billIdentify) {
return tryLock(billIdentify, 0L, null);
}
/**
* 鎖在給定的等待時間內空閒,則獲取鎖成功 返回true, 否則返回false
* @param billIdentify
* @param timeout
* @param unit
* @return
*/
public boolean tryLock(IBillIdentify billIdentify, long timeout, TimeUnit unit) {
String key = (String) billIdentify.uniqueIdentify();
Jedis jedis = null;
try {
jedis = getResource();
long nano = System.nanoTime();
do {
LOGGER.debug("try lock key: " + key);
Long i = jedis.setnx(key, key);
if (i == 1) {
jedis.expire(key, DEFAULT_SINGLE_EXPIRE_TIME);
LOGGER.debug("get lock, key: " + key + " , expire in " + DEFAULT_SINGLE_EXPIRE_TIME + " seconds.");
return Boolean.TRUE;
} else { // 存在鎖
if (LOGGER.isDebugEnabled()) {
String desc = jedis.get(key);
LOGGER.debug("key: " + key + " locked by another business:" + desc);
}
}
if (timeout == 0) {
break;
}
Thread.sleep(300);
} while ((System.nanoTime() - nano) < unit.toNanos(timeout));
return Boolean.FALSE;
} catch (JedisConnectionException je) {
LOGGER.error(je.getMessage(), je);
returnBrokenResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
} finally {
returnResource(jedis);
}
return Boolean.FALSE;
}
/**
* 如果鎖空閒立即返回 獲取失敗 一直等待
* @param billIdentify
*/
public void lock(IBillIdentify billIdentify) {
String key = (String) billIdentify.uniqueIdentify();
Jedis jedis = null;
try {
jedis = getResource();
do {
LOGGER.debug("lock key: " + key);
Long i = jedis.setnx(key, key);
if (i == 1) {
jedis.expire(key, DEFAULT_SINGLE_EXPIRE_TIME);
LOGGER.debug("get lock, key: " + key + " , expire in " + DEFAULT_SINGLE_EXPIRE_TIME + " seconds.");
return;
} else {
if (LOGGER.isDebugEnabled()) {
String desc = jedis.get(key);
LOGGER.debug("key: " + key + " locked by another business:" + desc);
}
}
Thread.sleep(300);
} while (true);
} catch (JedisConnectionException je) {
LOGGER.error(je.getMessage(), je);
returnBrokenResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
} finally {
returnResource(jedis);
}
}
/**
* 釋放鎖
* @param billIdentify
*/
public void unLock(IBillIdentify billIdentify) {
List<IBillIdentify> list = new ArrayList<IBillIdentify>();
list.add(billIdentify);
unLock(list);
}
/**
* 批量獲取鎖 如果全部獲取 立即返回true, 部分獲取失敗 返回false
* @param billIdentifyList
* @return
*/
public boolean tryLock(List<IBillIdentify> billIdentifyList) {
return tryLock(billIdentifyList, 0L, null);
}
/**
* 鎖在給定的等待時間內空閒,則獲取鎖成功 返回true, 否則返回false
* @param billIdentifyList
* @param timeout
* @param unit
* @return
*/
public boolean tryLock(List<IBillIdentify> billIdentifyList, long timeout, TimeUnit unit) {
Jedis jedis = null;
try {
List<String> needLocking = new CopyOnWriteArrayList<String>();
List<String> locked = new CopyOnWriteArrayList<String>();
jedis = getResource();
long nano = System.nanoTime();
do {
// 構建pipeline,批量提交
Pipeline pipeline = jedis.pipelined();
for (IBillIdentify identify : billIdentifyList) {
String key = (String) identify.uniqueIdentify();
needLocking.add(key);
pipeline.setnx(key, key);
}
LOGGER.debug("try lock keys: " + needLocking);
// 提交redis執行計數
List<Object> results = pipeline.syncAndReturnAll();
for (int i = 0; i < results.size(); ++i) {
Long result = (Long) results.get(i);
String key = needLocking.get(i);
if (result == 1) { // setnx成功,獲得鎖
jedis.expire(key, DEFAULT_BATCH_EXPIRE_TIME);
locked.add(key);
}
}
needLocking.removeAll(locked); // 已鎖定資源去除
if (CollectionUtils.isEmpty(needLocking)) {
return true;
} else {
// 部分資源未能鎖住
LOGGER.debug("keys: " + needLocking + " locked by another business:");
}
if (timeout == 0) {
break;
}
Thread.sleep(500);
} while ((System.nanoTime() - nano) < unit.toNanos(timeout));
// 得不到鎖,釋放鎖定的部分物件,並返回失敗
if (!CollectionUtils.isEmpty(locked)) {
jedis.del(locked.toArray(new String[0]));
}
return false;
} catch (JedisConnectionException je) {
LOGGER.error(je.getMessage(), je);
returnBrokenResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
} finally {
returnResource(jedis);
}
return true;
}
/**
* 批量釋放鎖
* @param billIdentifyList
*/
public void unLock(List<IBillIdentify> billIdentifyList) {
List<String> keys = new CopyOnWriteArrayList<String>();
for (IBillIdentify identify : billIdentifyList) {
String key = (String) identify.uniqueIdentify();
keys.add(key);
}
Jedis jedis = null;
try {
jedis = getResource();
jedis.del(keys.toArray(new String[0]));
LOGGER.debug("release lock, keys :" + keys);
} catch (JedisConnectionException je) {
LOGGER.error(je.getMessage(), je);
returnBrokenResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
} finally {
returnResource(jedis);
}
}
/**
* @return
*/
private Jedis getResource() {
return jedisPool.getResource();
}
/**
* 銷燬連線
* @author http://blog.csdn.net/java2000_wl
* @param jedis
*/
private void returnBrokenResource(Jedis jedis) {
if (jedis == null) {
return;
}
try {
//容錯
jedisPool.returnBrokenResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
/**
* @param jedis
*/
private void returnResource(Jedis jedis) {
if (jedis == null) {
return;
}
try {
jedisPool.returnResource(jedis);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
2)用Redis實現任務佇列的程式碼分析: