本文介绍SpringBoot基于数据库实现简单的分布式锁。

1.简介

分布式锁的方式有很多种,通常方案有:

基于mysql数据库

基于redis

基于ZooKeeper

网上的实现方式有很多,本文主要介绍的是如果使用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依赖,完整内容如下:

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.0.3.RELEASE

com.dalaoyang

springboot2_distributed_lock_mysql

0.0.1-SNAPSHOT

springboot2_distributed_lock_mysql

springboot2_distributed_lock_mysql

1.8

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-data-jpa

mysql

mysql-connector-java

runtime

org.springframework.boot

spring-boot-starter-test

test

org.projectlombok

lombok

1.16.22

provided

org.springframework.boot

spring-boot-maven-plugin

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 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

分别访问两个端口的项目,如图所示,只有一个请求可以获取锁。

4.总结

本案例实现的分布式锁只是一个简单的实现方案,还具备很多问题,不适合生产环境使用。

5.源码地址

springboot mysql行锁_SpringBoot基于数据库实现简单的分布式锁相关推荐

  1. 基于Redis实现简单的分布式锁

    在分布式场景下,有很多种情况都需要实现最终一致性.在设计远程上下文的领域事件的时候,为了保证最终一致性,在通过领域事件进行通讯的方式中,可以共享存储(领域模型和消息的持久化数据源),或者做全局XA事务 ...

  2. 分布式锁的三种实现方式_分布式锁的几种实现方式~

    目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们"任何一个分布式系统都无法同时满足一致性(Consistency) ...

  3. 分布式锁的三种实现方式_分布式锁的多种实现方式

    目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们"任何一个分布式系统都无法同时满足一致性(Consistency) ...

  4. 用数据库实现了一个分布式锁,虽简陋,但能用!

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 后台回复"k8s",可领取k8s资料 以前参加过一个库 ...

  5. 基于 Redis + Lua 脚本实现分布式锁,确保操作的原子性

    为了保证数据的争用安全,通常要采用锁机制控制. 如果是单应用部署,直接通过synchronized关键字修改方法,就能解决,但是如果是分布式的部署 该方法就不能解决这个问题啦,此时就引出了一个分布式锁 ...

  6. 基于注解的方式实现分布式锁

    基于注解的方式实现分布式锁 关于分布式锁的实现由两种 基于redis 基于zookeeper 为了方便分布式锁的使用, 基于注解的方式抽取成公用组件 DisLock注解 /*** 分布式锁的注解, 通 ...

  7. 基于后端开发Redisson实现分布式锁源码分析解读

    一.分布式锁的概念和使用场景 分布式锁是控制分布式系统之间同步访问共享资源的一种方式. 在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问 ...

  8. **Java有哪些悲观锁的实现_80% 人不知道的 Redis 分布式锁的正确实现方式(Java 版)...

    点击上方"小哈学Java",选择"星标" 回复"资源",领取全网最火的Java核心知识总结 来源:http://sina.lt/gfZU 前 ...

  9. 请列举你了解的分布式锁_这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排!...

    什么是分布式锁? 大家好,我是jack xu,今天跟大家聊一聊分布式锁.首先说下什么是分布式锁,当我们在进行下订单减库存,抢票,选课,抢红包这些业务场景时,如果在此处没有锁的控制,会导致很严重的问题. ...

最新文章

  1. mysql 如何修改wait_timeout,interactive_timeout ,和 session ,global 有什么关系
  2. CSS类名称/选择器中哪些字符有效?
  3. Java EE WEB工程师培训-JDBC+Servlet+JSP整合开发之10.Web_工程结构
  4. OpenCV使用VideoWriter和VideoCapture的实例(附完整代码)
  5. Burpsuite学习(2)
  6. org.springframework.beans.factory.BeanDefinitionStoreExcept
  7. Unity3D热更新之LuaFramework篇[06]--Lua中是怎么实现脚本生命周期的
  8. Tensorflow2.0 tf.function和AutoGraph模式
  9. Docker Compose如何与SkyEye完美结合
  10. oracle10g生成awr报告,oracle 10g awr报告生成步骤及awr报告分析
  11. 大家好 我是新来的
  12. 使用Spring-data-jpa修改数据后,查询到脏数据
  13. python练习:重命名大量图片
  14. 把人工智能体验做到极致,微软深挖人工情感的商机
  15. mysql drop语句怎么用_SQL DROP 语句
  16. pyQt5图片放大和缩小
  17. 如何正确与 HR 谈薪资?
  18. html中用CSS设置背景
  19. 基于springboot的支持http接口+dubbo接口的TestNG自动化测试框架
  20. 数据中心服务器机柜电气参数,数据中心服务器机柜一些安装与参数

热门文章

  1. 在Ubuntu 14.04 64位上使用libpcap进行抓包和解包
  2. 概念艺术绘画学习教程 Schoolism – Foolproof Concept Painting with Airi Pan
  3. PSM-省电模式(PowerSaving Mode)
  4. RTTI(三)相关函数1【转自大富翁】
  5. java读取配置文件信息
  6. 一些大佬博客里的个签
  7. 个人学习某个系统或平台的3问式的整理和细化指引
  8. 替换元素和非替换元素的学习
  9. 如何把文件隐藏在一张图片里面
  10. oracle exec 和 call 区别