DB分布式锁 多线程情况下对共享资源的操作需要加锁,避免数据被写乱,在分布式系统中,这个问题也是存在的,此时就需要一个分布式锁服务。常见的分布式锁实现一般是基于DB、Redis、zookeeper。下面笔者会按照顺序分析下这3种分布式锁的设计与实现。
分布式锁的实现由多种方式,但是不管怎样,分布式锁一般要有以下特点:
- 排他性:任意时刻,只能有一个client能获取到锁
- 容错性:分布式锁服务一般要满足AP,也就是说,只要分布式锁服务集群节点大部分存活,client就可以进行加锁解锁操作
- 避免死锁:分布式锁一定能得到释放,即使client在释放之前崩溃或者网络不可达
DB 实现方式: 下面就使用数据库的方式来实现一下分布式锁,使用下属方案存在一些问题,代码还是需要改进,请谨慎用于生成。
mavn 依赖
mysql
mysql-connector-java
5.1.6
DBLock 锁实现
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class DbLock {// 插入数据库的值
private static final int LOCK_ID = 1;
// 非阻塞式加锁public boolean tryLock() {boolean bret = false;
try {Statement st = null;
Connection conn = JdbcUtils.getConnection();
// 编写sql
String sql = "INSERT INTO db_lock (id) VALUES (1)";
st = conn.createStatement();
st.execute(sql);
JdbcUtils.close(conn, null);
bret = true;
} catch (Exception ex) {
return bret;
}
return bret;
}/**
* 数据据库锁
*/
public void lock() {// 尝试加锁
if (tryLock()) {
return;
}
// 如果没有成功等待,进行重试
waitLock();
// 递归调用再次尝试枷锁
lock();
}// 让当前线程休眠进行重试public void waitLock() {try {Thread.currentThread().sleep(10);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}/**
*释放锁
*/
public void unlock(){try{
Statement st = null;
Connection conn = JdbcUtils.getConnection();
st = conn.createStatement();
// 编写sql
String sql = "delete from db_lock where id = "+LOCK_ID;
st.execute(sql);
JdbcUtils.close(conn, null);
}catch (SQLException e){
e.printStackTrace();
}
}}
db 工具类 (数据库脚本就不提供了)
package com.qiku.study.db;
import java.sql.*;
public class JdbcUtils {// 可以把几个字符串定义成常量:用户名,密码,URL,驱动类
private static final String USER = "root";
private static final String PWD = "root";
private static final String URL = "jdbc:mysql://10.0.0.1:3306/db_lock";
private static final String DRIVER = "com.mysql.jdbc.Driver";
/**
* 注册驱动(可以省略)
*/
static {
try {
Class.forName(DRIVER);
}
catch (ClassNotFoundException e) { e.printStackTrace();
}
}/**
* 得到数据库的连接
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PWD);
}/**
* 关闭所有打开的资源
*/
public static void close(Connection conn, Statement stmt){
if(stmt != null) {
try { stmt.close();
} catch (SQLException e) { e.printStackTrace();
}
}
if(conn != null) {
try { conn.close();
}catch (SQLException e) { e.printStackTrace();
}
}}/**
* 关闭所有打开的资源 重载
*/
public static void close(Connection conn, Statement stmt, ResultSet rs) {
if(rs != null) {
try { rs.close();
} catch (SQLException e) { e.printStackTrace();
}
}close(conn, stmt);
}
}
测试代码如下:
import com.qiku.study.db.DbLock;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class DBTickerTest {private int count = 100;
DbLock dbLock = new DbLock();
@Test
publicvoid tickeTest() throws Exception {TickRunnable tr = new TickRunnable();
new Thread(tr,"窗口A").start();
new Thread(tr,"窗口B").start();
new Thread(tr,"窗口c").start();
new Thread(tr,"窗口d").start();
new Thread(tr,"窗口e").start();
new Thread(tr,"窗口f").start();
new Thread(tr,"窗口g").start();
Thread.sleep(50000);
}public class TickRunnable implements Runnable {@Override
public void run() {while (count>0) {
dbLock.lock();
try {
if(count>0){
System.out.println(Thread.currentThread().getName()+"售出:"+ count -- + " 张票");
}
}finally {
dbLock.unlock();
}}
}
}
}
DB 实现分布式锁方案缺点如下:
缺点:
1、这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
【java|分布式锁-使用DB 实现】解决方案:
1、数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
2、没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
3、非阻塞的?搞一个while循环,直到insert成功再返回成功。
4、非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)