SpringBoot基于数据库实现简单的分布式锁
本文介绍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依赖,完整内容如下:
<?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
分别访问两个端口的项目,如图所示,只有一个请求可以获取锁。


4.总结
本案例实现的分布式锁只是一个简单的实现方案,还具备很多问题,不适合生产环境使用。
5.源码地址
源码地址:https://gitee.com/dalaoyang/springboot_learn/tree/master/springboot2_distributed_lock_mysql
SpringBoot基于数据库实现简单的分布式锁的更多相关文章
- 基于Redis实现简单的分布式锁
在分布式场景下,有很多种情况都需要实现最终一致性.在设计远程上下文的领域事件的时候,为了保证最终一致性,在通过领域事件进行通讯的方式中,可以共享存储(领域模型和消息的持久化数据源),或者做全局XA ...
- 基于 Redis 实现简单的分布式锁
摘要 分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁和释放,这样才能保证数据的安全访问.分布式锁实现的方案有很多 ...
- 基于Redis实现简单的分布式锁【理论】
摘要 分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁和释放,这样才能保证数据的安全访问.分布式锁实现的方案有很多 ...
- 基于zookeeper简单实现分布式锁
https://blog.csdn.net/desilting/article/details/41280869 这里利用zookeeper的EPHEMERAL_SEQUENTIAL类型节点及watc ...
- 利用consul在spring boot中实现最简单的分布式锁
因为在项目实际过程中所采用的是微服务架构,考虑到承载量基本每个相同业务的服务都是多节点部署,所以针对某些资源的访问就不得不用到用到分布式锁了. 这里列举一个最简单的场景,假如有一个智能售货机,由于机器 ...
- 基于ZooKeeper的三种分布式锁实现
[欢迎关注公众号:程序猿讲故事 (codestory),及时接收最新文章] 今天介绍基于ZooKeeper的分布式锁的简单实现,包括阻塞锁和非阻塞锁.同时增加了网上很少介绍的基于节点的非阻塞锁实现,主 ...
- 超详细,新手都能看懂 !使用SpringBoot+Dubbo 搭建一个简单的分布式服务
来自:JavaGuide Github 地址:https://github.com/Snailclimb/springboot-integration-examples 目录: 使用 SpringBo ...
- ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁
作者:Grey 原文地址: ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁 前置知识 完成ZooKeeper集群搭建以及熟悉ZooKeeperAPI基本使用 需求 当多个进 ...
- 基于redis实现可靠的分布式锁
什么是锁 今天要谈的是如何在分布式环境下实现一个全局锁,在开始之前先说说非分布式下的锁: 单机 – 单进程程序使用互斥锁mutex,解决多个线程之间的同步问题 单机 – 多进程程序使用信号量sem,解 ...
随机推荐
- 一小部分机器学习算法小结: 优化算法、逻辑回归、支持向量机、决策树、集成算法、Word2Vec等
优化算法 先导知识:泰勒公式 \[ f(x)=\sum_{n=0}^{\infty}\frac{f^{(n)}(x_0)}{n!}(x-x_0)^n \] 一阶泰勒展开: \[ f(x)\approx ...
- Qt 访问网络的 HttpClient(封装QNetworkAccessManager,且有服务端)
Qt 使用 QNetworkAccessManager 访问网络,这里对其进行了简单的封装,访问网络的代码可以简化为: 1 2 3 HttpClient("http://localhost: ...
- 利用系统自带工具快速实现SSH免密远程登录
一.软件环境 操作系统:CentOS Linux release 7.4.1708 (Core) SSH版本 [root@Geeklp201 ~]# ssh -V OpenSSH_7.4p1, Ope ...
- 如何在 cmd 命令行中查看、修改、删除与添加环境变量
Windows 和 linux 区别 一.查看所有环境变量的名称和值:Linux下:exportWindows下:set二.根据名称查该环境变量的值:Linux下:echo $环境变量名比如:echo ...
- 一线Python运维开发带你秒懂Flask框架
相信曾经纠结过这个问题:怎样才能彻底掌握flask? Flask是一个使用 Python 编写的轻量级 Web 应用框架.其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 . ...
- Spring Boot:整合Swagger文档
综合概述 spring-boot作为当前最为流行的Java web开发脚手架,越来越多的开发者选择用其来构建企业级的RESTFul API接口.这些接口不但会服务于传统的web端(b/s),也会服务于 ...
- 04-MySQL中的数据类型
1 整体说明MYsql的数据类型#1. 数字: 整型:tinyint int bigint 小数: float :在位数比较短的情况下不精准 double ...
- 【jar包管理】Maven BOM
BOM Alibaba Spring Boot Dependencies is a Maven BOM used to manage the versions of most used Alibaba ...
- html、jsp页面标签的遍历
应用场景:最近的项目中二级子页面遍历生成.操作表格比较多,记录一下一直用的遍历方法. 一般此类表格都是通过ajax请求数据,然后从callbackFunction中获取数据集合,遍历生成表: eg: ...
- hdoj2036 改革春风吹满地——叉积
利用向量积(叉积)计算三角形的面积和多边形的面积 关于叉积计算三角形面积和多边形面积 Problem Description " 改革春风吹满地, 不会AC没关系; 实在不行回老家, 还有一 ...