自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java都為我們提供了各種鎖,為什么還需要分布式鎖?

開(kāi)發(fā) 后端 分布式
目前的項(xiàng)目單體結(jié)構(gòu)的基本上已經(jīng)沒(méi)有了,大多是分布式集群或者是微服務(wù)這些。既然是多臺(tái)服務(wù)器。就免不了資源的共享問(wèn)題。

[[357636]]

目前的項(xiàng)目單體結(jié)構(gòu)的基本上已經(jīng)沒(méi)有了,大多是分布式集群或者是微服務(wù)這些。既然是多臺(tái)服務(wù)器。就免不了資源的共享問(wèn)題。既然是資源共享就免不了并發(fā)的問(wèn)題。針對(duì)這些問(wèn)題,redis也給出了一個(gè)很好的解決方案,那就是分布式鎖。這篇文章主要是針對(duì)為什么需要使用分布式鎖這個(gè)話題來(lái)展開(kāi)討論的。

前一段時(shí)間在群里有個(gè)兄弟問(wèn),既然分布式鎖能解決大部分生產(chǎn)問(wèn)題,那么java為我們提供的那些鎖有什么用呢?直接使用分布式鎖不就結(jié)了嘛。針對(duì)這個(gè)問(wèn)題我想了很多,一開(kāi)始是在網(wǎng)上找找看看有沒(méi)有類(lèi)似的回答。后來(lái)想了想。想要解決這個(gè)問(wèn)題,還需要從本質(zhì)上來(lái)分析。

OK,開(kāi)始上車(chē)出發(fā)。

一、前言

既然是分布式鎖,這就說(shuō)明服務(wù)器不是一臺(tái),可能是很多臺(tái)。我們使用一個(gè)案例,來(lái)一步一步說(shuō)明。假設(shè)某網(wǎng)站有一個(gè)秒殺商品,一看還有100件,于是陜西、江蘇、西藏等地的人都看到了這個(gè)活動(dòng),于是開(kāi)始進(jìn)行瘋狂秒殺。假設(shè)這個(gè)秒殺商品的數(shù)量值保存在一個(gè)redis數(shù)據(jù)庫(kù)中。

但是不同地區(qū)的用戶(hù)使用不同的服務(wù)器進(jìn)行秒殺。這樣就形成了一個(gè)集群訪問(wèn)的方式。

方式我們使用Springboot來(lái)整合redis。

二、項(xiàng)目搭建準(zhǔn)備

(1)添加pom依賴(lài)

  1. <dependency> 
  2.             <groupId>org.springframework.boot</groupId> 
  3.             <artifactId>spring-boot-starter-web</artifactId> 
  4.         </dependency> 
  5.         <dependency> 
  6.             <groupId>org.springframework.boot</groupId> 
  7.             <artifactId>spring-boot-starter-test</artifactId> 
  8.             <scope>test</scope> 
  9.         </dependency> 
  10.         <dependency> 
  11.             <groupId>org.springframework.boot</groupId> 
  12.             <artifactId>spring-boot-starter-data-redis</artifactId> 
  13.         </dependency> 
  14.         <dependency> 
  15.             <groupId>org.apache.commons</groupId> 
  16.             <artifactId>commons-pool2</artifactId> 
  17.         </dependency> 

(2)添加屬性配置

  1. # Redis數(shù)據(jù)庫(kù)索引(默認(rèn)為0) 
  2. spring.redis.database=0   
  3. # Redis服務(wù)器地址 
  4. spring.redis.host=localhost 
  5. # Redis服務(wù)器連接端口 
  6. spring.redis.port=6379   
  7. # Redis服務(wù)器連接密碼(默認(rèn)為空) 
  8. spring.redis.password
  9. # 連接池最大連接數(shù)(使用負(fù)值表示沒(méi)有限制) 默認(rèn) 8 
  10. spring.redis.lettuce.pool.max-active=8 
  11. # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒(méi)有限制) 默認(rèn) -1 
  12. spring.redis.lettuce.pool.max-wait=-1 
  13. # 連接池中的最大空閑連接 默認(rèn) 8 
  14. spring.redis.lettuce.pool.max-idle=8 
  15. # 連接池中的最小空閑連接 默認(rèn) 0 
  16. spring.redis.lettuce.pool.min-idle=0 

