欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報激活碼778899分享:Redis-分布式鎖

柚子快報激活碼778899分享:Redis-分布式鎖

http://yzkb.51969.com/

什么是分布式鎖?

分布式鎖:滿足分布式系統(tǒng)或集群模式下多進程可見并且互斥的鎖。

分布式鎖的核心思想就是讓大家都使用同一把鎖,只要大家使用的是同一把鎖,那么我們就能鎖住線程,不讓線程進行,讓程序串行執(zhí)行,這就是分布式鎖的核心思路

分布式鎖應(yīng)該滿足一些什么樣的條件呢?

可見性:多個線程都能看到相同的結(jié)果,注意:這個地方說的可見性并不是并發(fā)編程中指的內(nèi)存可見性,只是說多個進程之間都能感知到變化的意思

互斥:互斥是分布式鎖的最基本的條件,使得程序串行執(zhí)行

高可用:程序不易崩潰,時時刻刻都保證較高的可用性

高性能:由于加鎖本身就讓性能降低,所有對于分布式鎖本身需要他就較高的加鎖性能和釋放鎖性能

安全性:安全也是程序中必不可少的一環(huán)

常見的分布式鎖

Mysql:mysql本身就帶有鎖機制,但是由于mysql性能本身一般,所以采用分布式鎖的情況下,其實使用mysql作為分布式鎖比較少見

Redis:redis作為分布式鎖是非常常見的一種使用方式,現(xiàn)在企業(yè)級開發(fā)中基本都使用redis或者zookeeper作為分布式鎖,利用setnx這個方法,如果插入key成功,則表示獲得到了鎖,如果有人插入成功,其他人插入失敗則表示無法獲得到鎖,利用這套邏輯來實現(xiàn)分布式鎖

Zookeeper:Zookeeper 是一個開源的分布式協(xié)調(diào)服務(wù),用于分布式應(yīng)用程序的協(xié)調(diào)。它提供了一種簡單而高效的方式來實現(xiàn)分布式系統(tǒng)中的數(shù)據(jù)一致性和協(xié)調(diào)。

Redis實現(xiàn)分布式鎖的核心思路

實現(xiàn)分布式鎖時需要實現(xiàn)的兩個基本方法:

獲取鎖:

互斥:確保只能有一個線程獲取鎖 非阻塞:嘗試一次,成功返回true,失敗返回false 釋放鎖:

手動釋放 超時釋放:獲取鎖時添加一個超時時間

核心思路:

SETNX?:SETNX?命令是redis中一個添加數(shù)據(jù)的命令,SETNX?只有在指定的key不存在時才會設(shè)置key的值。如果key已經(jīng)存在,命令不會進行任何操作。如果成功設(shè)置key值,返回1,反之返回0

我們利用redis 的setNx 方法,當(dāng)有多個線程進入時,第一個線程嘗試獲取鎖,搶到之后執(zhí)行義務(wù),然后再釋放鎖(刪除鎖),而沒有搶到鎖的線程則等待一定時間后再重試即可? ?

誤刪情況以及解決方案

說明:

持有鎖的線程在鎖的內(nèi)部出現(xiàn)了阻塞,導(dǎo)致他的鎖自動釋放(超時釋放),這時其他線程,線程2來嘗試獲得鎖,就拿到了這把鎖,然后線程2在持有鎖執(zhí)行過程中,線程1反應(yīng)過來,繼續(xù)執(zhí)行,而線程1執(zhí)行過程中,走到了刪除鎖邏輯,此時就會把本應(yīng)該屬于線程2的鎖進行刪除,這就是誤刪別人鎖的情況

解決方案:解決方案就是在每個線程釋放鎖的時候,去判斷一下當(dāng)前這把鎖是否屬于自己,如果屬于自己,則不進行鎖的刪除,那么怎么進行鎖的辨識呢:不同線程設(shè)置屬于自己的鎖的value即可,比如將線程id作為value值,即可達到辨識鎖身份的效果

分布式鎖的原子性問題以及解決方案

更為極端的誤刪邏輯說明:

線程1現(xiàn)在持有鎖之后,在執(zhí)行業(yè)務(wù)邏輯過程中,他正準(zhǔn)備刪除鎖,而且已經(jīng)走到了條件判斷的過程中,比如他已經(jīng)拿到了當(dāng)前這把鎖確實是屬于他自己的,正準(zhǔn)備刪除鎖,但是此時他的鎖到期了,那么此時線程2進來,但是線程1他會接著往后執(zhí)行,當(dāng)他卡頓結(jié)束后,他直接就會執(zhí)行刪除鎖那行代碼,也就是說線程1在即將刪除鎖時恰巧卡頓了,此時線程2恰巧進來獲取了一把鎖,在線程2獲取完之后線程1恢復(fù)了直接把線程2的鎖給刪掉,這就是刪鎖時的原子性問題,之所以有這個問題,是因為線程1的拿鎖,識鎖,刪鎖,實際上并不是原子性的

