基于mysql实现分布式锁

维护一个锁表

CREATE TABLE `dist_lock`
(
    `id`          bigint     PRIMARY KEY  NOT NULL AUTO_INCREMENT COMMENT '自增主键',
    `resource_name` varchar(64)             NOT NULL DEFAULT '' COMMENT '资源名字',
    `node_info`         varchar(64)     NOT NULL DEFAULT '' COMMENT '抢到锁的机器和线程信息',
    `count`              int     NOT NULL DEFAULT 0 COMMENT '重入次数',
    `create_at`   bigint                  NOT NULL DEFAULT 0 COMMENT '创建时间戳',
    UNIQUE KEY uk_resource_name (`resource_name`)
) ENGINE = InnoDB DEFAULT CHARSET utf8mb4 COMMENT '分布式锁';

获取锁

public boolean lock(String resourceName, String nodeInfo) {
	String retNodeInfo = @Select("select * from dist_lock where resource_name=#{resourceName}");
	if (nodeInfo == null) {
		// 还没人往里写,尝试获取锁
		int ret = @Insert("insert into dist_lock (resource_name, node_info, count) VALUES (#{resourceName}, #{nodeInfo}, 1) ") ; 
		if (ret == 1) {
			// 插入成功,认为获取到锁
			return true;
		} else {
			// 插入失败,认为获取锁失败
			return false;
		}
	}
	if (nodeInfo.equals(retNodeInfo)) {
		// 是自己写入的,可重入
		@Update("update dist_lock set count = count + 1 where resource_name = #{resourceName} and node_info = #{nodeInfo}")
		return true;
	} else {
		// 是别人写入的,获取锁失败
		return false;
	}
}

释放锁

public boolean unlock(String resourceName, String nodeInfo) {
	String count = @Select("select count from dist_lock where resource_name=#{resourceName} and node_info = #{nodeInfo}");
	if (count == null) {
		// 已经被释放了
		return false;
	}
	if (count == 1)) {
		// 只剩下一次,可以直接删除
		@Delete("delete from dist_lock where resource_name = #{resourceName} and node_info = #{nodeInfo};")
		return true;
	} else {
		// 可重入次数-1
		@("update dist_lock set count = count - 1 where resourceName = #{resourceName} and nodeInfo = #{nodeInfo}")
	}
}

超时自动释放锁

定时任务:

delete from dist_lock where create_time > ${now} - ${lockTimeOut};

需要加事务吗?

在获取锁的伪代码中:
假设c1 c2同时select发现resource是空,没人加锁,然后同时去insert,由于unique index的原因,实际上也只有一个线程可以真正插入成功获得锁。所以不需要加事务。

在释放锁的伪代码中:
假设现在可重入次数为2,c1和c2同时读到了次数为2,那都不满足=1的条件,于是都只会顺序去-1,最终count经过2次-1,==0了,还是没删掉这个锁,有bug。那有没有别的办法呢?假设我们将释放锁改成这样:

public boolean unlock(String resourceName, String nodeInfo) {	
		// 每次无论如何都会将可重入次数-1
		@Update("update dist_lock set count = count - 1 where resource_name = #{resourceName} and node_info = #{nodeInfo}")
		// 每次无论如何都把<=0的锁干掉
		@Delete("delete from dist_lock where resource_name = #{resourceName} and node_info = #{nodeInfo} and count <= 0;")
}

在这种情况下,假设可重入次数count为2
并发情况1:
C1 update, count=1,C2 update, count=0; C1 delete 删除成功,C2 delete 删除失败,最终成功释放了锁
并发情况2:
C1 update, count=1,C1 delete 删除失败;C2 update count=0; C2 delete 删除成功,最终成功释放了锁

测试

在这里插入图片描述
可以看到,支持了可重入”reentrant“,同时也能比较好地释放锁。

参考code

https://github.com/SteveWongBUAA/learning-spring-framework/blob/master/src/main/java/com/vava/distlock/DistLock.java

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章