<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文讀懂分布式鎖——使用SpringBoot+Redis實(shí)現(xiàn)分布式鎖

          共 10514字,需瀏覽 22分鐘

           ·

          2022-06-13 07:50

          隨著現(xiàn)在分布式架構(gòu)越來(lái)越盛行,在很多場(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)用中我們通過(guò)鎖實(shí)現(xiàn)共享資源訪問(wèn),而分布式鎖,就是解決了分布式系統(tǒng)中控制共享資源訪問(wèn)的問(wèn)題。


          可能初學(xué)的小伙伴就會(huì)有疑問(wèn),Java多線程中的公平鎖、非公平鎖、自旋鎖、可重入鎖、讀寫(xiě)鎖、互斥鎖這些都還沒(méi)鬧明白呢?怎么出來(lái)一個(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è)或一組資源,那么訪問(wèn)這些資源的時(shí)候,往往需要互斥來(lái)防止彼此干擾來(lái)保證一致性,這個(gè)時(shí)候,便需要使用到分布式鎖。


          目前幾乎很多大型網(wǎng)站及應(yīng)用都是分布式部署的,如何保證分布式場(chǎng)景中的數(shù)據(jù)一致性問(wèn)題一直是一個(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ò)誤,超賣等問(wèn)題。


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

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

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

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

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

          4、具備可重入特性;

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

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


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

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

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

          • 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖,(適用于并發(fā)小的系統(tǒng));

          • 基于緩存(Redis等)實(shí)現(xiàn)分布式鎖,(效率高,最流行,存在鎖超時(shí)的問(wèn)題);

          • 基于Zookeeper實(shí)現(xiàn)分布式鎖,(可靠,但是效率不搞);

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


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

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

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

          (1)獲取鎖的時(shí)候,使用setnx加鎖,并使用expire命令為鎖添加一個(gè)超時(shí)時(shí)間,超過(guò)該時(shí)間則自動(dòng)釋放鎖,鎖的value值為一個(gè)隨機(jī)生成的UUID,通過(guò)此在釋放鎖的時(shí)候進(jìn)行判斷。

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

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


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

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

          <dependency>       <groupId>org.springframework.boot</groupId>       <artifactId>spring-boot-starter-web</artifactId>   </dependency>
          <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
          <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
          <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
          <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency>
          <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.72</version> </dependency>
          <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>

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

          @Slf4j@Componentpublic class RedisLockUtils {    @Resource    private RedisTemplate redisTemplate;    private static Map<String, LockInfo> lockInfoMap = new ConcurrentHashMap<>();    private static final Long SUCCESS = 1L;    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;        }
          public String getKey() { return key;        } public void setKey(String key) { this.key = key;        } public String getValue() { return value;        } public void setValue(String value) { this.value = value;        } public int getExpireTime() { return expireTime;        } public void setExpireTime(int expireTime) { this.expireTime = expireTime;        } public long getRenewalTime() { return renewalTime;        } public void setRenewalTime(long renewalTime) { this.renewalTime = renewalTime;        } public long getRenewalInterval() { return renewalInterval;        } public void setRenewalInterval(long renewalInterval) { this.renewalInterval = renewalInterval; }    }
          /** * 使用lua腳本更新redis鎖的過(guò)期時(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<Boolean> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Boolean.class); redisScript.setScriptText(luaScript); List<String> keys = new ArrayList<>(); keys.add(lockKey);
          Object result = redisTemplate.execute(redisScript, keys, value, expireTime); log.info("更新redis鎖的過(guò)期時(shí)間:{}", result); return (boolean) result;    }
              /** * @param lockKey 鎖 * @param value 身份標(biāo)識(shí)(保證鎖不會(huì)被其他人釋放) * @param expireTime 鎖的過(guò)期時(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鎖的過(guò)期時(shí)間 */ @Scheduled(fixedRate = 5000L) @Async("redisExecutor") public void renewal() { long now = System.currentTimeMillis(); for (Map.Entry<String, LockInfo> 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āi)始扣減庫(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è)試

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

          接下來(lái),我們分別請(qǐng)求:http://localhost:8888/deduct-stock

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

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


          最后

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


          瀏覽 86
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲第一免费视频 | 好看的印度三色电费 | 久久成人电影 | 天堂在线aaa | 国产视频一区二区三区四区 |