解決方案:

使用lua腳本

Redis提供了Lua腳本功能,在一個腳本中編寫多條Redis命令,確保多條命令執(zhí)行時的原子性。Lua是一種編程語言,它的基本語法大家可以參考網(wǎng)站:Lua 教程 | 菜鳥教程

這里重點介紹lua腳本中Redis提供的調(diào)用函數(shù),語法如下:

redis.call('命令名稱', 'key', '其它參數(shù)', ...)

例如,我們要執(zhí)行set name jack,則腳本是這樣:

# 執(zhí)行 set name jack

redis.call('set', 'name', 'jack')

操作redis的拿鎖識鎖刪鎖的lua腳本:

-- 這里的 KEYS[1] 就是鎖的key,這里的ARGV[1] 就是當(dāng)前線程標(biāo)示

-- 獲取鎖中的標(biāo)示,判斷是否與當(dāng)前線程標(biāo)示一致

if (redis.call('GET', KEYS[1]) == ARGV[1]) then

-- 一致,則刪除鎖

return redis.call('DEL', KEYS[1])

end

-- 不一致,則直接返回

return 0

將寫好的腳本放到Resource下,然后再掉用即可:

private static final DefaultRedisScript UNLOCK_SCRIPT;

static {

UNLOCK_SCRIPT = new DefaultRedisScript<>();

UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));

UNLOCK_SCRIPT.setResultType(Long.class);

}

public void unlock() {

// 調(diào)用lua腳本

stringRedisTemplate.execute(

UNLOCK_SCRIPT,

Collections.singletonList(KEY_PREFIX + name),

ID_PREFIX + Thread.currentThread().getId());

}

經(jīng)過以上代碼改造后,我們就能夠?qū)崿F(xiàn) 拿鎖比鎖刪鎖的原子性動作了~

小總結(jié):

基于Redis的分布式鎖實現(xiàn)思路:

利用set nx ex獲取鎖,并設(shè)置過期時間,保存線程標(biāo)示 釋放鎖時先判斷線程標(biāo)示是否與自己一致,一致則刪除鎖

特性:

利用set nx滿足互斥性 利用set ex保證故障時鎖依然能釋放,避免死鎖,提高安全性 利用Redis集群保證高可用和高并發(fā)特性 利用lua腳本確保一致性

分布式鎖-redission

基于setnx實現(xiàn)的分布式鎖存在下面的問題:

重入問題:重入問題是指 獲得鎖的線程可以再次進入到相同的鎖的代碼塊中,可重入鎖的意義在于防止死鎖,比如HashTable這樣的代碼中,他的方法都是使用synchronized修飾的,假如他在一個方法內(nèi),調(diào)用另一個方法,那么此時如果是不可重入的,不就死鎖了嗎?所以可重入鎖他的主要意義是防止死鎖,我們的synchronized和Lock鎖都是可重入的。

不可重試:是指目前的分布式只能嘗試一次,我們認(rèn)為合理的情況是:當(dāng)線程在獲得鎖失敗后,他應(yīng)該能再次嘗試獲得鎖。

超時釋放:我們在加鎖時增加了過期時間,這樣的我們可以防止死鎖,但是如果卡頓的時間超長,雖然我們采用了lua表達式防止刪鎖的時候,誤刪別人的鎖,但是畢竟沒有鎖住,有安全隱患

主從一致性: 如果Redis提供了主從集群,當(dāng)我們向集群寫數(shù)據(jù)時,主機需要異步的將數(shù)據(jù)同步給從機,而萬一在同步過去之前,主機宕機了,就會出現(xiàn)死鎖問題。

那么什么是Redission呢

Redisson是一個在Redis的基礎(chǔ)上實現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid)。它不僅提供了一系列的分布式的Java常用對象,還提供了許多分布式服務(wù),其中就包含了各種分布式鎖的實現(xiàn)。

Redission提供了分布式鎖的多種多樣的功能

Redission依賴:

org.redisson

redisson

3.13.6

?配置Redisson客戶端:

@Configuration

