溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring Boot基于數(shù)據(jù)庫實現(xiàn)分布式鎖的案例

發(fā)布時間:2021-02-03 13:44:11 來源:億速云 閱讀:296 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關(guān)Spring Boot基于數(shù)據(jù)庫實現(xiàn)分布式鎖的案例的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

1.簡介

分布式鎖的方式有很多種,通常方案有:

  • 基于mysql數(shù)據(jù)庫

  • 基于redis

  • 基于ZooKeeper

網(wǎng)上的實現(xiàn)方式有很多,本文主要介紹的是如果使用mysql實現(xiàn)簡單的分布式鎖,加鎖流程如下圖:

Spring Boot基于數(shù)據(jù)庫實現(xiàn)分布式鎖的案例

其實大致思想如下:

1.根據(jù)一個值來獲取鎖(也就是我這里的tag),如果當(dāng)前不存在鎖,那么在數(shù)據(jù)庫插入一條記錄,然后進行處理業(yè)務(wù),當(dāng)結(jié)束,釋放鎖(刪除鎖)。

2.如果存在鎖,判斷鎖是否過期,如果過期則更新鎖的有效期,然后繼續(xù)處理業(yè)務(wù),當(dāng)結(jié)束時,釋放鎖。如果沒有過期,那么獲取鎖失敗,退出。

2.數(shù)據(jù)庫設(shè)計

2.1 數(shù)據(jù)表介紹

數(shù)據(jù)庫表是由JPA自動生成的,稍后會對實體進行介紹,內(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:鎖的標示,以訂單為例,可以鎖訂單id

  • expiration_time:過期時間

  • status:鎖狀態(tài),0,未鎖,1,已經(jīng)上鎖

3.實現(xiàn)

本文使用SpringBoot 2.0.3.RELEASE,MySQL 8.0.16,ORM層使用的JPA。

3.1 pom

新建項目,在項目中加入jpa和mysql依賴,完整內(nèi)容如下:

<?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 配置文件

配置文件配置了一下數(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時,驗證創(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 啟動時驗證表的結(jié)構(gòu),不會創(chuàng)建表
##none 啟動時不做任何操作
spring.jpa.hibernate.ddl-auto=update

##控制臺打印sql
spring.jpa.show-sql=true
##設(shè)置innodb
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

3.3 實體類

實體類如下,這里給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;

 /**
  * 鎖的標示,以訂單為例,可以鎖訂單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<Lock, Long> {

 Lock findByTag(String tag);

 void deleteByTag(String tag);
}

3.5 service

service接口定義了兩個方法,獲取鎖和釋放鎖,內(nèi)容如下:

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);
}

實現(xiàn)類對上面方法進行了實現(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;


@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 測試類

創(chuàng)建了一個測試的controller進行測試,里面寫了一個test方法,方法在獲取鎖的時候會sleep 2秒,便于我們進行測試。完整內(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;


@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 "當(dāng)前tag:" + tag + "已經(jīng)存在鎖,請稍后重試!";
 }
}

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基于數(shù)據(jù)庫實現(xiàn)分布式鎖的案例

Spring Boot基于數(shù)據(jù)庫實現(xiàn)分布式鎖的案例

感謝各位的閱讀!關(guān)于“Spring Boot基于數(shù)據(jù)庫實現(xiàn)分布式鎖的案例”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI