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

基于SpringBoot與數(shù)據(jù)庫(kù)表記錄的方式實(shí)現(xiàn)分布式鎖

數(shù)據(jù)庫(kù) 分布式
今天演示一下最簡(jiǎn)單的分布式鎖方案——基于數(shù)據(jù)庫(kù)表記錄的分布式鎖.

[[386855]]

 同一進(jìn)程內(nèi)的不同線程操作共享資源時(shí),我們只需要對(duì)資源加鎖,比如利用JUC下的工具,就可以保證操作的正確性。對(duì)JUC不熟悉的同學(xué),可以看看以下的幾篇文章:

  • 淺說(shuō)Synchronized
  • Synchronized的優(yōu)化
  • JUC基石——Unsafe類

但是,為了高可用,我們的系統(tǒng)總是多副本的,分布在不同的機(jī)器上,以上同進(jìn)程內(nèi)的鎖機(jī)制就不再起作用。為了保證多副本系統(tǒng)對(duì)共享資源的訪問(wèn),我們引入了分布式鎖。

分布式鎖主要的實(shí)現(xiàn)方式有以下幾種:

  • 基于數(shù)據(jù)庫(kù)的,其中又細(xì)分為基于數(shù)據(jù)庫(kù)的表記錄、悲觀鎖、樂(lè)觀鎖
  • 基于緩存的,比如Redis
  • 基于Zookeeper的

今天演示一下最簡(jiǎn)單的分布式鎖方案——基于數(shù)據(jù)庫(kù)表記錄的分布式鎖

主要的原理就是利用數(shù)據(jù)庫(kù)的唯一索引(對(duì)數(shù)據(jù)庫(kù)的索引不了解的同學(xué),可以參考我的另外一篇文章mysql索引簡(jiǎn)談)

