您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關Spring Boot基于數據庫實現分布式鎖的案例的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
1.簡介
分布式鎖的方式有很多種,通常方案有:
網上的實現方式有很多,本文主要介紹的是如果使用mysql實現簡單的分布式鎖,加鎖流程如下圖:
其實大致思想如下:
1.根據一個值來獲取鎖(也就是我這里的tag),如果當前不存在鎖,那么在數據庫插入一條記錄,然后進行處理業務,當結束,釋放鎖(刪除鎖)。
2.如果存在鎖,判斷鎖是否過期,如果過期則更新鎖的有效期,然后繼續處理業務,當結束時,釋放鎖。如果沒有過期,那么獲取鎖失敗,退出。
2.數據庫設計
2.1 數據表介紹
數據庫表是由JPA自動生成的,稍后會對實體進行介紹,內容如下:
CREATE TABLE `lock_info` ( `id` bigint(20) NOT NULL, `expiration_time` datetime NOT NULL, `status` int(11) NOT NULL, `tag` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_tag` (`tag`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
其中:
id:主鍵
tag:鎖的標示,以訂單為例,可以鎖訂單id
expiration_time:過期時間
status:鎖狀態,0,未鎖,1,已經上鎖
3.實現
本文使用SpringBoot 2.0.3.RELEASE,MySQL 8.0.16,ORM層使用的JPA。
3.1 pom
新建項目,在項目中加入jpa和mysql依賴,完整內容如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.dalaoyang</groupId> <artifactId>springboot2_distributed_lock_mysql</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot2_distributed_lock_mysql</name> <description>springboot2_distributed_lock_mysql</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.22</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3.2 配置文件
配置文件配置了一下數據庫信息和jpa的基本配置,如下:
server.port=20001 ##數據庫配置 ##數據庫地址 spring.datasource.url=jdbc:mysql://localhost:3306/lock?characterEncoding=utf8&useSSL=false ##數據庫用戶名 spring.datasource.username=root ##數據庫密碼 spring.datasource.password=12345678 ##數據庫驅動 spring.datasource.driver-class-name=com.mysql.jdbc.Driver ##validate 加載hibernate時,驗證創建數據庫表結構 ##create 每次加載hibernate,重新創建數據庫表結構,這就是導致數據庫表數據丟失的原因。 ##create-drop 加載hibernate時創建,退出是刪除表結構 ##update 加載hibernate自動更新數據庫結構 ##validate 啟動時驗證表的結構,不會創建表 ##none 啟動時不做任何操作 spring.jpa.hibernate.ddl-auto=update ##控制臺打印sql spring.jpa.show-sql=true ##設置innodb spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
3.3 實體類
實體類如下,這里給tag字段設置了唯一索引,防止重復插入相同的數據:
package com.dalaoyang.entity; import lombok.Data; import javax.persistence.*; import java.util.Date; @Data @Entity @Table(name = "LockInfo", uniqueConstraints={@UniqueConstraint(columnNames={"tag"},name = "uk_tag")}) public class Lock { public final static Integer LOCKED_STATUS = 1; public final static Integer UNLOCKED_STATUS = 0; /** * 主鍵id */ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; /** * 鎖的標示,以訂單為例,可以鎖訂單id */ @Column(nullable = false) private String tag; /** * 過期時間 */ @Column(nullable = false) private Date expirationTime; /** * 鎖狀態,0,未鎖,1,已經上鎖 */ @Column(nullable = false) private Integer status; public Lock(String tag, Date expirationTime, Integer status) { this.tag = tag; this.expirationTime = expirationTime; this.status = status; } public Lock() { } }
3.4 repository
repository層只添加了兩個簡單的方法,根據tag查找鎖和根據tag刪除鎖的操作,內容如下:
package com.dalaoyang.repository; import com.dalaoyang.entity.Lock; import org.springframework.data.jpa.repository.JpaRepository; public interface LockRepository extends JpaRepository<Lock, Long> { Lock findByTag(String tag); void deleteByTag(String tag); }
3.5 service
service接口定義了兩個方法,獲取鎖和釋放鎖,內容如下:
package com.dalaoyang.service; public interface LockService { /** * 嘗試獲取鎖 * @param tag 鎖的鍵 * @param expiredSeconds 鎖的過期時間(單位:秒),默認10s * @return */ boolean tryLock(String tag, Integer expiredSeconds); /** * 釋放鎖 * @param tag 鎖的鍵 */ void unlock(String tag); }
實現類對上面方法進行了實現,其內容與上述流程圖中一致,這里不在做介紹,完整內容如下:
package com.dalaoyang.service.impl; import com.dalaoyang.entity.Lock; import com.dalaoyang.repository.LockRepository; import com.dalaoyang.service.LockService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.util.Calendar; import java.util.Date; import java.util.Objects; @Service public class LockServiceImpl implements LockService { private final Integer DEFAULT_EXPIRED_SECONDS = 10; @Autowired private LockRepository lockRepository; @Override @Transactional(rollbackFor = Throwable.class) public boolean tryLock(String tag, Integer expiredSeconds) { if (StringUtils.isEmpty(tag)) { throw new NullPointerException(); } Lock lock = lockRepository.findByTag(tag); if (Objects.isNull(lock)) { lockRepository.save(new Lock(tag, this.addSeconds(new Date(), expiredSeconds), Lock.LOCKED_STATUS)); return true; } else { Date expiredTime = lock.getExpirationTime(); Date now = new Date(); if (expiredTime.before(now)) { lock.setExpirationTime(this.addSeconds(now, expiredSeconds)); lockRepository.save(lock); return true; } } return false; } @Override @Transactional(rollbackFor = Throwable.class) public void unlock(String tag) { if (StringUtils.isEmpty(tag)) { throw new NullPointerException(); } lockRepository.deleteByTag(tag); } private Date addSeconds(Date date, Integer seconds) { if (Objects.isNull(seconds)){ seconds = DEFAULT_EXPIRED_SECONDS; } Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.add(Calendar.SECOND, seconds); return calendar.getTime(); } }
3.6 測試類
創建了一個測試的controller進行測試,里面寫了一個test方法,方法在獲取鎖的時候會sleep 2秒,便于我們進行測試。完整內容如下:
package com.dalaoyang.controller; import com.dalaoyang.service.LockService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @Autowired private LockService lockService; @GetMapping("/tryLock") public Boolean tryLock(String tag, Integer expiredSeconds) { return lockService.tryLock(tag, expiredSeconds); } @GetMapping("/unlock") public Boolean unlock(String tag) { lockService.unlock(tag); return true; } @GetMapping("/test") public String test(String tag, Integer expiredSeconds) { if (lockService.tryLock(tag, expiredSeconds)) { try { //do something //這里使用睡眠兩秒,方便觀察獲取不到鎖的情況 Thread.sleep(2000); } catch (Exception e) { } finally { lockService.unlock(tag); } return "獲取鎖成功,tag是:" + tag; } return "當前tag:" + tag + "已經存在鎖,請稍后重試!"; } }
3.測試
項目使用maven打包,分別使用兩個端口啟動,分別是20000和20001。
java -jar springboot2_distributed_lock_mysql-0.0.1-SNAPSHOT.jar --server.port=20001
java -jar springboot2_distributed_lock_mysql-0.0.1-SNAPSHOT.jar --server.port=20000
分別訪問兩個端口的項目,如圖所示,只有一個請求可以獲取鎖。
感謝各位的閱讀!關于“Spring Boot基于數據庫實現分布式鎖的案例”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。