www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁 > 單片機(jī) > 架構(gòu)師社區(qū)
[導(dǎo)讀]本文中案例都會在上傳到git上,請放心瀏覽 git地址:https://github.com/muxiaonong/Spring-Cloud/tree/master/order-lock 本文會使用到 三臺 redis 獨立服務(wù)器,可以自行提前搭建好 前言 在Java中,我們對于鎖會比較熟悉,常用的有 synchronized、Lock鎖,在


本文中案例都會在上傳到git上,請放心瀏覽 git地址:https://github.com/muxiaonong/Spring-Cloud/tree/master/order-lock 本文會使用到 三臺 redis 獨立服務(wù)器,可以自行提前搭建好

前言

在Java中,我們對于鎖會比較熟悉,常用的有 synchronized、Lock鎖,在java并發(fā)編程中,我們通過鎖,來實現(xiàn)當(dāng)多個線程競爭同一個共享資源或者變量而造成的數(shù)據(jù)不一致的問題,但是JVM鎖只能針對于單個應(yīng)用服務(wù),隨著我們業(yè)務(wù)的發(fā)展需要,單體單機(jī)部署的系統(tǒng)早已演化成分布式系統(tǒng),由于分布式系統(tǒng)的多線程、多進(jìn)程而且分布在不同的機(jī)器上,這個時候JVM鎖的并發(fā)控制就沒有效果了,為了解決跨JVM鎖并且能夠控制共享資源的訪問,于是有了分布式鎖的誕生。

掌握Redis分布式鎖的正確姿勢

什么是分布式鎖

分布式鎖是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。在分布式系統(tǒng)中,常常需要協(xié)調(diào)他們的動作。如果不同的系統(tǒng)或是同一個系統(tǒng)的不同主機(jī)之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分布式鎖

為什么JVM鎖在分布式下不可以呢?

我們通過代碼來看一下就知道,為什么集群下jvm鎖是不可靠的呢?我們模擬一下商品搶購的場景,A服務(wù)有十個用戶去搶購這個商品,B服務(wù)有十個用戶去搶購這個商品,當(dāng)有其中一個用戶搶購成功后,其他用戶不可以在對這個商品進(jìn)行下單操作,那么到底是A服務(wù)會搶到還是B服務(wù)會搶到這個商品呢,我們來看一下

當(dāng)其中有一個用戶搶購成功后,status會變成1

掌握Redis分布式鎖的正確姿勢

GrabService:

public interface GrabService {
/** * 商品搶單 * @param orderId * @param driverId * @return */ public ResponseResult grabOrder(int orderId, int driverId);}

GrabJvmLockServiceImpl:

@Service("grabJvmLockService")public class GrabJvmLockServiceImpl implements GrabService {
@Autowired OrderService orderService;
@Override public ResponseResult grabOrder(int orderId, int driverId) { String lock = (orderId+"");
synchronized (lock.intern()) { try { System.out.println("用戶:"+driverId+" 執(zhí)行下單邏輯");
boolean b = orderService.grab(orderId, driverId); if(b) { System.out.println("用戶:"+driverId+" 下單成功"); }else { System.out.println("用戶:"+driverId+" 下單失敗"); } } finally {
} } return null; }}

OrderService :

public interface OrderService {  public boolean grab(int orderId, int driverId);}

OrderServiceImpl :

@Servicepublic class OrderServiceImpl implements OrderService {
@Autowired private OrderMapper mapper;
public boolean grab(int orderId, int driverId) { Order order = mapper.selectByPrimaryKey(orderId); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(order.getStatus().intValue() == 0) { order.setStatus(1); mapper.updateByPrimaryKeySelective(order);
return true; } return false;
}}

這里我們模擬集群環(huán)境,啟動兩個端口,8004和8005進(jìn)行訪問 這里我們用jmeter進(jìn)行測試 如果不會jmeter的可以看我之前對tomcat進(jìn)行壓測的文章:tomcat優(yōu)化

項目啟動順序:先啟動 Server-eureka注冊中心、在啟動 8004和8005端口

掌握Redis分布式鎖的正確姿勢掌握Redis分布式鎖的正確姿勢測試結(jié)果:掌握Redis分布式鎖的正確姿勢這里我們可以看到 8004 服務(wù)和 8005 服務(wù) 同時都有一個用戶去下單成功這個商品,但是這個商品只能有一個用戶能夠去搶到,因此jvm鎖如果是在集群或分布式下,是無法保證訪問共享變量的數(shù)據(jù)同時只有一個線程訪問的,無法解決分布式,集群環(huán)境的問題。所以需要使用到分布鎖。

分布式鎖三種實現(xiàn)方式

