場景:
電商網(wǎng)站上有很多秒殺活動,會迎來一個用戶請求的高峰期,可能會有幾十萬幾百萬的并發(fā)量,來搶這個手機,在高并發(fā)的情形下會對數(shù)據(jù)庫服務(wù)器或者是文件服務(wù)器應(yīng)用服務(wù)器造成巨大的壓力,嚴重時說不定就宕機了;
另一個問題是,秒殺的東西都是有量的,一款手機只有10臺的量秒殺,在高并發(fā)的情況下,成千上萬條數(shù)據(jù)更新數(shù)據(jù)庫(例如10臺的量被人搶一臺就會在數(shù)據(jù)集某些記錄下 減1),那次這個時候的先后順序是很亂的,很容易出現(xiàn)10臺的量,搶到的人就不止10個這種嚴重的問題。
對于redis的并發(fā)的處理:
a)Redis為單進程單線程模式,Redis本身沒有鎖的概念,Redis對于多個客戶端連接并不存在競爭,
但是在Jedis客戶端對Redis進行并發(fā)訪問時會發(fā)生 連接超時、連接阻塞、客戶端關(guān)閉連接等問題,對于這些問題對此有2種解決方法:
1.客戶端角度,為保證每個客戶端間正常有序與Redis進行通信,對連接進行池化,同時對客戶端讀寫Redis操作采用內(nèi)部鎖synchronized。
2..服務(wù)器角度,利用setnx實現(xiàn)鎖。
Redis分布式鎖的實現(xiàn):
思路很簡單,主要用到的redis函數(shù)是setnx(),首先是將某一任務(wù)標識名(這里用Lock:order作為標識名的例子)作為鍵存到redis里,并為其設(shè)個過期時間。
對于再次Lock:order請求過來,先是通過setnx()看看是否能將Lock:order插入到redis里,可以的話就返回true,不可以就返回false。當然,在我的代碼里會比這個思路復(fù)雜一些,我會在分析代碼時進一步說明。
Redis實現(xiàn)任務(wù)隊列:
實現(xiàn)會用到上面的Redis分布式的鎖機制,主要是用到了Redis里的有序集合這一數(shù)據(jù)結(jié)構(gòu),例如入隊時,通過zset的add()函數(shù)進行入隊,而出對時,可以用到zset的getScore()函數(shù)。另外還可以彈出頂部的幾個任務(wù)。
Redis實現(xiàn)分布式鎖代碼:需要注意的問題
1:為避免特殊原因?qū)е骆i無法釋放,在加鎖成功后,鎖會被賦予一個生存時間(通過lock方法的參數(shù)設(shè)置或者使用默認值),超出生存時間鎖會被自動釋放鎖;如果需要長時間加鎖,可以通過expire方法延長鎖的生存時間。
2:系統(tǒng)級的鎖在進程無論何種原因時出現(xiàn)崩潰時,操作系統(tǒng)會自己回收鎖,所以不會出現(xiàn)資源丟失;但是分布式鎖則不同,如果設(shè)置的鎖生成時間過長,一旦由于某個原因出現(xiàn)系統(tǒng)崩潰的時候,其他進程就會獲取不到鎖, 這個鎖就會變成垃圾鎖,其他進程也用不到這個鎖,進不到加鎖區(qū)。
3:加鎖代碼中主要的兩個參數(shù),一個是timeout,這個是循環(huán)獲取鎖的等待時間,在這個時間內(nèi)會一直嘗試獲取鎖知道超時,如果為0,則表示獲取鎖失敗后直接返回而不再等待;另一個重要參數(shù)的expire,這個參數(shù)指當前鎖的最大生存時間,以秒為單位的,它必須大于0,如果超過生存時間鎖仍未被釋放,則系統(tǒng)會自動強制釋放
代碼實現(xiàn)過程:先取得當前時間,然后再獲取到鎖失敗時的等待超時的時刻(是個時間戳),再獲取到鎖的最大生存時間。
key用這種格式:”Lock:鎖的標識名”,進入循環(huán)了,先是插入數(shù)據(jù)到redis里,使用setnx()函數(shù),key鍵不存在則插入數(shù)據(jù),如果插入成功,則對該鍵進行失效時間的設(shè)置,并將該key鍵放在$lockedName數(shù)組里,返回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);
}
/**
* 鎖在給定的等待時間內(nèi)空閑,則獲取鎖成功 返回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);
}
/**
* 鎖在給定的等待時間內(nèi)空閑,則獲取鎖成功 返回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 {
// 構(gòu)建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執(zhí)行計數(shù)
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);
}
}
轉(zhuǎn)自:https://blog.csdn.net/wanghang88/article/details/53028009