public class RedissonConfig {

@Bean

public RedissonClient redissonClient(){

// 配置

Config config = new Config();

config.useSingleServer().setAddress("redis://192.168.150.101:6379")

.setPassword("123321");

// 創(chuàng)建RedissonClient對象

return Redisson.create(config);

}

}

如何使用Redission的分布式鎖:

@Resource

private RedissionClient redissonClient;

@Test

void testRedisson() throws Exception{

//獲取鎖(可重入),指定鎖的名稱

RLock lock = redissonClient.getLock("anyLock");

//嘗試獲取鎖,參數(shù)分別是:獲取鎖的最大等待時間(期間會重試),鎖自動釋放時間,時間單位

boolean isLock = lock.tryLock(1,10,TimeUnit.SECONDS);

//判斷獲取鎖成功

if(isLock){

try{

System.out.println("執(zhí)行業(yè)務(wù)");

}finally{

//釋放鎖

lock.unlock();

}

}

}

分布式鎖-redission可重入鎖原理

在Lock鎖中,他是借助于底層的一個voaltile的一個state變量來記錄重入的狀態(tài)的,比如當(dāng)前沒有人持有這把鎖,那么state=0,假如有人持有這把鎖,那么state=1,如果持有這把鎖的人再次持有這把鎖,那么state就會+1 ,如果是對于synchronized而言,他在c語言代碼中會有一個count,原理和state類似,也是重入一次就加一,釋放一次就-1 ,直到減少成0 時,表示當(dāng)前這把鎖沒有被人持有。

在redission中,我們的也支持支持可重入鎖

在分布式鎖中,他采用hash結(jié)構(gòu)用來存儲鎖,其中大key表示表示這把鎖是否存在,用小key表示當(dāng)前這把鎖被哪個線程持有,所以接下來我們一起分析一下當(dāng)前的這個lua表達式

這個地方一共有3個參數(shù)

KEYS[1] : 鎖名稱

ARGV[1]: 鎖失效時間

ARGV[2]: id + ":" + threadId; 鎖的小key

exists: 判斷數(shù)據(jù)是否存在 name:是lock是否存在,如果==0,就表示當(dāng)前這把鎖不存在

redis.call('hset', KEYS[1], ARGV[2], 1);此時他就開始往redis里邊去寫數(shù)據(jù) ,寫成一個hash結(jié)構(gòu)

Lock{

id + ":" + threadId : 1

}

如果當(dāng)前這把鎖存在,則第一個條件不滿足,再判斷

redis.call('hexists', KEYS[1], ARGV[2]) == 1

此時需要通過大key+小key判斷當(dāng)前這把鎖是否是屬于自己的,如果是自己的,則進行

redis.call('hincrby', KEYS[1], ARGV[2], 1)

將當(dāng)前這個鎖的value進行+1 ,redis.call('pexpire', KEYS[1], ARGV[1]); 然后再對其設(shè)置過期時間,如果以上兩個條件都不滿足,則表示當(dāng)前這把鎖搶鎖失敗,最后返回pttl,即為當(dāng)前這把鎖的失效時間

如果小伙幫們看了前邊的源碼, 你會發(fā)現(xiàn)他會去判斷當(dāng)前這個方法的返回值是否為null,如果是null,則對應(yīng)則前兩個if對應(yīng)的條件,退出搶鎖邏輯,如果返回的不是null,即走了第三個分支,在源碼處會進行while(true)的自旋搶鎖。

"if (redis.call('exists', KEYS[1]) == 0) then " +

"redis.call('hset', KEYS[1], ARGV[2], 1); " +

"redis.call('pexpire', KEYS[1], ARGV[1]); " +

"return nil; " +

"end; " +

"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +

"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +

"redis.call('pexpire', KEYS[1], ARGV[1]); " +

"return nil; " +

"end; " +

"return redis.call('pttl', KEYS[1]);"

分布式鎖-redission鎖重試和WatchDog機制

說明:由于課程中已經(jīng)說明了有關(guān)tryLock的源碼解析以及其看門狗原理,所以筆者在這里給大家分析lock()方法的源碼解析,希望大家在學(xué)習(xí)過程中,能夠掌握更多的知識

搶鎖過程中,獲得當(dāng)前線程,通過tryAcquire進行搶鎖,該搶鎖邏輯和之前邏輯相同

1、先判斷當(dāng)前這把鎖是否存在,如果不存在,插入一把鎖,返回null

2、判斷當(dāng)前這把鎖是否是屬于當(dāng)前線程,如果是,則返回null