例如,有以下的一張表:

  1. CREATE TABLE `test`.`Untitled`  ( 
  2.   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增序號(hào)'
  3.   `namevarchar(255) NOT NULL COMMENT '鎖名稱'
  4.   `survival_time` int(11) NOT NULL COMMENT '存活時(shí)間,單位ms'
  5.   `create_time` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '創(chuàng)建時(shí)間'
  6.   `thread_name` varchar(255) NOT NULL COMMENT '線程名稱'
  7.   PRIMARY KEY (`id`) USING BTREE, 
  8.   UNIQUE INDEX `uk_name`(`name`) USING BTREE 
  9. ) ENGINE = InnoDB ROW_FORMAT = Dynamic

 其中name字段加上了唯一索引,多條含有同樣name值的新增操作,數(shù)據(jù)庫(kù)只能保證僅有一個(gè)操作成功,其他操作都會(huì)被拒絕掉,并且拋出“重復(fù)鍵”的錯(cuò)誤。

那么,當(dāng)系統(tǒng)1準(zhǔn)備獲取分布式鎖時(shí),就嘗試往數(shù)據(jù)庫(kù)中插入一條name="key"的記錄,如果插入成功,則代表獲取鎖成功。其他系統(tǒng)想要獲取分布式鎖,同樣需要往數(shù)據(jù)庫(kù)插入相同name的記錄,當(dāng)然數(shù)據(jù)庫(kù)會(huì)報(bào)錯(cuò),插入失敗,也就代表著這些系統(tǒng)獲取鎖失敗。當(dāng)系統(tǒng)1想要釋放掉鎖時(shí),刪除掉此記錄即可。thread_name列可以用來(lái)保證只能主動(dòng)釋放自己創(chuàng)建的鎖。

我們希望實(shí)現(xiàn)的分布式鎖有以下的效果:

  1. 獲取鎖是阻塞的,獲取不到會(huì)一直阻塞
  2. 鎖會(huì)失效,超過(guò)鎖的生存時(shí)間后,會(huì)自動(dòng)釋放掉。這一點(diǎn)可以避免某些系統(tǒng)因?yàn)殄礄C(jī)而無(wú)法主動(dòng)釋放鎖的問(wèn)題

大致的流程圖如下:

使用到了以下依賴:

  • SpringBoot
  • MyBatis-plus
  • Lombok

項(xiàng)目的工程目錄為:

其中pom文件用到的依賴:

  1. <dependencies> 
  2.     <dependency> 
  3.         <groupId>org.springframework.boot</groupId> 
  4.         <artifactId>spring-boot-starter-web</artifactId> 
  5.     </dependency> 
  6.  
  7.     <dependency> 
  8.         <groupId>org.projectlombok</groupId> 
  9.         <artifactId>lombok</artifactId> 
  10.         <version>1.18.6</version> 
  11.     </dependency> 
  12.  
  13.     <dependency> 
  14.         <groupId>mysql</groupId> 
  15.         <artifactId>mysql-connector-java</artifactId> 
  16.     </dependency> 
  17.  
  18.     <dependency> 
  19.         <groupId>com.baomidou</groupId> 
  20.         <artifactId>mybatis-plus-boot-starter</artifactId> 
  21.         <version>3.3.1</version> 
  22.     </dependency> 
  23.  
  24.     <dependency> 
  25.         <groupId>com.baomidou</groupId> 
  26.         <artifactId>mybatis-plus-extension</artifactId> 
  27.         <version>3.3.1</version> 
  28.     </dependency> 
  29.  
  30.     <dependency> 
  31.         <groupId>org.springframework.boot</groupId> 
  32.         <artifactId>spring-boot-starter-test</artifactId> 
  33.         <scope>test</scope> 
  34.     </dependency> 
  35. </dependencies> 

 配置項(xiàng)為:

  1. server: 
  2.   port: 9091 
  3.  
  4.  
  5. spring: 
  6.   datasource: 
  7.     driver-class-name: com.mysql.cj.jdbc.Driver 
  8.     url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai 
  9.     username: root 
  10.     password: a123 
  11.  
  12. logging: 
  13.   level
  14.     root: info 

 用于映射數(shù)據(jù)庫(kù)字段的實(shí)體類為:

  1. package com.yang.lock1.entity; 
  2.  
  3. import com.baomidou.mybatisplus.annotation.IdType; 
  4. import com.baomidou.mybatisplus.annotation.TableField; 
  5. import com.baomidou.mybatisplus.annotation.TableId; 
  6. import com.baomidou.mybatisplus.annotation.TableName; 
  7. import lombok.AllArgsConstructor; 
  8. import lombok.Data; 
  9. import lombok.NoArgsConstructor; 
  10.  
  11. import java.util.Date
  12.  
  13. /** 
  14.  * @author qcy 
  15.  * @create 2020/08/25 15:03:47 
  16.  */ 
  17. @Data 
  18. @NoArgsConstructor 
  19. @TableName(value = "t_lock"
  20. public class Lock { 
  21.  
  22.     /** 
  23.      * 自增序號(hào) 
  24.      */ 
  25.     @TableId(value = "id", type = IdType.AUTO) 
  26.     private Integer id; 
  27.  
  28.     /** 
  29.      * 鎖名稱 
  30.      */ 
  31.     private String name
  32.  
  33.     /** 
  34.      * 存活時(shí)間,單位ms 
  35.      */ 
  36.     private int survivalTime; 
  37.  
  38.     /** 
  39.      * 鎖創(chuàng)建的時(shí)間 
  40.      */ 
  41.     private Date createTime; 
  42.  
  43.     /** 
  44.      * 線程名稱 
  45.      */ 
  46.     private String ThreadName; 

 Dao層:

  1. package com.yang.lock1.dao; 
  2.  
  3. import com.baomidou.mybatisplus.core.mapper.BaseMapper; 
  4. import com.yang.lock1.entity.Lock; 
  5. import org.apache.ibatis.annotations.Mapper; 
  6.  
  7. /** 
  8.  * @author qcy 
  9.  * @create 2020/08/25 15:06:24 
  10.  */ 
  11. @Mapper 
  12. public interface LockDao extends BaseMapper<Lock> { 

 Service接口層:

  1. package com.yang.lock1.service; 
  2.  
  3. import com.baomidou.mybatisplus.extension.service.IService; 
  4. import com.yang.lock1.entity.Lock; 
  5.  
  6. /** 
  7.  * @author qcy 
  8.  * @create 2020/08/25 15:07:44 
  9.  */ 
  10. public interface LockService extends IService<Lock> { 
  11.  
  12.     /** 
  13.      * 阻塞獲取分布式鎖 
  14.      * 
  15.      * @param name         鎖名稱 
  16.      * @param survivalTime 存活時(shí)間 
  17.      */ 
  18.     void lock(String nameint survivalTime); 
  19.  
  20.     /** 
  21.      * 釋放鎖 
  22.      * 
  23.      * @param name 鎖名稱 
  24.      */ 
  25.     public void unLock(String name); 

 Service實(shí)現(xiàn)層:

  1. package com.yang.lock1.service.impl; 
  2.  
  3. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 
  4. import com.yang.lock1.dao.LockDao; 
  5. import com.yang.lock1.entity.Lock; 
  6. import com.yang.lock1.service.LockService; 
  7. import lombok.extern.slf4j.Slf4j; 
  8. import org.springframework.dao.DuplicateKeyException; 
  9. import org.springframework.stereotype.Service; 
  10.  
  11. import java.util.Date
  12.  
  13. /** 
  14.  * @author qcy 
  15.  * @create 2020/08/25 15:08:25 
  16.  */ 
  17. @Slf4j 
  18. @Service 
  19. public class LockServiceImpl extends ServiceImpl<LockDao, Lock> implements LockService { 
  20.  
  21.     @Override 
  22.     public void lock(String nameint survivalTime) { 
  23.         String threadName = "system1-" + Thread.currentThread().getName(); 
  24.         while (true) { 
  25.             Lock lock = this.lambdaQuery().eq(Lock::getName, name).one(); 
  26.             if (lock == null) { 
  27.                 //說(shuō)明無(wú)鎖 
  28.                 Lock lk = new Lock(); 
  29.                 lk.setName(name); 
  30.                 lk.setSurvivalTime(survivalTime); 
  31.                 lk.setThreadName(threadName); 
  32.                 try { 
  33.                     save(lk); 
  34.                     log.info(threadName + "獲取鎖成功"); 
  35.                     return
  36.                 } catch (DuplicateKeyException e) { 
  37.                     //繼續(xù)重試 
  38.                     log.info(threadName + "獲取鎖失敗"); 
  39.                     continue
  40.                 } 
  41.             } 
  42.  
  43.             //此時(shí)有鎖,判斷鎖是否過(guò)期 
  44.             Date now = new Date(); 
  45.             Date expireDate = new Date(lock.getCreateTime().getTime() + lock.getSurvivalTime()); 
  46.             if (expireDate.before(now)) { 
  47.                 //鎖已經(jīng)過(guò)期 
  48.                 boolean result = removeById(lock.getId()); 
  49.                 if (result) { 
  50.                     log.info(threadName + "刪除了過(guò)期鎖"); 
  51.                 } 
  52.  
  53.                 //嘗試獲取鎖 
  54.                 Lock lk = new Lock(); 
  55.                 lk.setName(name); 
  56.                 lk.setSurvivalTime(survivalTime); 
  57.                 lk.setThreadName(threadName); 
  58.                 try { 
  59.                     save(lk); 
  60.                     log.info(threadName + "獲取鎖成功"); 
  61.                     return
  62.                 } catch (DuplicateKeyException e) { 
  63.                     log.info(threadName + "獲取鎖失敗"); 
  64.                 } 
  65.             } 
  66.         } 
  67.  
  68.     } 
  69.  
  70.     @Override 
  71.     public void unLock(String name) { 
  72.         //釋放鎖的時(shí)候,需要注意只能釋放自己創(chuàng)建的鎖 
  73.         String threadName = "system1-" + Thread.currentThread().getName(); 
  74.         Lock lock = lambdaQuery().eq(Lock::getName, name).eq(Lock::getThreadName, threadName).one(); 
  75.         if (lock != null) { 
  76.             boolean b = removeById(lock.getId()); 
  77.             if (b) { 
  78.                 log.info(threadName + "釋放了鎖"); 
  79.             } else { 
  80.                 log.info(threadName + "準(zhǔn)備釋放鎖,但鎖過(guò)期了,被其他客戶端強(qiáng)制釋放掉了"); 
  81.             } 
  82.         } else { 
  83.             log.info(threadName + "準(zhǔn)備釋放鎖,但鎖過(guò)期了,被其他客戶端強(qiáng)制釋放掉了"); 
  84.         } 
  85.     } 
  86.  

 測(cè)試類如下:

 

  1. package com.yang.lock1; 
  2.  
  3. import com.yang.lock1.service.LockService; 
  4. import lombok.extern.slf4j.Slf4j; 
  5. import org.junit.Test; 
  6. import org.junit.runner.RunWith; 
  7. import org.springframework.boot.test.context.SpringBootTest; 
  8. import org.springframework.test.context.junit4.SpringRunner; 
  9.  
  10. import javax.annotation.Resource; 
  11.  
  12. /** 
  13.  * @author qcy 
  14.  * @create 2020/08/25 15:10:54 
  15.  */ 
  16. @Slf4j 
  17. @RunWith(SpringRunner.class) 
  18. @SpringBootTest 
  19. public class Lock1ApplicationTest { 
  20.  
  21.     @Resource 
  22.     LockService lockService; 
  23.  
  24.     @Test 
  25.     public void testLock() { 
  26.         log.info("system1準(zhǔn)備獲取鎖"); 
  27.         lockService.lock("key", 6 * 1000); 
  28.         try { 
  29.             //模擬業(yè)務(wù)耗時(shí) 
  30.             Thread.sleep(4 * 1000); 
  31.         } catch (Exception e) { 
  32.             e.printStackTrace(); 
  33.         } finally { 
  34.             lockService.unLock("key"); 
  35.         } 
  36.     } 
  37.  

 

將代碼復(fù)制一份出來(lái),將system1改為system2。現(xiàn)在,同時(shí)啟動(dòng)兩個(gè)系統(tǒng):

system1的輸出如下:


system2的輸出如下:


第23.037秒時(shí),system1嘗試獲取鎖,23.650秒時(shí)獲取成功,持有分布式鎖。第26秒時(shí)system2嘗試獲取鎖,被阻塞。到27.701秒時(shí),system1釋放掉了鎖,system2在27.749時(shí)才獲取到了鎖,在31秒時(shí)釋放掉了。

現(xiàn)在我們將system1的業(yè)務(wù)時(shí)長(zhǎng)改為10秒,就可以模擬出system2釋放system1超時(shí)的鎖的場(chǎng)景了。

先啟動(dòng)system1,再啟動(dòng)system2

此時(shí)system1的輸出如下:


system2的輸出如下:


14秒時(shí),system1獲取到了鎖,接著由于業(yè)務(wù)耗時(shí)突然超出預(yù)期,需要運(yùn)行10秒。在此期間,system1創(chuàng)建的鎖超過(guò)了其存活時(shí)間。此時(shí)system2在19秒時(shí),刪除了此過(guò)期鎖,接著獲取到了鎖。24秒時(shí),system1回頭發(fā)現(xiàn)自己的鎖已經(jīng)被釋放掉了,最后system2正常釋放掉了自己的鎖。

基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖,還有悲觀鎖與樂(lè)觀鎖方式,我會(huì)另開(kāi)篇幅。

 

責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2024-05-06 00:00:00

.NET分布式鎖技術(shù)

2017-01-16 14:13:37

分布式數(shù)據(jù)庫(kù)

2018-04-03 16:24:34

分布式方式

2017-04-13 10:51:09

Consul分布式

2022-10-27 10:44:14

分布式Zookeeper

2017-10-24 11:28:23

Zookeeper分布式鎖架構(gòu)

2024-10-09 17:12:34

2024-07-29 09:57:47

2023-01-13 07:39:07

2021-08-16 09:55:41

鴻蒙HarmonyOS應(yīng)用

2019-05-30 08:31:39

數(shù)據(jù)庫(kù)QTSDB分布式

2015-10-16 18:03:25

Docker分布式CoreOS

2024-11-28 15:11:28

2023-09-04 08:12:16

分布式鎖Springboot

2022-11-06 19:28:02

分布式鎖etcd云原生

2019-08-19 10:24:33

分布式事務(wù)數(shù)據(jù)庫(kù)

2021-07-13 06:57:12

SpringbootAOP緩存

2024-12-11 12:41:33

2019-06-19 15:40:06

分布式鎖RedisJava

2024-01-24 13:15:00

Redis分布式鎖SpringBoot
點(diǎn)贊
收藏

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