分布式锁_数据库分布式锁实践

分布式锁_数据库分布式锁实践

环境说明

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, 循环获取锁。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值