所以如果返回是null,則代表著當(dāng)前這哥們已經(jīng)搶鎖完畢,或者可重入完畢,但是如果以上兩個條件都不滿足,則進入到第三個條件,返回的是鎖的失效時間

long threadId = Thread.currentThread().getId();

Long ttl = tryAcquire(-1, leaseTime, unit, threadId);

// lock acquired

if (ttl == null) {

return;

}

接下來會有一個條件分支,因為lock方法有重載方法,一個是帶參數(shù),一個是不帶參數(shù),如果不帶參數(shù)傳入的值是-1,如果傳入?yún)?shù),則leaseTime是他本身,所以如果傳入了參數(shù),此時leaseTime != -1 則會進去搶鎖,搶鎖的邏輯就是之前說的那三個邏輯

如果是沒有傳入時間,則此時也會進行搶鎖, 而且搶鎖時間是默認(rèn)看門狗時間

commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout()

ttlRemainingFuture.onComplete((ttlRemaining, e)

這句話相當(dāng)于對以上搶鎖進行了監(jiān)聽,也就是說當(dāng)上邊搶鎖完畢后,此方法會被調(diào)用,具體調(diào)用的邏輯就是去后臺開啟一個線程,進行續(xù)約邏輯,也就是看門狗線程

RFuture ttlRemainingFuture = tryLockInnerAsync(waitTime,

commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),

TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);

ttlRemainingFuture.onComplete((ttlRemaining, e) -> {

if (e != null) {

return;

}

// lock acquired

if (ttlRemaining == null) {

scheduleExpirationRenewal(threadId);

}

});

return ttlRemainingFuture;

此邏輯就是續(xù)約邏輯,注意看commandExecutor.getConnectionManager().newTimeout() 此方法

Method( new TimerTask() {},參數(shù)2 ,參數(shù)3 )

指的是:通過參數(shù)2,參數(shù)3 去描述什么時候去做參數(shù)1的事情,現(xiàn)在的情況是:10s之后去做參數(shù)一的事情

因為鎖的失效時間是30s,當(dāng)10s之后,此時這個timeTask 就觸發(fā)了,他就去進行續(xù)約,把當(dāng)前這把鎖續(xù)約成30s,如果操作成功,那么此時就會遞歸調(diào)用自己,再重新設(shè)置一個timeTask(),于是再過10s后又再設(shè)置一個timerTask,完成不停的續(xù)約

那么大家可以想一想,假設(shè)我們的線程出現(xiàn)了宕機他還會續(xù)約嗎?當(dāng)然不會,因為沒有人再去調(diào)用renewExpiration這個方法,所以等到時間之后自然就釋放了。

private void renewExpiration() {

ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());

if (ee == null) {

return;

}

Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {

@Override

public void run(Timeout timeout) throws Exception {

ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());

if (ent == null) {

return;

}

Long threadId = ent.getFirstThreadId();

if (threadId == null) {

return;

}

RFuture future = renewExpirationAsync(threadId);

future.onComplete((res, e) -> {

if (e != null) {

log.error("Can't update lock " + getName() + " expiration", e);

return;

}

if (res) {

// reschedule itself

renewExpiration();

}

});

}

}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

ee.setTimeout(task);

}

分布式鎖-redission鎖的MutiLock原理

為了提高redis的可用性,我們會搭建集群或者主從,現(xiàn)在以主從為例

此時我們?nèi)懨?,寫在主機上, 主機會將數(shù)據(jù)同步給從機,但是假設(shè)在主機還沒有來得及把數(shù)據(jù)寫入到從機去的時候,此時主機宕機,哨兵會發(fā)現(xiàn)主機宕機,并且選舉一個slave變成master,而此時新的master中實際上并沒有鎖信息,此時鎖信息就已經(jīng)丟掉了。

為了解決這個問題,redission提出來了MutiLock鎖,使用這把鎖咱們就不使用主從了,每個節(jié)點的地位都是一樣的, 這把鎖加鎖的邏輯需要寫入到每一個主叢節(jié)點上,只有所有的服務(wù)器都寫入成功,此時才是加鎖成功,假設(shè)現(xiàn)在某個節(jié)點掛了,那么他去獲得鎖的時候,只要有一個節(jié)點拿不到,都不能算是加鎖成功,就保證了加鎖的可靠性。 ?

柚子快報激活碼778899分享:Redis-分布式鎖

http://yzkb.51969.com/

相關(guān)文章

評論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。

轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/19544232.html

發(fā)布評論

您暫未設(shè)置收款碼

請在主題配置——文章設(shè)置里上傳

掃描二維碼手機訪問

文章目錄