springboot mysql行锁_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_unicode_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.源码地址
相关文章:

简单shell
执行脚本结果重定向 sh hah.sh hello 1>>/home/qiso/job.log 2>&1 上面这句话的意思是 首先通过sh执行脚本hah.sh,其中执行这个脚本的时候,需要传入参数,参数是hello, 1表示的是标准输出,以上脚本执行…

个人随笔、收藏——(包括技术、设计思想等)
1、开源自动化工具 Sahi、Selenium、AutoIt Sahi,是一个用于Web应用程序的自动测试工具。Sahi运行为一个代理服务器,必须把浏览器的代理设置为Sahi服务器。 然后Sahi注入javascript来访问Web页面中的元素。Sahi支持Http与Https并且独立于Web站点或Web应用…

安装QCreator2.5+Qt4.8.2+MinGW_gcc_4.4
QCreator最近升级了。正好想试试新功能,所以把原来安装的QCreator2.3.1Qt4.7.1卸载了。安装新的版本。具体步骤如下: 1# 下载QCreator2.5version。 2# 下载Qt4.8.2version。 3# 根据Qt官网给的链接,下载MinGW-gcc440_1(因为从QCre…

第一次结对作业
211606368林书浩 211606352陈彬 一、预估与实际 PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)Planning计划• Estimate• 估计这个任务需要多少时间3540Development开发• Analysis• 需求分析 (包括学习…

mysql如何根据业务分表设计_mysql分表分库的应用场景和设计方式
很多朋友在论坛和留言区域问mysql在什么情况下才需要进行分库分表,以及采用何种设计方式才是最优的选择,根据这些问题,小编为大家整理了关于MySQL分库分表的应用场景和最优的设计方式举例。一. 分表场景:对于大型的互联网应用来说…

简单protobuf
protobuf的数据类型,有最简单的那种数据类型,就是一个文件中,定义了一个message 可以在一个文件中定义两个message,两个message之间是没有关联的可以在一个文件中,定义两个message,其中一个是简单的&#x…

迭代器、生成器
迭代器 lst range(10) #生成一个枚举列表 从0-9 itr iter(lst) #生成一个迭代器 itr.next() #访问迭代器方法 遍历迭代器 try:while True:val itr.next()print val except StopIteration:pass 注意: 1、如果对list dict tuple 用for遍历,则for内部自动…

Linq 无法删除尚未附加的实体的问题
Linq删除个集合数据, 刚开始用的注释掉的那行, 会提示"无法删除尚未附加的实体"错误, 使用Attach方法依然不行. 想想以前用过DeleteAllOnSubmit没啥问题哈, 估计是_db对象的引用问题, 换了种写法就OK了. //删掉所有工作组部门关联 //wgdrLst Wor…

从云端到边缘 AI推动FPGA应用拓展
近日,全球最大的FPGA厂商赛灵思宣布收购深鉴科技的消息,引发人工智能芯片行业热议,这也是首起中国AI芯片公司被收购的案例。值得注意的是,收购深鉴科技的赛灵思在2018年下半年重点发展方面是汽车自动驾驶。 FPGA市场的竞争正在发生…

mysql中的%_mysql入门
MySQL数据库1 数据库概念(了解)1.1 什么是数据库数据库就是用来存储和管理数据的仓库!数据库存储数据的优先:可存储大量数据;方便检索;保持数据的一致性、完整性;安全,可共享;通过组合分析&am…

关于timewait状态
四次挥手 主动关闭连接的一方,调用close,协议层发送FIN包,在TCP报头的FIN字段设置为1,意思是我要和你断开链接,主动关闭连接的一方进入到了FIN_WATI_1状态 被动关闭的一方收到了FIN包之后,协议层回复ACK包…

DWZ基于ajax重复请求的修复
在同一个通用上传插件,每次都需要客户端去请求服务器,返回的html页面,如果请求的间隔很短的话,ajax会认为是重复作废的请求,这个时候需要修改一下源码来达到在短时间内重复请求也能得到响应找到js/dwz.ajax.js修改源码为function ajaxTodo(url, callback){t Date.parse(new D…

TLS/HTTPS 证书生成与验证
https://www.cnblogs.com/kyrios/p/tls-and-certificates.html 最近在研究基于ssl的传输加密,涉及到了key和证书相关的话题,走了不少弯路,现在总结一下做个备忘 科普:TLS、SSL、HTTPS以及证书 不少人可能听过其中的超过3个名词&am…

高并发系统搭建:web负载均衡
高并发系统搭建:web负载均衡 所谓的负载均衡就是让多个请求尽量均衡的分配到不同的机器上面去 1. HTTP负载均衡 当用户的请求发来之后,web服务器通过修改HTTP响应报头中的Location标记,返回一个新的url,然后浏览器继续请求这个…

centos 7.0 安装mysql_CentOS 7.0yum安装MySQL
1.下载mysql的repo源$ wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm2.安装mysql-community-release-el7-5.noarch.rpm包$ sudo rpm -ivh mysql-community-release-el7-5.noarch.rpm安装这个包后,会获得两个mysql的yum repo源:/…

UVa 11174 - Stand in a Line
http://uva.onlinejudge.org/index.php?optioncom_onlinejudge&Itemid8&pageshow_problem&problem2115 数学的特点在于不断的推导,此题还需要用到 欧拉定理和逆元的相关性质,推荐博客(有部分小错误):http…

计算背板带宽方法
背板带宽:端口数*端口速率*2包转发率:接口带宽(bps)/8bit/(64812)千兆包转发率:1.488Mpps百兆:0.1488Mpps万兆:14.88Mpps例如2950G-48背板2*1000*248*100*213600Mbps13.6Gbps相当于13.6/26.8个千…

Windows下安装PHP开发环境
一、Apache 因为Apache官网只提供源代码,如果要使用必须得自己编译,这里我选择第三方安装包Apache Lounge。 进入Apachelounge官方下载地址:http://www.apachelounge.com/download/首先下载并安装vc redist,这是Apache运行必需的一…

高并发简单设计
系统内存不足,主要是每次来一个请求的时候,就要创建倒排的哈希,这个时候如果高并发的情况下,就会出现问题,每次一个倒排索引占据内存,内存只有2G肯定是不够使用的 可以根据日志分析的结果,看看…

mysql 8.0数据备份恢复_第7章 备份和恢复
## 目录- 备份和恢复类型- 数据库备份方法- 例备份和恢复策略- 使用mysqldump进行备份- 使用二进制日志进行- 点时间(增量)恢复- MyISAM表维护和崩溃恢复备份数据库非常重要,这样您就可以恢复数据,并在发生问题时再次启动并运行,例如系统崩溃…

CSS 实例之打开大门
本个实例主要的效果如下图所示 本案例主要运用到了3D旋转和定位技术。具体步骤如下: 1、首先在页面主体加三个很简单的div标签: <div class"door"><div class"door-l"></div><div class"door-r">…

为 Asp.net 网站新增发送手机短信功能
本文旨在帮助那些为网站发送手机短信正在寻求解决方案还未最终找到解决方案的朋友提供参考。 适合人群 须满足一下条件之一,如果以下3个条件您都不满足,为节约您宝贵的时间,请终止阅读本篇文章。 条件如下: 1.一条短信内容进行短信…

搜索引擎Killed原因排查
问题描述 腾讯云单核2G内存,运行程序的时候,程序有时会挂掉了,设置ulimit -c unlimited之后,想要core文件,结果程序运行的时候,直接提示killed,没有出现core文件 调研查询 killed的原因多是因…
mysql 8.0配置主从同步_MySQL8.0.19开启GTID主从同步CentOS8
前言本次搭建目标为1主2从MySQL主从同步结构。采用CentOS8作为操作系统,IP为[10.0.0.211,10.0.0.212,10.0.0.213]。MySQL版本为8.0.19,端口均采用3306。本文仅讲解主从配置,因此安装MySQL的方式请参考安装文档。GTID模式介绍一、GTID Replica…

IO流总结笔记三
字节流: 抽象基类:InputStream, OutputStream。 字节流可以操作任何数据。注意:字符流使用的数组是字符数组。Char [] chs 字节流使用的数组是字节数组。Byte [] bt 转换流: 特点:1,是字节流…

awk1.0 — awk基础
简介 grep,sed,awk被称为Linux文本处理的三剑客,各有特点 grep:适合文本的匹配和查找 sed:编辑匹配到的文本 awk:对文本进行格式化输出 awk简介 awk的基本语法是 awk [options] Pattern {Actions} …

mysql dump 参数_mysqldump常用参数
收集一些常用的mysqldump命令组合。备份数据库1.导出结构不导出数据 **复制代码代码如下:2.导出数据不导出结构3.导出数据和表结构4.导出特定表的结构导入数据:由于mysqldump导出的是完整的SQL语句,所以用mysql客户程序很容易就能把数据导入了࿱…

细心看完这篇文章,刷新对Javascript Prototype的理解
var person{name:ninja}; person.prototype.sayNamefunction(){return this.name; } 分析上面这段代码,看看有没有问题? 没错,这段代码是有问题的,我们可以通过Chrome看一下执行结果: 错误提示说找不到sayName 属性&am…

那些值得回味的MySQL的基础知识
那些值得回味的MySQL的基础知识 MySQL零碎知识点整理 题记: 在如今甚是流行的MySQL中有些基础的知识却是我们日常工作中处理问题容易忘却的一部分,所以不能忘了本,那么我们现在就去回忆那些曾经熟悉的基本吧,废话不多说了 基础常识ÿ…