分布式鎖的實現(xiàn)方式總共有三種:

  • 基于數(shù)據(jù)庫實現(xiàn)分布式鎖

  • 基于緩存(Redis)實現(xiàn)分布式鎖

  • 基于Zookeeper實現(xiàn)分布式鎖

今天,我們主要講的是基于Redis實現(xiàn)的分布式鎖

reids實現(xiàn)分布式鎖有三種方式

1、基于redis的 SETNX 實現(xiàn)分布式鎖 2、Redisson實現(xiàn)分布式鎖 4、使用redLock實現(xiàn)分布式鎖

目錄結(jié)構(gòu):

掌握Redis分布式鎖的正確姿勢

方式一:基于 SETNX 實現(xiàn)分布式鎖

將key的值設(shè)為value ,當(dāng)且僅當(dāng)key不存在。若給定的key已經(jīng)存在,則SETNX不做任何動作。setnx:當(dāng)key存在,不做任何操作,key不存在,才設(shè)置

加鎖:

SET orderId driverId NX PX 30000 上面的命令如果執(zhí)行成功,則客戶端成功獲取到了鎖,接下來就可以訪問共享資源了;而如果上面的命令執(zhí)行失敗,則說明獲取鎖失敗。

釋放鎖:關(guān)鍵,判斷是不是自己加的鎖。

GrabService :

public interface GrabService {
/** * 商品搶單 * @param orderId * @param driverId * @return */ public ResponseResult grabOrder(int orderId, int driverId);}

GrabRedisLockServiceImpl :

@Service("grabRedisLockService")public class GrabRedisLockServiceImpl implements GrabService {
@Autowired StringRedisTemplate stringRedisTemplate;
@Autowired OrderService orderService;
@Override public ResponseResult grabOrder(int orderId , int driverId){ //生成key String lock = "order_"+(orderId+""); /* * 情況一,如果鎖沒執(zhí)行到釋放,比如業(yè)務(wù)邏輯執(zhí)行一半,運維重啟服務(wù),或 服務(wù)器掛了,沒走 finally,怎么辦? * 加超時時間 */// boolean lockStatus = stringRedisTemplate.opsForValue().setIfAbsent(lock.intern(), driverId+"");// if(!lockStatus) {// return null;// }
/* * 情況二:加超時時間,會有加不上的情況,運維重啟 */// boolean lockStatus = stringRedisTemplate.opsForValue().setIfAbsent(lock.intern(), driverId+"");// stringRedisTemplate.expire(lock.intern(), 30L, TimeUnit.SECONDS);// if(!lockStatus) {// return null;// }
/* * 情況三:超時時間應(yīng)該一次加,不應(yīng)該分2行代碼, * */ boolean lockStatus = stringRedisTemplate.opsForValue().setIfAbsent(lock.intern(), driverId+"", 30L, TimeUnit.SECONDS); if(!lockStatus) { return null; }
try { System.out.println("用戶:"+driverId+" 執(zhí)行搶單邏輯");
boolean b = orderService.grab(orderId, driverId); if(b) { System.out.println("用戶:"+driverId+" 搶單成功"); }else { System.out.println("用戶:"+driverId+" 搶單失敗"); }
} finally { /** * 這種釋放鎖有,可能釋放了別人的鎖。 */// stringRedisTemplate.delete(lock.intern());
/** * 下面代碼避免釋放別人的鎖 */ if((driverId+"").equals(stringRedisTemplate.opsForValue().get(lock.intern()))) { stringRedisTemplate.delete(lock.intern()); } } return null; }}


這里可能會有人問,如果我業(yè)務(wù)的執(zhí)行時間超過了鎖釋放的時間,會怎么辦呢?我們可以使用守護(hù)線程,只要我們當(dāng)前線程還持有這個鎖,到了10S的時候,守護(hù)線程會自動對該線程進(jìn)行加時操作,會續(xù)上30S的過期時間,直到把鎖釋放,就不會在進(jìn)行續(xù)約了,開啟一個子線程,原來時間是N,每隔N/3,在去續(xù)上N