(3)新建config包,創(chuàng)建RedisConfig類(lèi)

  1. @Configuration 
  2. public class RedisConfig { 
  3.     @Bean 
  4.     public RedisTemplate<String, Serializable>  
  5.             redisTemplate(LettuceConnectionFactory connectionFactory) { 
  6.         RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>(); 
  7.         redisTemplate.setKeySerializer(new StringRedisSerializer()); 
  8.         redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); 
  9.         redisTemplate.setConnectionFactory(connectionFactory); 
  10.         return redisTemplate; 
  11.     } 

(4)新建controller,創(chuàng)建Mycontroller類(lèi)

  1. @RestController 
  2. public class MyController { 
  3.     @Autowired 
  4.     private StringRedisTemplate stringRedisTemplate; 
  5.     @GetMapping("/test"
  6.     public String deduceGoods(){ 
  7.         int goods =Integer.parseInt(stringRedisTemplate.opsForValue().get("goods")); 
  8.         int realGoods = goods-1; 
  9.         if(goods>0){ 
  10.             stringRedisTemplate.opsForValue().set("goods",realGoods+""); 
  11.             return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realGoods + "件"
  12.         }else
  13.             return "商品已經(jīng)售罄,歡迎下次活動(dòng)"
  14.         } 
  15.     } 

很簡(jiǎn)單的一個(gè)整合教程。這個(gè)端口是8080,我們復(fù)制一份這個(gè)項(xiàng)目,把端口改成8090,并且以nginx作負(fù)載均衡搭建集群。現(xiàn)在環(huán)境我們已經(jīng)整理好了。下面我們就開(kāi)始進(jìn)行分析。

三、為什么需要分布式鎖

階段一:采用原生方式

我們使用多個(gè)線程訪問(wèn)8080這個(gè)端口。因?yàn)闆](méi)有加鎖,此時(shí)肯定會(huì)出現(xiàn)并發(fā)問(wèn)題。因此我們可能會(huì)想到,既然這個(gè)goods是一個(gè)共享資源,而且是多線程訪問(wèn)的,就立馬能想到j(luò)ava中的各種鎖了,最有名的就是synchronized。所以我們不如對(duì)上面的代碼進(jìn)行優(yōu)化。

階段二:使用synchronized加鎖

此時(shí)我們對(duì)代碼修改一下:

  1. @RestController 
  2. public class MyController { 
  3.     @Autowired 
  4.     private StringRedisTemplate stringRedisTemplate; 
  5.     @GetMapping("/test"
  6.     public String deduceGoods(){ 
  7.         synchronized (this){ 
  8.             int goods =Integer.parseInt(stringRedisTemplate.opsForValue().get("goods")); 
  9.             int realGoods = goods-1; 
  10.             if(goods>0){ 
  11.                 stringRedisTemplate.opsForValue().set("goods",realGoods+""); 
  12.                 return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realGoods + "件"
  13.             }else
  14.                 return "商品已經(jīng)售罄,歡迎下次活動(dòng)"
  15.             } 
  16.         } 
  17.  
  18.     } 

看到?jīng)],現(xiàn)在我們使用synchronized關(guān)鍵字加上鎖,這樣多個(gè)線程并發(fā)訪問(wèn)的時(shí)候就不會(huì)出現(xiàn)數(shù)據(jù)不一致等各種問(wèn)題了。這種方式在單體結(jié)構(gòu)下的確有用。目前的項(xiàng)目單體結(jié)構(gòu)的很少,一般都是集群方式的。此時(shí)的synchronized就不再起作用了。為什么synchronized不起作用了呢?

我們采用集群的方式去訪問(wèn)秒殺商品(nginx為我們做了負(fù)載均衡)。就會(huì)看到數(shù)據(jù)不一致的現(xiàn)象。也就是說(shuō)synchronized關(guān)鍵字的作用域其實(shí)是一個(gè)進(jìn)程,在這個(gè)進(jìn)程下面的所有線程都能夠進(jìn)行加鎖。但是多進(jìn)程就不行了。對(duì)于秒殺商品來(lái)說(shuō),這個(gè)值是固定的。但是每個(gè)地區(qū)都可能有一臺(tái)服務(wù)器。這樣不同地區(qū)服務(wù)器不一樣,地址不一樣,進(jìn)程也不一樣。因此synchronized無(wú)法保證數(shù)據(jù)的一致性。

