Redis實(shí)現(xiàn)分布式鎖的幾種方法總結(jié)
Redis實(shí)現(xiàn)分布式鎖的幾種方法總結(jié)
分布式鎖是控制分布式系統(tǒng)之間同步訪問(wèn)共享資源的一種方式。在分布式系統(tǒng)中,常常需要協(xié)調(diào)他們的動(dòng)作。如果不同的系統(tǒng)或是同一個(gè)系統(tǒng)的不同主機(jī)之間共享了一個(gè)或一組資源,那么訪問(wèn)這些資源的時(shí)候,往往需要互斥來(lái)防止彼此干擾來(lái)保證一致性,在這種情況下,便需要使用到分布式鎖。
我們來(lái)假設(shè)一個(gè)最簡(jiǎn)單的秒殺場(chǎng)景:數(shù)據(jù)庫(kù)里有一張表,column分別是商品ID,和商品ID對(duì)應(yīng)的庫(kù)存量,秒殺成功就將此商品庫(kù)存量-1?,F(xiàn)在假設(shè)有1000個(gè)線程來(lái)秒殺兩件商品,500個(gè)線程秒殺第一個(gè)商品,500個(gè)線程秒殺第二個(gè)商品。我們來(lái)根據(jù)這個(gè)簡(jiǎn)單的業(yè)務(wù)場(chǎng)景來(lái)解釋一下分布式鎖。
通常具有秒殺場(chǎng)景的業(yè)務(wù)系統(tǒng)都比較復(fù)雜,承載的業(yè)務(wù)量非常巨大,并發(fā)量也很高。這樣的系統(tǒng)往往采用分布式的架構(gòu)來(lái)均衡負(fù)載。那么這1000個(gè)并發(fā)就會(huì)是從不同的地方過(guò)來(lái),商品庫(kù)存就是共享的資源,也是這1000個(gè)并發(fā)爭(zhēng)搶的資源,這個(gè)時(shí)候我們需要將并發(fā)互斥管理起來(lái)。這就是分布式鎖的應(yīng)用。
1.實(shí)現(xiàn)分布式鎖的幾種方案
1.Redis實(shí)現(xiàn) (推薦)
2.Zookeeper實(shí)現(xiàn)
3.數(shù)據(jù)庫(kù)實(shí)現(xiàn)
Redis實(shí)現(xiàn)分布式鎖 * * 在集群等多服務(wù)器中經(jīng)常使用到同步處理一下業(yè)務(wù),這是普通的事務(wù)是滿足不了業(yè)務(wù)需求,需要分布式鎖 * * 分布式鎖的常用3種實(shí)現(xiàn): * 0.數(shù)據(jù)庫(kù)樂(lè)觀鎖實(shí)現(xiàn) * 1.Redis實(shí)現(xiàn) --- 使用redis的setnx()、get()、getset()方法,用于分布式鎖,解決死鎖問(wèn)題 * 2.Zookeeper實(shí)現(xiàn) * 參考:http://surlymo.iteye.com/blog/2082684 * http://www.dbjr.com.cn/article/103617.htm * http://www.hollischuang.com/archives/1716?utm_source=tuicool&utm_medium=referral * 1、實(shí)現(xiàn)原理: 基于zookeeper瞬時(shí)有序節(jié)點(diǎn)實(shí)現(xiàn)的分布式鎖,其主要邏輯如下(該圖來(lái)自于IBM網(wǎng)站)。大致思想即為:每個(gè)客戶端對(duì)某個(gè)功能加鎖時(shí),在zookeeper上的與該功能對(duì)應(yīng)的指定節(jié)點(diǎn)的目錄下,生成一個(gè)唯一的瞬時(shí)有序節(jié)點(diǎn)。判斷是否獲取鎖的方式很簡(jiǎn)單,只需要判斷有序節(jié)點(diǎn)中序號(hào)最小的一個(gè)。當(dāng)釋放鎖的時(shí)候,只需將這個(gè)瞬時(shí)節(jié)點(diǎn)刪除即可。同時(shí),其可以避免服務(wù)宕機(jī)導(dǎo)致的鎖無(wú)法釋放,而產(chǎn)生的死鎖問(wèn)題。 2、優(yōu)點(diǎn) 鎖安全性高,zk可持久化 3、缺點(diǎn) 性能開(kāi)銷比較高。因?yàn)槠湫枰獎(jiǎng)討B(tài)產(chǎn)生、銷毀瞬時(shí)節(jié)點(diǎn)來(lái)實(shí)現(xiàn)鎖功能。 4、實(shí)現(xiàn) 可以直接采用zookeeper第三方庫(kù)curator即可方便地實(shí)現(xiàn)分布式鎖 * * Redis實(shí)現(xiàn)分布式鎖的原理: * 1.通過(guò)setnx(lock_timeout)實(shí)現(xiàn),如果設(shè)置了鎖返回1, 已經(jīng)有值沒(méi)有設(shè)置成功返回0 * 2.死鎖問(wèn)題:通過(guò)實(shí)踐來(lái)判斷是否過(guò)期,如果已經(jīng)過(guò)期,獲取到過(guò)期時(shí)間get(lockKey),然后getset(lock_timeout)判斷是否和get相同, * 相同則證明已經(jīng)加鎖成功,因?yàn)榭赡軐?dǎo)致多線程同時(shí)執(zhí)行g(shù)etset(lock_timeout)方法,這可能導(dǎo)致多線程都只需getset后,對(duì)于判斷加鎖成功的線程, * 再加expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS)過(guò)期時(shí)間,防止多個(gè)線程同時(shí)疊加時(shí)間,導(dǎo)致鎖時(shí)效時(shí)間翻倍 * 3.針對(duì)集群服務(wù)器時(shí)間不一致問(wèn)題,可以調(diào)用redis的time()獲取當(dāng)前時(shí)間
2.Redis分分布式鎖的代碼實(shí)現(xiàn)
1.定義鎖接口
package com.jay.service.redis;
/**
* Redis分布式鎖接口
* Created by hetiewei on 2017/4/7.
*/
public interface RedisDistributionLock {
/**
* 加鎖成功,返回加鎖時(shí)間
* @param lockKey
* @param threadName
* @return
*/
public long lock(String lockKey, String threadName);
/**
* 解鎖, 需要更新加鎖時(shí)間,判斷是否有權(quán)限
* @param lockKey
* @param lockValue
* @param threadName
*/
public void unlock(String lockKey, long lockValue, String threadName);
/**
* 多服務(wù)器集群,使用下面的方法,代替System.currentTimeMillis(),獲取redis時(shí)間,避免多服務(wù)的時(shí)間不一致問(wèn)題!??!
* @return
*/
public long currtTimeForRedis();
}
2.定義鎖實(shí)現(xiàn)
package com.jay.service.redis.impl;
import com.jay.service.redis.RedisDistributionLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.util.concurrent.TimeUnit;
/**
* Created by hetiewei on 2017/4/7.
*/
public class RedisLockImpl implements RedisDistributionLock {
//加鎖超時(shí)時(shí)間,單位毫秒, 即:加鎖時(shí)間內(nèi)執(zhí)行完操作,如果未完成會(huì)有并發(fā)現(xiàn)象
private static final long LOCK_TIMEOUT = 5*1000;
private static final Logger LOG = LoggerFactory.getLogger(RedisLockImpl.class);
private StringRedisTemplate redisTemplate;
public RedisLockImpl(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 加鎖
* 取到鎖加鎖,取不到鎖一直等待知道獲得鎖
* @param lockKey
* @param threadName
* @return
*/
@Override
public synchronized long lock(String lockKey, String threadName) {
LOG.info(threadName+"開(kāi)始執(zhí)行加鎖");
while (true){ //循環(huán)獲取鎖
//鎖時(shí)間
Long lock_timeout = currtTimeForRedis()+ LOCK_TIMEOUT +1;
if (redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
//定義序列化方式
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
byte[] value = serializer.serialize(lock_timeout.toString());
boolean flag = redisConnection.setNX(lockKey.getBytes(), value);
return flag;
}
})){
//如果加鎖成功
LOG.info(threadName +"加鎖成功 ++++ 111111");
//設(shè)置超時(shí)時(shí)間,釋放內(nèi)存
redisTemplate.expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS);
return lock_timeout;
}else {
//獲取redis里面的時(shí)間
String result = redisTemplate.opsForValue().get(lockKey);
Long currt_lock_timeout_str = result==null?null:Long.parseLong(result);
//鎖已經(jīng)失效
if (currt_lock_timeout_str != null && currt_lock_timeout_str < System.currentTimeMillis()){
//判斷是否為空,不為空時(shí),說(shuō)明已經(jīng)失效,如果被其他線程設(shè)置了值,則第二個(gè)條件判斷無(wú)法執(zhí)行
//獲取上一個(gè)鎖到期時(shí)間,并設(shè)置現(xiàn)在的鎖到期時(shí)間
Long old_lock_timeout_Str = Long.valueOf(redisTemplate.opsForValue().getAndSet(lockKey, lock_timeout.toString()));
if (old_lock_timeout_Str != null && old_lock_timeout_Str.equals(currt_lock_timeout_str)){
//多線程運(yùn)行時(shí),多個(gè)線程簽好都到了這里,但只有一個(gè)線程的設(shè)置值和當(dāng)前值相同,它才有權(quán)利獲取鎖
LOG.info(threadName + "加鎖成功 ++++ 22222");
//設(shè)置超時(shí)間,釋放內(nèi)存
redisTemplate.expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS);
//返回加鎖時(shí)間
return lock_timeout;
}
}
}
try {
LOG.info(threadName +"等待加鎖, 睡眠100毫秒");
// TimeUnit.MILLISECONDS.sleep(100);
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 解鎖
* @param lockKey
* @param lockValue
* @param threadName
*/
@Override
public synchronized void unlock(String lockKey, long lockValue, String threadName) {
LOG.info(threadName + "執(zhí)行解鎖==========");//正常直接刪除 如果異常關(guān)閉判斷加鎖會(huì)判斷過(guò)期時(shí)間
//獲取redis中設(shè)置的時(shí)間
String result = redisTemplate.opsForValue().get(lockKey);
Long currt_lock_timeout_str = result ==null?null:Long.valueOf(result);
//如果是加鎖者,則刪除鎖, 如果不是,則等待自動(dòng)過(guò)期,重新競(jìng)爭(zhēng)加鎖
if (currt_lock_timeout_str !=null && currt_lock_timeout_str == lockValue){
redisTemplate.delete(lockKey);
LOG.info(threadName + "解鎖成功------------------");
}
}
/**
* 多服務(wù)器集群,使用下面的方法,代替System.currentTimeMillis(),獲取redis時(shí)間,避免多服務(wù)的時(shí)間不一致問(wèn)題?。?!
* @return
*/
@Override
public long currtTimeForRedis(){
return redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
return redisConnection.time();
}
});
}
}
3.分布式鎖驗(yàn)證
@RestController
@RequestMapping("/distribution/redis")
public class RedisLockController {
private static final String LOCK_NO = "redis_distribution_lock_no_";
private static int i = 0;
private ExecutorService service;
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 模擬1000個(gè)線程同時(shí)執(zhí)行業(yè)務(wù),修改資源
*
* 使用線程池定義了20個(gè)線程
*
*/
@GetMapping("lock1")
public void testRedisDistributionLock1(){
service = Executors.newFixedThreadPool(20);
for (int i=0;i<1000;i++){
service.execute(new Runnable() {
@Override
public void run() {
task(Thread.currentThread().getName());
}
});
}
}
@GetMapping("/{key}")
public String getValue(@PathVariable("key") String key){
Serializable result = redisTemplate.opsForValue().get(key);
return result.toString();
}
private void task(String name) {
// System.out.println(name + "任務(wù)執(zhí)行中"+(i++));
//創(chuàng)建一個(gè)redis分布式鎖
RedisLockImpl redisLock = new RedisLockImpl(redisTemplate);
//加鎖時(shí)間
Long lockTime;
if ((lockTime = redisLock.lock((LOCK_NO+1)+"", name))!=null){
//開(kāi)始執(zhí)行任務(wù)
System.out.println(name + "任務(wù)執(zhí)行中"+(i++));
//任務(wù)執(zhí)行完畢 關(guān)閉鎖
redisLock.unlock((LOCK_NO+1)+"", lockTime, name);
}
}
}
4.結(jié)果驗(yàn)證:
在Controller中模擬了1000個(gè)線程,通過(guò)線程池方式提交,每次20個(gè)線程搶占分布式鎖,搶到分布式鎖的執(zhí)行代碼,沒(méi)搶到的等待
結(jié)果如下:
2017-04-07 16:27:17.385 INFO 8652 --- [pool-2-thread-4] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-4等待加鎖, 睡眠100毫秒
2017-04-07 16:27:17.385 INFO 8652 --- [pool-2-thread-7] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-7解鎖成功------------------
2017-04-07 16:27:17.391 INFO 8652 --- [pool-2-thread-5] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-5加鎖成功 ++++ 111111
pool-2-thread-5任務(wù)執(zhí)行中994
2017-04-07 16:27:17.391 INFO 8652 --- [pool-2-thread-5] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-5執(zhí)行解鎖==========
2017-04-07 16:27:17.391 INFO 8652 --- [pool-2-thread-1] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-1等待加鎖, 睡眠100毫秒
2017-04-07 16:27:17.391 INFO 8652 --- [pool-2-thread-5] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-5解鎖成功------------------
2017-04-07 16:27:17.397 INFO 8652 --- [pool-2-thread-6] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-6加鎖成功 ++++ 111111
pool-2-thread-6任務(wù)執(zhí)行中995
2017-04-07 16:27:17.398 INFO 8652 --- [pool-2-thread-6] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-6執(zhí)行解鎖==========
2017-04-07 16:27:17.398 INFO 8652 --- [pool-2-thread-6] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-6解鎖成功------------------
2017-04-07 16:27:17.400 INFO 8652 --- [ool-2-thread-19] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-19加鎖成功 ++++ 111111
pool-2-thread-19任務(wù)執(zhí)行中996
2017-04-07 16:27:17.400 INFO 8652 --- [ool-2-thread-19] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-19執(zhí)行解鎖==========
2017-04-07 16:27:17.400 INFO 8652 --- [ool-2-thread-19] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-19解鎖成功------------------
2017-04-07 16:27:17.571 INFO 8652 --- [ool-2-thread-11] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-11加鎖成功 ++++ 111111
pool-2-thread-11任務(wù)執(zhí)行中997
2017-04-07 16:27:17.572 INFO 8652 --- [ool-2-thread-11] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-11執(zhí)行解鎖==========
2017-04-07 16:27:17.572 INFO 8652 --- [ool-2-thread-11] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-11解鎖成功------------------
2017-04-07 16:27:17.585 INFO 8652 --- [pool-2-thread-4] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-4加鎖成功 ++++ 111111
pool-2-thread-4任務(wù)執(zhí)行中998
2017-04-07 16:27:17.586 INFO 8652 --- [pool-2-thread-4] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-4執(zhí)行解鎖==========
2017-04-07 16:27:17.586 INFO 8652 --- [pool-2-thread-4] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-4解鎖成功------------------
2017-04-07 16:27:17.591 INFO 8652 --- [pool-2-thread-1] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-1加鎖成功 ++++ 111111
pool-2-thread-1任務(wù)執(zhí)行中999
2017-04-07 16:27:17.591 INFO 8652 --- [pool-2-thread-1] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-1執(zhí)行解鎖==========
2017-04-07 16:27:17.591 INFO 8652 --- [pool-2-thread-1] c.jay.service.redis.impl.RedisLockImpl : pool-2-thread-1解鎖成功------------------
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- SpringBoot集成redis與session實(shí)現(xiàn)分布式單點(diǎn)登錄
- redis中使用java腳本實(shí)現(xiàn)分布式鎖
- 基于Redis實(shí)現(xiàn)分布式鎖以及任務(wù)隊(duì)列
- Redis分布式鎖的實(shí)現(xiàn)方式(redis面試題)
- Redis實(shí)現(xiàn)分布式鎖的五種方法詳解
- 淺談Redis分布式鎖的正確實(shí)現(xiàn)方式
- Redis分布式鎖實(shí)現(xiàn)方式及超時(shí)問(wèn)題解決
- redis分布式鎖及會(huì)出現(xiàn)的問(wèn)題解決
- Redis基于Session實(shí)現(xiàn)分布式登錄的示例代碼
相關(guān)文章
記錄一次并發(fā)情況下的redis導(dǎo)致服務(wù)假死的問(wèn)題解決
由于Redis需要依賴于操作系統(tǒng)環(huán)境,如果系統(tǒng)資源受限,比如過(guò)量的進(jìn)程在擠占系統(tǒng)資源、系統(tǒng)死鎖等情況,本文主要介紹了記錄一次并發(fā)情況下的redis導(dǎo)致服務(wù)假死的問(wèn)題解決,感興趣的可以了解一下2023-09-09
大家都應(yīng)該知道的Redis過(guò)期鍵與過(guò)期策略
這篇文章主要給大家介紹了一些應(yīng)該知道的Redis過(guò)期鍵與過(guò)期策略的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
淺談Redis如何應(yīng)對(duì)并發(fā)訪問(wèn)
本文主要介紹了Redis如何應(yīng)對(duì)并發(fā)訪問(wèn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
在Centos?8.0中安裝Redis服務(wù)器的教程詳解
由于考慮到linux服務(wù)器的性能,所以經(jīng)常需要把一些中間件安裝在linux服務(wù)上,今天通過(guò)本文給大家介紹下在Centos?8.0中安裝Redis服務(wù)器的詳細(xì)過(guò)程,感興趣的朋友一起看看吧2022-03-03