關(guān)注點:

  1. key,是我們的要鎖的目標(biāo),比如訂單ID。

  2. driverId 是由我們的商品ID,它要保證在足夠長的一段時間內(nèi)在所有客戶端的所有獲取鎖的請求中都是唯一的。即一個訂單被一個用戶搶。

  3. NX表示只有當(dāng)orderId不存在的時候才能SET成功。這保證了只有第一個請求的客戶端才能獲得鎖,而其它客戶端在鎖被釋放之前都無法獲得鎖。

  4. PX 30000表示這個鎖有一個30秒的自動過期時間。當(dāng)然,這里30秒只是一個例子,客戶端可以選擇合適的過期時間。

  5. 這個鎖必須要設(shè)置一個過期時間。 否則的話,當(dāng)一個客戶端獲取鎖成功之后,假如它崩潰了,或者由于發(fā)生了網(wǎng)絡(luò)分區(qū),導(dǎo)致它再也無法和Redis節(jié)點通信了,那么它就會一直持有這個鎖,而其它客戶端永遠(yuǎn)無法獲得鎖了。antirez在后面的分析中也特別強(qiáng)調(diào)了這一點,而且把這個過期時間稱為鎖的有效時間(lock validity time)。獲得鎖的客戶端必須在這個時間之內(nèi)完成對共享資源的訪問。

  6. 此操作不能分割。>SETNX orderId driverId EXPIRE orderId 30 雖然這兩個命令和前面算法描述中的一個SET命令執(zhí)行效果相同,但卻不是原子的。如果客戶端在執(zhí)行完SETNX后崩潰了,那么就沒有機(jī)會執(zhí)行EXPIRE了,導(dǎo)致它一直持有這個鎖。造成死鎖。

方式二:基于redisson實現(xiàn)分布式鎖

流程圖:掌握Redis分布式鎖的正確姿勢代碼實現(xiàn):

@Service("grabRedisRedissonService")public class GrabRedisRedissonServiceImpl implements GrabService {
@Autowired RedissonClient redissonClient;
@Autowired OrderService orderService;
@Override public ResponseResult grabOrder(int orderId , int driverId){ //生成key String lock = "order_"+(orderId+"");
RLock rlock = redissonClient.getLock(lock.intern());

try { // 此代碼默認(rèn) 設(shè)置key 超時時間30秒,過10秒,再延時 rlock.lock(); System.out.println("用戶:"+driverId+" 執(zhí)行搶單邏輯");
boolean b = orderService.grab(orderId, driverId); if(b) { System.out.println("用戶:"+driverId+" 搶單成功"); }else { System.out.println("用戶:"+driverId+" 搶單失敗"); }
} finally { rlock.unlock(); } return null; }}

關(guān)注點:

  1. redis故障問題。如果redis故障了,所有客戶端無法獲取鎖,服務(wù)變得不可用。為了提高可用性。我們給redis 配置主從。當(dāng)master不可用時,系統(tǒng)切換到slave,由于Redis的主從復(fù)制(replication)是異步的,這可能導(dǎo)致喪失鎖的安全性

    1.客戶端1從Master獲取了鎖。2.Master宕機(jī)了,存儲鎖的key還沒有來得及同步到Slave上。3.Slave升級為Master。4.客戶端2從新的Master獲取到了對應(yīng)同一個資源的鎖。

    客戶端1和客戶端2同時持有了同一個資源的鎖。鎖的安全性被打破。


  2. 鎖的有效時間(lock validity time),設(shè)置成多少合適?如果設(shè)置太短的話,鎖就有可能在客戶端完成對于共享資源的訪問之前過期,從而失去保護(hù);如果設(shè)置太長的話,一旦某個持有鎖的客戶端釋放鎖失敗,那么就會導(dǎo)致所有其它客戶端都無法獲取鎖,從而長時間內(nèi)無法正常工作。應(yīng)該設(shè)置稍微短一些,如果線程持有鎖,開啟線程自動延長有效期


方式三:基于RedLock實現(xiàn)分布式鎖

針對于以上兩點,antirez設(shè)計了Redlock算法 Redis的作者antirez給出了一個更好的實現(xiàn),稱為Redlock,算是Redis官方對于實現(xiàn)分布式鎖的指導(dǎo)規(guī)范。Redlock的算法描述就放在Redis的官網(wǎng)上:https://redis.io/topics/distlock

目的:對共享資源做互斥訪問

因此antirez提出了新的分布式鎖的算法Redlock,它基于N個完全獨立的Redis節(jié)點(通常情況下N可以設(shè)置成5),意思就是N個Redis數(shù)據(jù)不互通,類似于幾個陌生人

代碼實現(xiàn):

@Service("grabRedisRedissonRedLockLockService")public class GrabRedisRedissonRedLockLockServiceImpl implements GrabService {
@Autowired private RedissonClient redissonRed1; @Autowired private RedissonClient redissonRed2; @Autowired private RedissonClient redissonRed3;
@Autowired OrderService orderService;
@Override public ResponseResult grabOrder(int orderId , int driverId){ //生成key String lockKey = (RedisKeyConstant.GRAB_LOCK_ORDER_KEY_PRE + orderId).intern(); //紅鎖 RLock rLock1 = redissonRed1.getLock(lockKey); RLock rLock2 = redissonRed2.getLock(lockKey); RLock rLock3 = redissonRed2.getLock(lockKey); RedissonRedLock rLock = new RedissonRedLock(rLock1,rLock2,rLock3);
try { rLock.lock(); // 此代碼默認(rèn) 設(shè)置key 超時時間30秒,過10秒,再延時 System.out.println("用戶:"+driverId+" 執(zhí)行搶單邏輯");
boolean b = orderService.grab(orderId, driverId); if(b) { System.out.println("用戶:"+driverId+" 搶單成功"); }else { System.out.println("用戶:"+driverId+" 搶單失敗"); }
} finally { rLock.unlock(); } return null; }}


