亚洲精品中文免费|亚洲日韩中文字幕制服|久久精品亚洲免费|一本之道久久免费

<optgroup id="cczp1"><ruby id="cczp1"><cite id="cczp1"></cite></ruby></optgroup>
  • <acronym id="cczp1"></acronym>
    <acronym id="cczp1"><option id="cczp1"><ol id="cczp1"></ol></option></acronym>
    <delect id="cczp1"></delect>
    <center id="cczp1"></center>
    <delect id="cczp1"></delect><em id="cczp1"><button id="cczp1"><blockquote id="cczp1"></blockquote></button></em>
    1. <optgroup id="cczp1"><td id="cczp1"><dfn id="cczp1"></dfn></td></optgroup>

      一文讀懂分布式鎖-使用SpringBoot+Redis實(shí)現(xiàn)分布式鎖解決方案

      一文讀懂分布式鎖-使用SpringBoot+Redis實(shí)現(xiàn)分布式鎖解決方案

      隨著現(xiàn)在分布式架構(gòu)越來越盛行,在很多場(chǎng)景下需要使用到分布式鎖。很多小伙伴對(duì)于分布式鎖還不是特別了解,所以特地總結(jié)了一篇文章,讓大家一文讀懂分布式鎖的前世今生。

      分布式鎖的實(shí)現(xiàn)有很多種,比如基于數(shù)據(jù)庫(kù)、Redis 、 zookeeper 等實(shí)現(xiàn),本文的示例主要介紹使用Redis實(shí)現(xiàn)分布式鎖。

      一、什么是分布式鎖

      分布式鎖,即分布式系統(tǒng)中的鎖,分布式鎖是控制分布式系統(tǒng)有序地對(duì)共享資源進(jìn)行操作,在單體應(yīng)用中我們通過鎖實(shí)現(xiàn)共享資源訪問,而分布式鎖,就是解決了分布式系統(tǒng)中控制共享資源訪問的問題。

      可能初學(xué)的小伙伴就會(huì)有疑問,Java多線程中的公平鎖、非公平鎖、自旋鎖、可重入鎖、讀寫鎖、互斥鎖這些都還沒鬧明白呢?怎么有出來一個(gè)分布式鎖?

      其實(shí),可以這么理解:Java的原生鎖是解決多線程下對(duì)于共享資源的操作,而分布式鎖則是多進(jìn)程下對(duì)于共享資源的操作。分布式系統(tǒng)中競(jìng)爭(zhēng)共享資源的最小粒度從線程升級(jí)成了進(jìn)程。

      分布式鎖已經(jīng)被應(yīng)用到各種高并發(fā)的場(chǎng)景下,典型場(chǎng)景案例包括:秒殺、車票、訂單、退款、庫(kù)存等場(chǎng)景。

      二、為什么要使用分布式鎖

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

      目前幾乎很多大型網(wǎng)站及應(yīng)用都是分布式部署的,如何保證分布式場(chǎng)景中的數(shù)據(jù)一致性問題一直是一個(gè)比較重要的話題。在某些場(chǎng)景下,為了保證數(shù)據(jù)的完整性和一致性,我們需要保證一個(gè)方法在同一時(shí)間內(nèi)只能被同一個(gè)線程執(zhí)行,這就需要使用分布式鎖。

      如上圖所示,假設(shè)用戶A和用戶B同時(shí)購(gòu)買了某款商品,訂單創(chuàng)建成功后,下單系統(tǒng)A和下單系統(tǒng)B就會(huì)同時(shí)對(duì)數(shù)據(jù)庫(kù)中的該款商品的庫(kù)存進(jìn)行扣減。如果此時(shí)不加任何控制,系統(tǒng)B提交的數(shù)據(jù)更新就會(huì)覆蓋系統(tǒng)A的數(shù)據(jù),導(dǎo)致庫(kù)存錯(cuò)誤,超賣等問題。

      三、分布式鎖應(yīng)該具備哪些條件

      在介紹分布式鎖的實(shí)現(xiàn)方式之前,先了解一下分布式鎖應(yīng)該具備哪些條件:

      1、在分布式系統(tǒng)環(huán)境下,一個(gè)方法在同一時(shí)間只能被一個(gè)機(jī)器的一個(gè)線程執(zhí)行;

      2、高可用的獲取鎖與釋放鎖;

      3、高性能的獲取鎖與釋放鎖;

      4、具備可重入特性;

      5、具備鎖失效機(jī)制,防止死鎖;

      6、具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗。

      四、分布式鎖的實(shí)現(xiàn)方式

      隨著業(yè)務(wù)發(fā)展的需要,原來的單體應(yīng)用被演化成分布式集群系統(tǒng)后,由于系統(tǒng)分布在不同機(jī)器上,這就使得原有的并發(fā)控制鎖策略失效,為了解決這個(gè)問題就需要一種跨進(jìn)程的互斥機(jī)制來控制共享資源的訪問,這就需要用到分布式鎖!

      分布式鎖的實(shí)現(xiàn)有多種方式,下面介紹下這幾種分布式鎖的實(shí)現(xiàn)方式:

      • 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖,(適用于并發(fā)小的系統(tǒng));
      • 基于緩存(Redis等)實(shí)現(xiàn)分布式鎖,(效率高,最流行,存在鎖超時(shí)的問題);
      • 基于Zookeeper實(shí)現(xiàn)分布式鎖,(可靠,但是效率不高);

      盡管有這三種方案,但是不同的業(yè)務(wù)也要根據(jù)自己的情況進(jìn)行選型,他們之間沒有最好只有更適合!

      五、基于Redis實(shí)現(xiàn)分布式鎖

      使用Redis實(shí)現(xiàn)分布式鎖是目前比較流行的解決方案,主要是使用Redis 獲取鎖與釋放鎖效率都很高,實(shí)現(xiàn)方式也特別簡(jiǎn)單。

      實(shí)現(xiàn)原理:

      (1)獲取鎖的時(shí)候,使用setnx加鎖,并使用expire命令為鎖添加一個(gè)超時(shí)時(shí)間,超過該時(shí)間則自動(dòng)釋放鎖,鎖的value值為當(dāng)前線程ID,通過此在釋放鎖的時(shí)候進(jìn)行判斷。

      (2)獲取鎖的時(shí)候還設(shè)置一個(gè)獲取的超時(shí)時(shí)間,若超過這個(gè)時(shí)間則放棄獲取鎖。

      (3)釋放鎖的時(shí)候,通過線程ID判斷是不是該鎖,若是該鎖,則執(zhí)行delete進(jìn)行鎖釋放。

      接下來我們就一步一步實(shí)現(xiàn)Redis 分布式鎖。

      第一步,創(chuàng)建Spring Boot項(xiàng)目,并引入相關(guān)依賴。

      org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.apache.commons commons-lang3 com.alibaba fastjson 1.2.72 org.projectlombok lombok

      第二步,創(chuàng)建Redis分布式鎖通用操作類,示例代碼如下:

      import com.alibaba.fastjson.JSON;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.springframework.context.annotation.Bean;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.script.DefaultRedisScript;import org.springframework.scheduling.annotation.Async;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import org.springframework.stereotype.Component;import javax.annotation.Resource;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.Executor;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;/** * Lua腳本 * // 加鎖 * if * redis.call(‘setNx’,KEYS[1],ARGV[1]) * then * if redis.call(‘get’,KEYS[1])==ARGV[1] * return redis.call(‘expire’,KEYS[1],ARGV[2]) * else * return 0 * end * end * * // 解鎖 * redis.call(‘get’, KEYS[1]) == ARGV[1] * then * return redis.call(‘del’, KEYS[1]) * else * return 0 * * * //更新時(shí)間 * if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘expire’, KEYS[1], ARGV[2]) else return 0 end * * * * */@Slf4j@Componentpublic class RedisLockUtils { @Resource private RedisTemplate redisTemplate; private static Map lockInfoMap = new ConcurrentHashMap(); private static final Long SUCCESS = 1L; @Data public static class LockInfo { private String key; private String value; private int expireTime; //更新時(shí)間 private long renewalTime; //更新間隔 private long renewalInterval; public static LockInfo getLockInfo(String key, String value, int expireTime) { LockInfo lockInfo = new LockInfo(); lockInfo.setKey(key); lockInfo.setValue(value); lockInfo.setExpireTime(expireTime); lockInfo.setRenewalTime(System.currentTimeMillis()); lockInfo.setRenewalInterval(expireTime * 2000 / 3); return lockInfo; } } /** * 使用lua腳本加鎖 * @param lockKey 鎖 * @param value 身份標(biāo)識(shí)(保證鎖不會(huì)被其他人釋放) * @param expireTime 鎖的過期時(shí)間(單位:秒) * @Desc 注意事項(xiàng),redisConfig配置里面必須使用 genericToStringSerializer序列化,否則獲取不了返回值 */ public boolean tryLock(String lockKey, String value, int expireTime) { String luaScript = “if redis.call(‘setNx’,KEYS[1],ARGV[1]) then if redis.call(‘get’,KEYS[1])==ARGV[1] then return redis.call(‘expire’,KEYS[1],ARGV[2]) else return 0 end end”; DefaultRedisScript redisScript = new DefaultRedisScript(); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List keys = new ArrayList(); keys.add(lockKey); //Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey),value,expireTime + “”); // Object result = redisTemplate.execute(redisScript, new StringRedisSerializer(), new StringRedisSerializer(), Collections.singletonList(lockKey), identity, expireTime); Object result = redisTemplate.execute(redisScript, keys, value, expireTime); log.info(“已獲取到{}對(duì)應(yīng)的鎖!”, lockKey); if (expireTime >= 10) { lockInfoMap.put(lockKey + value, LockInfo.getLockInfo(lockKey, value, expireTime)); } return (boolean) result; } /** * 使用lua腳本釋放鎖 * @param lockKey * @param value * @return 成功返回true, 失敗返回false */ public boolean unlock(String lockKey, String value) { lockInfoMap.remove(lockKey + value); String luaScript = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”; DefaultRedisScript redisScript = new DefaultRedisScript(); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List keys = new ArrayList(); keys.add(lockKey); Object result = redisTemplate.execute(redisScript, keys, value); log.info(“解鎖成功:{}”, result); return (boolean) result; } /** * 使用lua腳本更新redis鎖的過期時(shí)間 * @param lockKey * @param value * @return 成功返回true, 失敗返回false */ public boolean renewal(String lockKey, String value, int expireTime) { String luaScript = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘expire’, KEYS[1], ARGV[2]) else return 0 end”; DefaultRedisScript redisScript = new DefaultRedisScript(); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List keys = new ArrayList(); keys.add(lockKey); Object result = redisTemplate.execute(redisScript, keys, value, expireTime); log.info(“更新redis鎖的過期時(shí)間:{}”, result); return (boolean) result; } /** * * @param lockKey 鎖 * @param value 身份標(biāo)識(shí)(保證鎖不會(huì)被其他人釋放) * @param expireTime 鎖的過期時(shí)間(單位:秒) * @return 成功返回true, 失敗返回false */ public boolean lock(String lockKey, String value, long expireTime) { return redisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS); } /** * redisTemplate解鎖 * @param key * @param value * @return 成功返回true, 失敗返回false */ public boolean unlock2(String key, String value) { Object currentValue = redisTemplate.opsForValue().get(key); boolean result = false; if (StringUtils.isNotEmpty(String.valueOf(currentValue)) && currentValue.equals(value)) { result = redisTemplate.opsForValue().getOperations().delete(key); } return result; } /** * 定時(shí)去檢查redis鎖的過期時(shí)間 */ @Scheduled(fixedRate = 5000L) @Async(“redisExecutor”) public void renewal() { long now = System.currentTimeMillis(); for (Map.Entry lockInfoEntry : lockInfoMap.entrySet()) { LockInfo lockInfo = lockInfoEntry.getValue(); if (lockInfo.getRenewalTime() + lockInfo.getRenewalInterval() < now) { renewal(lockInfo.getKey(), lockInfo.getValue(), lockInfo.getExpireTime()); lockInfo.setRenewalTime(now); log.info("lockInfo {}", JSON.toJSONString(lockInfo)); } } } /** * 分布式鎖設(shè)置單獨(dú)線程池 * @return */ @Bean("redisExecutor") public Executor redisExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(1); executor.setMaxPoolSize(1); executor.setQueueCapacity(1); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("redis-renewal-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); return executor; }}

      第三步,創(chuàng)建RedisTemplate 配置類,配置Redistemplate,示例代碼如下:

      @Configurationpublic class RedisConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) throws Exception { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); // 創(chuàng)建 序列化類 GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(genericToStringSerializer); return redisTemplate; }}

      第四步,實(shí)現(xiàn)業(yè)務(wù)調(diào)用,這里以扣減庫(kù)存為例,示例代碼如下:

      @RestControllerpublic class IndexController { @Resource private RedisTemplate redisTemplate; @Autowired private RedisLockUtils redisLock; @RequestMapping(“/deduct-stock”) public String deductStock() { String productId = “product001”; System.out.println(“—————->>>開始扣減庫(kù)存”); String key = productId; String requestId = productId + Thread.currentThread().getId(); try { boolean locked = redisLock.lock(key, requestId, 10); if (!locked) { return “error”; } //執(zhí)行業(yè)務(wù)邏輯 //System.out.println(“—————->>>執(zhí)行業(yè)務(wù)邏輯:”+appTitle); int stock = Integer.parseInt(redisTemplate.opsForValue().get(“product001-stock”).toString()); int currentStock = stock-1; redisTemplate.opsForValue().set(“product001-stock”,currentStock); try { Random random = new Random(); Thread.sleep(random.nextInt(3) *1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(“—————->>>扣減庫(kù)存結(jié)束:current stock:” + currentStock); return “success,current stock:” + currentStock; } finally { redisLock.unlock2(key, requestId); } }}

      六、驗(yàn)證測(cè)試

      代碼完成之后,開始測(cè)試。我們同時(shí)啟動(dòng)兩個(gè)實(shí)例,端口號(hào)為:8888和8889模擬分布式系統(tǒng)。

      接下來,我們分別請(qǐng)求:http://localhost:8888/deduct-stock和http://localhost:8889/deduct-stock,或者使用JMater分別請(qǐng)求這兩個(gè)地址,模擬高并發(fā)的情況。

      通過上圖我們可以看到,在批量請(qǐng)求的情況下,庫(kù)存扣減也沒有出現(xiàn)問題。說明分布式鎖生效了。

      最后

      以上,我們就把什么是分布式鎖,如何基于Redis 實(shí)現(xiàn)分布式鎖的解決方案介紹完了。分布式鎖是分布式系統(tǒng)中的重要功能組件,希望大家能夠熟練掌握。

      鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場(chǎng),版權(quán)歸原作者所有,如有侵權(quán)請(qǐng)聯(lián)系管理員(admin#wlmqw.com)刪除。
      用戶投稿
      上一篇 2022年6月12日 14:49
      下一篇 2022年6月12日 14:49

      相關(guān)推薦

      • 分享4條發(fā)微商朋友圈的方法(微商朋友圈應(yīng)該怎么發(fā))

        對(duì)于微商朋友來說,朋友圈的重要性不言而喻了。 那么微商的朋友圈到底該怎么發(fā)呢? 為什么同樣是經(jīng)營(yíng)一個(gè)朋友圈,有的微商看起來逼格滿滿,實(shí)際效果也不錯(cuò);而有的卻動(dòng)都不動(dòng)就被屏蔽甚至拉黑…

        2022年11月27日
      • 淘寶直播庫(kù)存哪里拿貨(淘寶哪里看庫(kù)存)

        近年倆直播帶貨越來越火爆,抖音、淘寶、拼多多等平臺(tái)都有直播帶貨功能,其中淘寶直播時(shí)主流帶貨平臺(tái),一些小伙伴也紛紛加入,但是作為新手不知道淘寶直播庫(kù)存哪里拿貨?下面小編為大家?guī)硖詫殹?/p>

        2022年11月25日
      • 5+3疫情防控從哪天開始算(遼寧疫情防控最新政策)

        最近有關(guān)國(guó)內(nèi)各地的疫情大家也都有在持續(xù)關(guān)注,目前國(guó)內(nèi)各地疫情隔離時(shí)間也根據(jù)二十條防控措施有了新的調(diào)整。那么,5+3疫情防控從哪天開始算?對(duì)于密接的5+3隔離時(shí)間計(jì)算大家還是比較關(guān)心…

        2022年11月25日
      • 藍(lán)碼怎么變綠碼需要幾天(藍(lán)碼怎么變綠碼需要幾天)

        大家都知道健康碼的顏色有紅碼、綠碼、黃碼,近日湖南健康碼上線“藍(lán)碼”,不少小伙伴發(fā)現(xiàn)自己健康碼變藍(lán)了,都想趕緊恢復(fù)綠碼,那么藍(lán)碼怎么變綠碼需要幾天?下面小編為大家?guī)硭{(lán)碼變綠碼需要…

        2022年11月25日
      • 拼多多百億補(bǔ)貼預(yù)售一般多久發(fā)貨(拼多多百億補(bǔ)貼預(yù)售)

        拼多多里面有很多優(yōu)惠活動(dòng),其中百億補(bǔ)貼活動(dòng)非?;鸨恍├锩娴臇|西價(jià)格比別的平臺(tái)便宜,質(zhì)量也有保障,還有預(yù)售的活動(dòng),那么拼多多百億補(bǔ)貼預(yù)售一般多久發(fā)貨?下面小編為大家?guī)砥炊喽喟賰|…

        2022年11月25日
      • 北京疫情多久能解除封控(北京疫情還要多久結(jié)束)

        最近一段時(shí)間北京疫情形勢(shì)備受關(guān)注,馬上就要到年底了,不少人想要去北京辦事,。都非常關(guān)注當(dāng)?shù)匾咔橄嚓P(guān)政策,那么 北京疫情多久能解除封控?北京疫情什么時(shí)候恢復(fù)正常生活?下面小編為大家?guī)А?/p>

        2022年11月25日
      • 直播帶貨詳細(xì)腳本(直播文案策劃怎么寫)

        短視頻運(yùn)營(yíng)策劃方案怎么寫?涉及哪幾個(gè)方面? 我在網(wǎng)上看到好多千篇一律的文章,關(guān)于【短視頻運(yùn)營(yíng)策劃方案】這一塊,基本都是在講賬號(hào)的內(nèi)容本身。 你內(nèi)容做得再好,卻不掌握算法的規(guī)律,能有…

        2022年11月25日
      • 抖音直播帶貨有哪些方法技巧(抖音直播帶貨有哪些痛點(diǎn))

        如今抖音這個(gè)短視頻的變現(xiàn)能力越來越突顯了,尤其是在平臺(tái)上開通直播,更具有超強(qiáng)的帶貨屬性,已經(jīng)有越來越多的普通人加入到其中了。不過直播帶貨雖然很火,但是也不是每個(gè)人都能做好的,那么在…

        2022年11月24日
      • 淘寶直播平臺(tái)抽成多少(淘寶直播平臺(tái)抽成比例)

        隨著時(shí)代的發(fā)展,現(xiàn)在直播帶貨已經(jīng)成為主要帶貨方式,其中淘寶是主流帶貨平臺(tái),不少人在上面直播帶貨賺錢,一些小伙伴也想加入,那么淘寶直播平臺(tái)抽成多少?下面小編為大家?guī)硖詫氈辈テ脚_(tái)抽成…

        2022年11月24日
      • 淘寶直播開通后帶貨鏈接怎么做(淘寶直播需要開通淘寶店鋪嗎)

        直播帶貨無論是對(duì)于商家來說還是主播收益都是非??捎^的,所以不少平臺(tái)都有直播帶貨功能,一些小伙伴也想加入淘寶直播,那么淘寶直播開通后帶貨鏈接怎么做?下面小編為大家?guī)硖詫氈辈ラ_通后帶…

        2022年11月24日

      聯(lián)系我們

      聯(lián)系郵箱:admin#wlmqw.com
      工作時(shí)間:周一至周五,10:30-18:30,節(jié)假日休息