分布式锁_数据库分布式锁实践
环境说明
ubuntu: 20.04
jdk: 1.8
postgresql: 12
mybatis: 3.5.7
数据库
sudo apt install postgresql
创建数据库用户"myuser"
sudo su postgres -c "createuser -D -A -P myuser"
# 这里需要输入密码,我这里使用"myuser"
创建数据库**“mydb”** 分配权限给"myuser"
sudo su postgres -c "createdb -O myuser mydb"
创建表
登录数据库
sudo su postgres -c "psql -d mydb"
建表语句
CREATE TABLE distribute_lock (
id int4 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
resource_code varchar(255) NOT NULL,
resource_name varchar NULL,
create_time varchar NOT NULL DEFAULT CURRENT_TIME,
CONSTRAINT distribute_lock_pk PRIMARY KEY (id)
);
CREATE UNIQUE INDEX distribute_lock_resource_code_idx ON distribute_lock USING btree (resource_code);
COMMENT ON COLUMN distribute_luck.id IS '主键自增';
COMMENT ON COLUMN distribute_luck.resource_code IS '资源code';
COMMENT ON COLUMN distribute_luck.resource_name IS '资源名称';
COMMENT ON COLUMN distribute_lock.create_time IS '创建时间';
ALTER TABLE distribute_lock OWNER TO myuser;
Java代码开发
pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.quange</groupId>
<artifactId>distribute-lock</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.23</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
数据库分布式锁实现类
DbDistributeLock.java
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.session.SqlSession;
public class DbDistributeLock implements DistributeLock {
// 缓存SqlSession,用于释放锁
ThreadLocal<SqlSession> cache = new ThreadLocal<>();
// 用于资源没有时,进行创建资源时控制并发
static Map<String, String> resourceMap = new ConcurrentHashMap<>();
/**
* mybatis 动态的设置超时时间,比较复杂,这里使用另一中方案, 默认超时10s, 多次去获取锁。
* @param resource
* @param timeout 单位:每10秒
* @return
*/
public boolean lock(String resource, int timeout) {
do {
try {
SqlSession sqlSession = MybatisUtil.getSqlSessionFactory().openSession(false);
DistributeLockMapper mapper = sqlSession.getMapper(DistributeLockMapper.class);
DistributeLockDO distributeLockDO = null;
if (timeout == -1) {
distributeLockDO = mapper.selectForUpdate(resource);
} else {
distributeLockDO = mapper.selectForUpdateWithTimeout(resource);
}
if (distributeLockDO != null) {
cache.set(sqlSession);
resourceMap.remove(resource);
return true;
} else {
if (!resourceMap.containsKey(resource)) {
insertResource(mapper, resource);
sqlSession.commit(true);
}
continue;
}
} catch (Exception e) {
Log.d("timeout");
}
} while (timeout == -1 || timeout > 0);
return false;
}
// 资源不存在时,插入资源
private void insertResource(DistributeLockMapper mapper, String resource) {
try {
resource = resource.intern();
synchronized (resource) {
if (!resourceMap.containsKey(resource)) {
Log.d("insert " + resource );
DistributeLockDO distributeLockDO = new DistributeLockDO();
distributeLockDO.setResourceCode(resource);
distributeLockDO.setResourceName("lock " + resource);
mapper.insertDistributeLock(distributeLockDO);
resourceMap.put(resource, resource);
Log.d("insert success");
}
}
} catch (Exception e){
Log.d("insert error" );
}
}
@Override
public boolean lock(String resource) {
return lock(resource, -1);
}
@Override
public void unLock(String resource) {
SqlSession sqlSession = cache.get();
if (sqlSession != null) {
sqlSession.commit(true);
sqlSession.close();
}
}
}
工具类
MybatisUtil.java
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class MybatisUtil {
private static class SqlSessionFactoryHolder {
private static SqlSessionFactory sqlSessionFactory = createSqlSessionFactory();
}
private final static SqlSessionFactory createSqlSessionFactory() {
// 可以从配置文件中获取,这里就简单hard code
DataSource dataSource = new PooledDataSource("org.postgresql.Driver", "jdbc:postgresql://localhost:5432/mydb", "myuser", "myuser");
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.setMapUnderscoreToCamelCase(true);
configuration.addMapper(DistributeLockMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
return sqlSessionFactory;
}
public final static SqlSessionFactory getSqlSessionFactory() {
return SqlSessionFactoryHolder.sqlSessionFactory;
}
}
DistributeLockMapper.java
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface DistributeLockMapper {
// 因为简单,使用注解的方式
@Options(timeout = 10)
@Select("select id, resource_code, resource_name from distribute_lock where resource_code = #{resourceCode} for update")
DistributeLockDO selectForUpdateWithTimeout(@Param("resourceCode") String resourceCode);
@Select("select id, resource_code, resource_name from distribute_lock where resource_code = #{resourceCode} for update")
DistributeLockDO selectForUpdate(@Param("resourceCode") String resourceCode);
@Insert("insert into distribute_lock (resource_code, resource_name) values ('${resourceCode}', '${resourceName}')")
int insertDistributeLock(DistributeLockDO distributeLockDO);
}
DistributeLockDO.java
public class DistributeLockDO {
private Long id;
private String resourceCode;
private String resourceName;
// 省略get set 方法
}
其他不重要类
点击查看
public interface DistributeLock {
boolean lock(String resource);
boolean unLock(String resource);
}
public enum DistributeLockType {
DB,
REDIS,
ZOOKEEPER
}
public class DistributeLockFactory {
public static DistributeLock getDistributeLock() {
return new DbDistributeLock();
}
public static DistributeLock getDistributeLock(DistributeLockType type) {
switch (type) {
case REDIS:
return new RedisDistributeLock();
case ZOOKEEPER:
return new ZkDistributeLock();
default:
return new DbDistributeLock();
}
}
}
public class Log {
public static void d(String msg) {
System.out.println(System.currentTimeMillis() + " - " + Thread.currentThread().getName() + " - " + msg );
}
}
单进程测试
DbDistributeLockTest.java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import org.junit.Test;
public class DbDistributeLockTest {
public volatile static int j = 1;
@Test
public void testDbDistributeLock() {
DistributeLock distributeLock = DistributeLockFactory.getDistributeLock();
int threadCount = 10;
CyclicBarrier cyclicBarrier = new CyclicBarrier(threadCount);// 控制开始
CountDownLatch countDownLatch = new CountDownLatch(threadCount); // 控制等待并发结束
for (int i = 0; i <threadCount; i++) {
new Thread(()->{
try {
cyclicBarrier.await();// 控制并发
String resource = "testcode22";
Log.d("to get lock: " + resource);
boolean locked = distributeLock.lock(resource);
if (locked) {
Log.d("has get lock: " + resource);
Thread.sleep(1000); // 模拟业务耗时
distributeLock.unLock(resource);
Log.d("release lock: " + resource);
} else {
Log.d("not get lock: " + resource);
}
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
try {
countDownLatch.await(); // 等待并发结束
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
多进程测试
Main.java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
public class Main {
public static void main(String[] args) {
String resource = (args != null && args.length > 0)? args[0] : "testcode"; // 资源
String tid = (args != null && args.length > 1)? args[1] : "0"; // 线程标识
int threadCount = Integer.parseInt((args != null && args.length > 2)? args[2] : "5"); // 并发线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(threadCount);
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
MybatisUtil.getSqlSessionFactory(); // 提前初始化SqlSessionFactory
DistributeLock distributeLock = DistributeLockFactory.getDistributeLock();
for (int i = 0; i <threadCount; i++) {
new Thread(()->{
try {
cyclicBarrier.await();
Log.d("to get lock: " + resource);
boolean locked = distributeLock.lock(resource);
if (locked) {
Log.d("has get lock: " + resource);
Thread.sleep(1000);
distributeLock.unLock(resource);
Log.d("release lock: " + resource);
} else {
Log.d("not get lock: " + resource);
}
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-"+ tid + "-"+i).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
打包:
cd <项目代码路径>
mkdir -p $HOME/Test/DistributeLock/lib
mvn dependency:copy-dependencies -DoutputDirectory=$HOME/Test/DistributeLock/lib
mvn clean package
cp target/distribute-lock-1.0.jar $HOME/Test/DistributeLock/lib
启动脚本:
在**$HOME/Test/DistributeLock/目录下编写启动脚本app.sh**
app.sh
#!/bin/bash
# main入口类
main=com.quange.Main
resource=$1
if [ "$resource" == "" ]; then
resource=testcode$(($RANDOM%100))
fi
workdir=$(dirname $0)
cd $workdir
app_classpath=`find lib -name "*.jar" | xargs | sed "s/ /:/g"`
echo $app_classpath
for ((i=1;i<=5;i++));
do
nohup java -cp $app_classpath $main $resource $i 2>&1 >> test.log &
done
将所有的进程日志都保存到test.log中便于查询而已。
测试:
./app.sh resource_11
结果:
点击查看test.log
1627369194450 - Thread-4-3 - to get lock: resourc_11
1627369194451 - Thread-4-0 - to get lock: resourc_11
1627369194451 - Thread-4-2 - to get lock: resourc_11
1627369194451 - Thread-4-1 - to get lock: resourc_11
1627369194451 - Thread-4-4 - to get lock: resourc_11
1627369194563 - Thread-5-3 - to get lock: resourc_11
1627369194564 - Thread-5-0 - to get lock: resourc_11
1627369194566 - Thread-5-2 - to get lock: resourc_11
1627369194567 - Thread-5-4 - to get lock: resourc_11
1627369194563 - Thread-5-1 - to get lock: resourc_11
1627369194626 - Thread-1-4 - to get lock: resourc_11
1627369194627 - Thread-1-1 - to get lock: resourc_11
1627369194627 - Thread-1-2 - to get lock: resourc_11
1627369194627 - Thread-1-3 - to get lock: resourc_11
1627369194627 - Thread-1-0 - to get lock: resourc_11
1627369194652 - Thread-3-4 - to get lock: resourc_11
1627369194653 - Thread-3-0 - to get lock: resourc_11
1627369194653 - Thread-3-2 - to get lock: resourc_11
1627369194654 - Thread-3-1 - to get lock: resourc_11
1627369194664 - Thread-3-3 - to get lock: resourc_11
1627369194960 - Thread-2-0 - to get lock: resourc_11
1627369194961 - Thread-2-1 - to get lock: resourc_11
1627369194962 - Thread-2-4 - to get lock: resourc_11
1627369194975 - Thread-2-2 - to get lock: resourc_11
1627369194975 - Thread-2-3 - to get lock: resourc_11
1627369195897 - Thread-1-4 - insert resourc_11
1627369195906 - Thread-4-2 - insert resourc_11
1627369195911 - Thread-5-1 - insert resourc_11
1627369196123 - Thread-1-4 - insert success
1627369196207 - Thread-1-3 - has get lock: resourc_11
1627369196287 - Thread-4-1 - insert resourc_11
1627369196290 - Thread-4-1 - insert error
1627369196290 - Thread-4-4 - insert resourc_11
1627369196292 - Thread-4-4 - insert error
1627369196294 - Thread-4-3 - insert resourc_11
1627369196298 - Thread-4-3 - insert error
1627369196311 - Thread-4-2 - insert error
1627369196366 - Thread-5-0 - insert resourc_11
1627369196372 - Thread-5-0 - insert error
1627369196373 - Thread-5-4 - insert resourc_11
1627369196375 - Thread-5-4 - insert error
1627369196382 - Thread-5-1 - insert error
1627369197213 - Thread-1-3 - release lock: resourc_11
1627369197217 - Thread-4-0 - has get lock: resourc_11
1627369198272 - Thread-4-0 - release lock: resourc_11
1627369198273 - Thread-1-2 - has get lock: resourc_11
1627369199335 - Thread-1-2 - release lock: resourc_11
1627369199338 - Thread-5-3 - has get lock: resourc_11
1627369200472 - Thread-5-3 - release lock: resourc_11
1627369200472 - Thread-4-1 - has get lock: resourc_11
1627369201609 - Thread-4-1 - release lock: resourc_11
1627369201610 - Thread-4-2 - has get lock: resourc_11
1627369202741 - Thread-4-2 - release lock: resourc_11
1627369202741 - Thread-5-2 - has get lock: resourc_11
1627369203871 - Thread-5-2 - release lock: resourc_11
1627369203871 - Thread-1-4 - has get lock: resourc_11
1627369205006 - Thread-1-4 - release lock: resourc_11
1627369205006 - Thread-4-3 - has get lock: resourc_11
1627369206103 - Thread-4-3 - release lock: resourc_11
1627369206104 - Thread-1-0 - has get lock: resourc_11
1627369207108 - Thread-1-0 - release lock: resourc_11
1627369207109 - Thread-4-4 - has get lock: resourc_11
1627369208147 - Thread-4-4 - release lock: resourc_11
1627369208148 - Thread-5-1 - has get lock: resourc_11
1627369209284 - Thread-5-1 - release lock: resourc_11
1627369209285 - Thread-1-1 - has get lock: resourc_11
1627369210315 - Thread-1-1 - release lock: resourc_11
1627369210342 - Thread-3-2 - has get lock: resourc_11
1627369211478 - Thread-3-2 - release lock: resourc_11
1627369211478 - Thread-5-0 - has get lock: resourc_11
1627369212612 - Thread-5-0 - release lock: resourc_11
1627369212612 - Thread-5-4 - has get lock: resourc_11
1627369213747 - Thread-5-4 - release lock: resourc_11
1627369213747 - Thread-3-4 - has get lock: resourc_11
1627369214802 - Thread-3-4 - release lock: resourc_11
1627369214802 - Thread-3-0 - has get lock: resourc_11
1627369215805 - Thread-3-0 - release lock: resourc_11
1627369215834 - Thread-2-0 - has get lock: resourc_11
1627369216839 - Thread-2-0 - release lock: resourc_11
1627369216840 - Thread-3-1 - has get lock: resourc_11
1627369217846 - Thread-3-1 - release lock: resourc_11
1627369217846 - Thread-2-3 - has get lock: resourc_11
1627369218855 - Thread-2-3 - release lock: resourc_11
1627369218856 - Thread-3-3 - has get lock: resourc_11
1627369219859 - Thread-3-3 - release lock: resourc_11
1627369219861 - Thread-2-1 - has get lock: resourc_11
1627369220864 - Thread-2-1 - release lock: resourc_11
1627369220865 - Thread-2-4 - has get lock: resourc_11
1627369221870 - Thread-2-4 - release lock: resourc_11
1627369221870 - Thread-2-2 - has get lock: resourc_11
1627369222877 - Thread-2-2 - release lock: resourc_11
总结:
Mybatis动态控制查询的超时时间比较复杂,这里使用默认超时10s, 循环获取锁。