掌握Redis分布式鎖的正確姿勢

運行Redlock算法的客戶端依次執(zhí)行下面各個步驟,來完成 獲取鎖 的操作:

  1. 獲取當(dāng)前時間(毫秒數(shù))。

  2. 按順序依次向N個Redis節(jié)點執(zhí)行 獲取鎖 的操作。這個獲取操作跟前面基于單Redis節(jié)點的 獲取鎖 的過程相同,包含value driverId ,也包含過期時間(比如 PX30000 ,即鎖的有效時間)。為了保證在某個Redis節(jié)點不可用的時候算法能夠繼續(xù)運行,這個 獲取鎖 的操作還有一個超時時間(time out),它要遠(yuǎn)小于鎖的有效時間(幾十毫秒量級)。

  3. 客戶端在向某個Redis節(jié)點獲取鎖失敗以后,應(yīng)該立即嘗試下一個Redis節(jié)點。這里的失敗,應(yīng)該包含任何類型的失敗,比如該Redis節(jié)點不可用,或者該Redis節(jié)點上的鎖已經(jīng)被其它客戶端持有

  4. 計算整個獲取鎖的過程總共消耗了多長時間,計算方法是用當(dāng)前時間減去第1步記錄的時間。如果客戶端從大多數(shù)Redis節(jié)點(>= N/2+1)成功獲取到了鎖,比如:五臺機(jī)器如果加鎖成功三臺就默認(rèn)加鎖成功,并且獲取鎖總共消耗的時間沒有超過鎖的有效時間(lock validity time),那么這時客戶端才認(rèn)為最終獲取鎖成功;否則,認(rèn)為最終獲取鎖失敗

  5. 如果最終獲取鎖成功了,那么這個鎖的有效時間應(yīng)該重新計算,它等于最初的鎖的有效時間減去第3步計算出來的獲取鎖消耗的時間。

  6. 如果最終獲取鎖失敗了(可能由于獲取到鎖的Redis節(jié)點個數(shù)少于N/2+1,或者整個獲取鎖的過程消耗的時間超過了鎖的最初有效時間),那么客戶端應(yīng)該立即向所有Redis節(jié)點發(fā)起 釋放鎖 的操作(即前面介紹的Redis Lua腳本)。

上面描述的只是 獲取鎖 的過程,而 釋放鎖 的過程比較簡單:客戶端向所有Redis節(jié)點發(fā)起 釋放鎖 的操作,不管這些節(jié)點當(dāng)時在獲取鎖的時候成功與否。

總結(jié)

到這里redis分布式鎖就講完了,具體使用哪一種類型的分布式鎖需要看公司業(yè)務(wù)的,流量大的可以使用RedLock實現(xiàn)分布式鎖,流量小的可以使用redisson,后面會講解Zookeeper實現(xiàn)分布式鎖,喜歡的小伙伴可以關(guān)注我,對本文內(nèi)容有疑問或者問題的同學(xué)可以留言,小農(nóng)看到了會第一時間回復(fù),謝謝大家,大家加油!

特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:

掌握Redis分布式鎖的正確姿勢

掌握Redis分布式鎖的正確姿勢

掌握Redis分布式鎖的正確姿勢

長按訂閱更多精彩▼

掌握Redis分布式鎖的正確姿勢

如有收獲,點個在看,誠摯感謝

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當(dāng)下,工業(yè)電機(jī)作為核心動力設(shè)備,其驅(qū)動電源的性能直接關(guān)系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護(hù)是驅(qū)動電源設(shè)計中至關(guān)重要的兩個環(huán)節(jié),集成化方案的設(shè)計成為提升電機(jī)驅(qū)動性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機(jī) 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設(shè)備的使用壽命。然而,在實際應(yīng)用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護(hù)成本,還影響了用戶體驗。要解決這一問題,需從設(shè)計、生...

關(guān)鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術(shù)之一是電機(jī)驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機(jī)驅(qū)動系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動汽車的動力性能和...

關(guān)鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進(jìn)步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設(shè)計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機(jī)重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關(guān)鍵字: LED 驅(qū)動電源 開關(guān)電源

LED驅(qū)動電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動電源
關(guān)閉