目前幾乎很多大型網(wǎng)站及應(yīng)用都是分布式部署的,分布式場景中的數(shù)據(jù)一致性問題一直是一個比較重要的話題。分布式的CAP理論告訴我們“任何一個分布式系統(tǒng)都無法同時滿足一致性(Consistency)娱颊、可用性(Availability)和分區(qū)容錯性(Partition tolerance)军熏,最多只能同時滿足兩項涨颜。”所以别垮,很多系統(tǒng)在設(shè)計之初就要對這三者做出取舍。在互聯(lián)網(wǎng)領(lǐng)域的絕大多數(shù)的場景中扎谎,都需要犧牲強(qiáng)一致性來換取系統(tǒng)的高可用性碳想,系統(tǒng)往往只需要保證“最終一致性”,只要這個最終時間是在用戶可以接受的范圍內(nèi)即可毁靶。
在很多場景中胧奔,我們?yōu)榱吮WC數(shù)據(jù)的最終一致性,需要很多的技術(shù)方案來支持预吆,比如分布式事務(wù)龙填、分布式鎖等。
選用Redis實現(xiàn)分布式鎖原因
Redis有很高的性能
Redis命令對此支持較好啡浊,實現(xiàn)起來比較方便
使用命令介紹
SETNX
SETNX key val
當(dāng)且僅當(dāng)key不存在時觅够,set一個key為val的字符串,返回1巷嚣;若key存在喘先,則什么都不做,返回0廷粒。
expire
expire key timeout
為key設(shè)置一個超時時間窘拯,單位為second,超過這個時間鎖會自動釋放坝茎,避免死鎖涤姊。
delete
delete key
刪除key
在使用Redis實現(xiàn)分布式鎖的時候,主要就會使用到這三個命令嗤放。
實現(xiàn)
使用的是jedis來連接Redis思喊。
實現(xiàn)思想
獲取鎖的時候,使用setnx加鎖次酌,并使用expire命令為鎖添加一個超時時間恨课,超過該時間則自動釋放鎖舆乔,鎖的value值為一個隨機(jī)生成的UUID,通過此在釋放鎖的時候進(jìn)行判斷剂公。
獲取鎖的時候還設(shè)置一個獲取的超時時間希俩,若超過這個時間則放棄獲取鎖。
釋放鎖的時候纲辽,通過UUID判斷是不是該鎖颜武,若是該鎖,則執(zhí)行delete進(jìn)行鎖釋放拖吼。
分布式鎖的核心代碼如下:
import?redis.clients.jedis.Jedis;import?redis.clients.jedis.JedisPool;import?redis.clients.jedis.Transaction;import?redis.clients.jedis.exceptions.JedisException;import?java.util.List;import?java.util.UUID;/**
*?Created?by?liuyang?on?2017/4/20.
*/public?class?DistributedLock?{????private?final?JedisPool?jedisPool;????public?DistributedLock(JedisPool?jedisPool)?{????????this.jedisPool?=?jedisPool;
}????/**
*?加鎖
*?@param?locaName??鎖的key
*?@param?acquireTimeout??獲取超時時間
*?@param?timeout???鎖的超時時間
*?@return?鎖標(biāo)識
*/
public?String?lockWithTimeout(String?locaName,??????????????????????????????????long?acquireTimeout,?long?timeout)?{
Jedis?conn?=?null;
String?retIdentifier?=?null;????????try?{????????????//?獲取連接
conn?=?jedisPool.getResource();????????????//?隨機(jī)生成一個value
String?identifier?=?UUID.randomUUID().toString();????????????//?鎖名鳞上,即key值
String?lockKey?=?"lock:"?+?locaName;????????????//?超時時間,上鎖后超過此時間則自動釋放鎖
int?lockExpire?=?(int)(timeout?/?1000);????????????//?獲取鎖的超時時間吊档,超過這個時間則放棄獲取鎖
long?end?=?System.currentTimeMillis()?+?acquireTimeout;????????????while?(System.currentTimeMillis()?<?end)?{????????????????if?(conn.setnx(lockKey,?identifier)?==?1)?{
conn.expire(lockKey,?lockExpire);????????????????????//?返回value值因块,用于釋放鎖時間確認(rèn)
retIdentifier?=?identifier;????????????????????return?retIdentifier;
}????????????????//?返回-1代表key沒有設(shè)置超時時間,為key設(shè)置一個超時時間
想學(xué)習(xí)更多java知識的朋友可以進(jìn)群:874811168?一起學(xué)習(xí)?還有全套的免費(fèi)資料領(lǐng)取
if?(conn.ttl(lockKey)?==?-1)?{
conn.expire(lockKey,?lockExpire);
}????????????????try?{
Thread.sleep(10);
}?catch?(InterruptedException?e)?{
Thread.currentThread().interrupt();
}
}
}?catch?(JedisException?e)?{
e.printStackTrace();
}?finally?{????????????if?(conn?!=?null)?{
conn.close();
}
}????????return?retIdentifier;
}????/**
*?釋放鎖
*?@param?lockName?鎖的key
*?@param?identifier????釋放鎖的標(biāo)識
*?@return
*/
public?boolean?releaseLock(String?lockName,?String?identifier)?{
Jedis?conn?=?null;
String?lockKey?=?"lock:"?+?lockName;????????boolean?retFlag?=?false;????????try?{
conn?=?jedisPool.getResource();????????????while?(true)?{????????????????//?監(jiān)視lock籍铁,準(zhǔn)備開始事務(wù)
conn.watch(lockKey);????????????????//?通過前面返回的value值判斷是不是該鎖涡上,若是該鎖,則刪除拒名,釋放鎖
if?(identifier.equals(conn.get(lockKey)))?{
Transaction?transaction?=?conn.multi();
transaction.del(lockKey);
List?results?=?transaction.exec();????????????????????if?(results?==?null)?{????????????????????????continue;
}
retFlag?=?true;
}
conn.unwatch();????????????????break;
}
}?catch?(JedisException?e)?{
e.printStackTrace();
}?finally?{????????????if?(conn?!=?null)?{
conn.close();
}
}????????return?retFlag;
}
}
測試
下面就用一個簡單的例子測試剛才實現(xiàn)的分布式鎖吩愧。
例子中使用50個線程模擬秒殺一個商品,使用--運(yùn)算符來實現(xiàn)商品減少增显,從結(jié)果有序性就可以看出是否為加鎖狀態(tài)雁佳。
模擬秒殺服務(wù),在其中配置了jedis線程池同云,在初始化的時候傳給分布式鎖糖权,供其使用。
import?redis.clients.jedis.JedisPool;
import?redis.clients.jedis.JedisPoolConfig;/**
*?Created?by?liuyang?on?2017/4/20.
*/public?class?Service?{????private?static?JedisPool?pool?=?null;????static?{
JedisPoolConfig?config?=?new?JedisPoolConfig();????????//?設(shè)置最大連接數(shù)
config.setMaxTotal(200);????????//?設(shè)置最大空閑數(shù)
config.setMaxIdle(8);????????//?設(shè)置最大等待時間
config.setMaxWaitMillis(1000?*?100);????????//?在borrow一個jedis實例時炸站,是否需要驗證星澳,若為true,則所有jedis實例均是可用的
config.setTestOnBorrow(true);
想學(xué)習(xí)更多java知識的朋友可以進(jìn)群:874811168 一起學(xué)習(xí) 還有全套的免費(fèi)資料領(lǐng)取
pool?=?new?JedisPool(config,?"127.0.0.1",?6379,?3000);
}
?
DistributedLock?lock?=?new?DistributedLock(pool);????int?n?=?500;????public?void?seckill()?{????????//?返回鎖的value值旱易,供釋放鎖時候進(jìn)行判斷
String?indentifier?=?lock.lockWithTimeout("resource",?5000,?1000);
System.out.println(Thread.currentThread().getName()?+?"獲得了鎖");
System.out.println(--n);????????lock.releaseLock("resource",?indentifier);
}
}
// 模擬線程進(jìn)行秒殺服務(wù)
public?class?ThreadA?extends?Thread?{????private?Service?service;????public?ThreadA(Service?service)?{????????this.service?=?service;
}????@Override
public?void?run()?{
service.seckill();
}
}public?class?Test?{????public?static?void?main(String[]?args)?{
Service?service?=?new?Service();????????for?(int?i?=?0;?i?<?50;?i++)?{
ThreadA?threadA?=?new?ThreadA(service);
threadA.start();
}
}
}
結(jié)果如下禁偎,結(jié)果為有序的。
若注釋掉使用鎖的部分
public?void?seckill()?{????//?返回鎖的value值阀坏,供釋放鎖時候進(jìn)行判斷
//String?indentifier?=?lock.lockWithTimeout("resource",?5000,?1000);
System.out.println(Thread.currentThread().getName()?+?"獲得了鎖");
想學(xué)習(xí)更多java知識的朋友可以進(jìn)群:874811168?一起學(xué)習(xí)?還有全套的免費(fèi)資料領(lǐng)取
System.out.println(--n);????//lock.releaseLock("resource",?indentifier);}
從結(jié)果可以看出如暖,有一些是異步進(jìn)行的。
在分布式環(huán)境中忌堂,對資源進(jìn)行上鎖有時候是很重要的盒至,比如搶購某一資源,這時候使用分布式鎖就可以很好地控制資源。
當(dāng)然枷遂,在具體使用中寝蹈,還需要考慮很多因素,比如超時時間的選取登淘,獲取鎖時間的選取對并發(fā)量都有很大的影響,上述實現(xiàn)的分布式鎖也只是一種簡單的實現(xiàn)封字,主要是一種思想黔州。
下一次我會使用zookeeper實現(xiàn)分布式鎖,使用zookeeper的可靠性是要大于使用redis實現(xiàn)的分布式鎖的阔籽,但是相比而言流妻,redis的性能更好。