本篇內(nèi)容介紹了“SpringBoot如何實(shí)現(xiàn)簡單的分布式鎖”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
新鄉(xiāng)ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為成都創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!
1.簡介
分布式鎖的方式有很多種,通常方案有:
基于MySQL數(shù)據(jù)庫 基于redis 基于ZooKeeper
網(wǎng)上的實(shí)現(xiàn)方式有很多,本文主要介紹的是如果使用mysql實(shí)現(xiàn)簡單的分布式鎖,加鎖流程如下圖:
其實(shí)大致思想如下:
1.根據(jù)一個值來獲取鎖(也就是我這里的tag),如果當(dāng)前不存在鎖,那么在數(shù)據(jù)庫插入一條記錄,然后進(jìn)行處理業(yè)務(wù),當(dāng)結(jié)束,釋放鎖(刪除鎖)。
2.如果存在鎖,判斷鎖是否過期,如果過期則更新鎖的有效期,然后繼續(xù)處理業(yè)務(wù),當(dāng)結(jié)束時,釋放鎖。如果沒有過期,那么獲取鎖失敗,退出。
2.數(shù)據(jù)庫設(shè)計(jì)
2.1 數(shù)據(jù)表介紹
數(shù)據(jù)庫表是由JPA自動生成的,稍后會對實(shí)體進(jìn)行介紹,內(nèi)容如下:
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:鎖的標(biāo)示,以訂單為例,可以鎖訂單id expiration_time:過期時間 status:鎖狀態(tài),0,未鎖,1,已經(jīng)上鎖
3.實(shí)現(xiàn)
本文使用SpringBoot 2.0.3.RELEASE,MySQL 8.0.16,ORM層使用的JPA。
3.1 pom
新建項(xiàng)目,在項(xiàng)目中加入jpa和mysql依賴,完整內(nèi)容如下:
3.2 配置文件
配置文件配置了一下數(shù)據(jù)庫信息和jpa的基本配置,如下:
server.port=20001##數(shù)據(jù)庫配置##數(shù)據(jù)庫地址spring.datasource.url=jdbc:mysql://localhost:3306/lock?characterEncoding=utf8&useSSL=false##數(shù)據(jù)庫用戶名spring.datasource.username=root##數(shù)據(jù)庫密碼spring.datasource.password=12345678##數(shù)據(jù)庫驅(qū)動spring.datasource.driver-class-name=com.mysql.jdbc.Driver##validate 加載hibernate時,驗(yàn)證創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu)##create 每次加載hibernate,重新創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu),這就是導(dǎo)致數(shù)據(jù)庫表數(shù)據(jù)丟失的原因。##create-drop 加載hibernate時創(chuàng)建,退出是刪除表結(jié)構(gòu)##update 加載hibernate自動更新數(shù)據(jù)庫結(jié)構(gòu)##validate 啟動時驗(yàn)證表的結(jié)構(gòu),不會創(chuàng)建表##none 啟動時不做任何操作spring.jpa.hibernate.ddl-auto=update##控制臺打印sqlspring.jpa.show-sql=true##設(shè)置innodbspring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
3.3 實(shí)體類
實(shí)體類如下,這里給tag字段設(shè)置了唯一索引,防止重復(fù)插入相同的數(shù)據(jù):
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; /** * 鎖的標(biāo)示,以訂單為例,可以鎖訂單id */ @Column(nullable = false) private String tag; /** * 過期時間 */ @Column(nullable = false) private Date expirationTime; /** * 鎖狀態(tài),0,未鎖,1,已經(jīng)上鎖 */ @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層只添加了兩個簡單的方法,根據(jù)tag查找鎖和根據(jù)tag刪除鎖的操作,內(nèi)容如下:
package com.dalaoyang.repository;import com.dalaoyang.entity.Lock;import org.springframework.data.jpa.repository.JpaRepository;public interface LockRepository extends JpaRepository
3.5 service
service接口定義了兩個方法,獲取鎖和釋放鎖,內(nèi)容如下:
package com.dalaoyang.service;public interface LockService { /** * 嘗試獲取鎖 * @param tag 鎖的鍵 * @param expiredSeconds 鎖的過期時間(單位:秒),默認(rèn)10s * @return */ boolean tryLock(String tag, Integer expiredSeconds); /** * 釋放鎖 * @param tag 鎖的鍵 */ void unlock(String tag);}
實(shí)現(xiàn)類對上面方法進(jìn)行了實(shí)現(xiàn),其內(nèi)容與上述流程圖中一致,這里不在做介紹,完整內(nèi)容如下:
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;@Servicepublic 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 測試類
創(chuàng)建了一個測試的controller進(jìn)行測試,里面寫了一個test方法,方法在獲取鎖的時候會sleep 2秒,便于我們進(jìn)行測試。完整內(nèi)容如下:
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;@RestControllerpublic 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 "當(dāng)前tag:" + tag + "已經(jīng)存在鎖,請稍后重試!"; }}
3.測試
項(xiàng)目使用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
分別訪問兩個端口的項(xiàng)目,如圖所示,只有一個請求可以獲取鎖。
4.總結(jié)
本案例實(shí)現(xiàn)的分布式鎖只是一個簡單的實(shí)現(xiàn)方案,還具備很多問題,不適合生產(chǎn)環(huán)境使用。
“SpringBoot如何實(shí)現(xiàn)簡單的分布式鎖”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!