階段三:分布式鎖

上面synchronized關(guān)鍵字無(wú)法保證多進(jìn)程的鎖機(jī)制,為了解決這個(gè)問(wèn)題,我們可以使用redis分布式鎖。現(xiàn)在我們把代碼再進(jìn)行修改一下:

  1. @RestController 
  2. public class MyController { 
  3.     @Autowired 
  4.     private StringRedisTemplate stringRedisTemplate; 
  5.     @GetMapping("/test"
  6.     public String deduceGoods(){ 
  7.       Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lock","馮冬冬"); 
  8.       if(!result){ 
  9.            return "其他人正在秒殺,無(wú)法進(jìn)入"
  10.       } 
  11.       int goods =Integer.parseInt(stringRedisTemplate.opsForValue().get("goods")); 
  12.       int realGoods = goods-1; 
  13.       if(goods>0){ 
  14.           stringRedisTemplate.opsForValue().set("goods",realGoods+""); 
  15.           System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realGoods + "件"); 
  16.       }else
  17.           System.out.println("商品已經(jīng)售罄,歡迎下次活動(dòng)"); 
  18.       } 
  19.       stringRedisTemplate.delete("lock"); 
  20.       return "success"
  21.     } 

就是這么簡(jiǎn)單,我們只是加了一句話,然后進(jìn)行判斷了一下。其實(shí)setIfAbsent方法的作用就是redis中的setnx。意思是如果當(dāng)前key已經(jīng)存在了,就不做任何操作了,返回false。如果當(dāng)前key不存在,那我們就可以操作。最后別忘了釋放這個(gè)key,這樣別人就可以再進(jìn)來(lái)實(shí)時(shí)秒殺操作。

當(dāng)然這里只是給出一個(gè)最基本的案例,其實(shí)分布式鎖實(shí)現(xiàn)起來(lái)步驟還是比較多的,而且里面很多坑也沒(méi)有給出。我們隨便解決幾個(gè):

階段四:分布式鎖優(yōu)化

(1)第一個(gè)坑:秒殺商品出現(xiàn)異常,最終無(wú)法釋放lock分布式鎖

  1.   public String deduceGoods() throws Exception{ 
  2.      Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lock","馮冬冬"); 
  3.      if(!result){ 
  4.         return "其他人正在秒殺,無(wú)法進(jìn)入"
  5.      } 
  6.      try { 
  7.         int goods =Integer.parseInt(stringRedisTemplate.opsForValue().get("goods")); 
  8.         int realGoods = goods-1; 
  9.         if(goods>0){ 
  10.            stringRedisTemplate.opsForValue().set("goods",realGoods+""); 
  11.            System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realGoods + "件"); 
  12.        }else
  13.            System.out.println("商品已經(jīng)售罄,歡迎下次活動(dòng)"); 
  14.        } 
  15.     }finally { 
  16.        stringRedisTemplate.delete("lock"); 
  17.     } 
  18.     return "success"

此時(shí)我們加一個(gè)try和finally語(yǔ)句就可以了。最終一定要?jiǎng)h除lock。

(2)第二個(gè)坑:秒殺商品時(shí)間太久,其他用戶(hù)等不及

  1. public String deduceGoods() throws Exception{ 
  2.     stringRedisTemplate.expire("lock",10, TimeUnit.MILLISECONDS); 
  3.     Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lock","馮冬冬"); 
  4.     if(!result){ 
  5.        return "其他人正在秒殺,無(wú)法進(jìn)入"
  6.     } 
  7.     try { 
  8.        int goods =Integer.parseInt(stringRedisTemplate.opsForValue().get("goods")); 
  9.        int realGoods = goods-1; 
  10.        if(goods>0){ 
  11.            stringRedisTemplate.opsForValue().set("goods",realGoods+""); 
  12.            System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realGoods + "件"); 
  13.        }else
  14.            System.out.println("商品已經(jīng)售罄,歡迎下次活動(dòng)"); 
  15.        } 
  16.     }finally { 
  17.        stringRedisTemplate.delete("lock"); 
  18.     } 
  19.     return "success"

給其添加一個(gè)過(guò)期時(shí)間,也就是說(shuō)如果10毫秒內(nèi)沒(méi)有秒殺成功,就表示秒殺失敗,換下一個(gè)用戶(hù)。

(3)第三個(gè)坑:高并發(fā)場(chǎng)景下,秒殺時(shí)間太久,鎖永久失效問(wèn)題

我們剛剛設(shè)置的鎖過(guò)期時(shí)間是10毫秒,如果一個(gè)用戶(hù)秒殺時(shí)間是15毫秒,這也就意味著他可能還沒(méi)秒殺成功,就有其他用戶(hù)進(jìn)來(lái)了。當(dāng)這種情況過(guò)多時(shí),就可能有大量用戶(hù)還沒(méi)秒殺成功其他大量用戶(hù)就進(jìn)來(lái)了。有可能其他用戶(hù)提前刪除了lock,但是當(dāng)前用戶(hù)還沒(méi)有秒殺成功。最終造成數(shù)據(jù)的不一致??纯慈绾谓鉀Q:

  1. public String deduceGoods() throws Exception{ 
  2.     String user = UUID.randomUUID().toString(); 
  3.     stringRedisTemplate.expire("lock",10, TimeUnit.MILLISECONDS); 
  4.     Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lock",user); 
  5.     if(!result){ 
  6.        return "其他人正在秒殺,無(wú)法進(jìn)入"
  7.     } 
  8.     try { 
  9.        int goods =Integer.parseInt(stringRedisTemplate.opsForValue().get("goods")); 
  10.        int realGoods = goods-1; 
  11.        if(goods>0){ 
  12.            stringRedisTemplate.opsForValue().set("goods",realGoods+""); 
  13.            System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realGoods + "件"); 
  14.        }else
  15.            System.out.println("商品已經(jīng)售罄,歡迎下次活動(dòng)"); 
  16.        } 
  17.     }finally { 
  18.        if(user.equals(stringRedisTemplate.opsForValue().get("lock"))){ 
  19.                  stringRedisTemplate.delete("lock"); 
  20.        } 
  21.     } 
  22.     return "success"

也就是說(shuō),我們?cè)趧h除lock的時(shí)候判斷是不是當(dāng)前的線程,如果是那就刪除,如果不是那就不刪除,這樣就算別的線程進(jìn)來(lái)也不會(huì)亂刪lock,造成混亂。

OK,到目前為止基本上把分布式鎖的緣由介紹了一遍。對(duì)于分布式鎖redisson完成的相當(dāng)出色,下篇文章也將圍著繞Redisson來(lái)介紹一下分布式如何實(shí)現(xiàn),以及其中的原理。

本文轉(zhuǎn)載自微信公眾號(hào)「愚公要移山」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系愚公要移山公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 愚公要移山
相關(guān)推薦

2020-07-06 14:53:24

分布式鎖系統(tǒng)單機(jī)鎖

2023-09-12 14:02:30

數(shù)組vector

2021-11-26 06:43:19

Java分布式

2023-10-24 15:15:26

HTTPWebSocket

2021-10-12 18:48:07

HTTP 協(xié)議Websocket網(wǎng)絡(luò)通信

2024-02-22 10:34:00

NULLC++nullptr

2024-02-18 12:39:15

C++autodecltype

2018-11-27 16:17:13

分布式Tomcat

2020-11-26 06:38:14

分布式系統(tǒng)

2020-04-10 08:03:04

分布式鎖Redlock算法流行算法

2019-06-19 15:40:06

分布式鎖RedisJava

2021-07-06 08:37:29

Redisson分布式

2021-07-16 07:57:34

ZooKeeperCurator源碼

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2022-08-04 08:45:50

Redisson分布式鎖工具

2024-07-15 08:25:07

2018-11-28 16:00:41

2018-12-12 15:20:27

2024-01-09 09:27:08

RedLock分布式鎖Redis

2021-07-02 08:51:09

Redisson分布式鎖公平鎖
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)