分布式锁原理

应用场景

在业务传统场景下,单机版本的并发控制我们可以利用sychronizedretrantLock来完成,但是在集群环境下,不同的的JVM如何完成并发控制呢?这就需要利用分布式锁。

实现方式

1.  基于数据库的唯一约束实现
2.  基于缓存如Redis实现
3.  基于ZooKeeper实现

一 基于数据库的实现

实现思路:对lockMethod施加唯一约束

DROP TABLE IF EXISTS `c_table_lock_info`;
CREATE TABLE `c_table_lock_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,,
  `lockMethod` varchar(64) DEFAULT NULL COMMENT '锁定的方法名',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注信息',
  `addTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `updTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_lockMethod` (`lockMethod`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='系统锁信息';

在之后每次遇到需要加锁的方法的时候,先执行以下SQL

INSERT INTO c_table_lock_info(lockMethod,remark) VALUES('加锁的方法名','方法备注');

如果多个请求都需要调用此方法,当其中某一个请求插入成功时,即可认为该请求获取了锁,完成相应的业务逻辑,在处理完之后,调用以下SQL

DELETE FROM c_table_lock_info WHERE lockMethod='加锁的方法名';

此时,锁被释放,可以再次被使用

但是存在一些缺点急需优化

1.  基于数据库实现,数据库的性能会直接影响锁的性能
2.  不具备可重入的性质,若在未释放锁之前,该请求再次调用此方法,导致无法插入数据,获取锁失败,需要加相应的认证信息,确认你还是你,但你还是你的时候,直接把锁给你
3.  锁没有相应的失效机制,在服务宕机之后,数据库中仍然存在该数据,当请求再次过来的时候,没有请求能获取到锁,需要加上失效时间,一段时间之后,锁自动失效
4.  没有阻塞特性,需要优化获取锁的代码逻辑,不断的循环重试去获取锁

二 基于缓存如Redis的实现

Redis是单线程的,也就是说存在很多客人来放松的时候,永远只有一个来处理客人要求的各种服务

实现思路:基于其实现的分布式锁思路就是利用原子命令来完成public String setex(final String key, final int seconds, final String value) {

在保存某一个key的时候并为其设置expirTime

    return (String)(new JedisClusterCommand(this.connectionHandler, this.maxAttempts) {
        public String execute(Jedis connection) {
            return connection.setex(key, seconds, value);
        }
    }).run(key);
}

为了保证在这种情况下的可重入性,仍然需要编写代码封装set

import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description
 * @Author taren
 * @DATE 2019/8/22 16:29
 */
public class RedisLockTest01 {

private ThreadLocal lockers = new ThreadLocal<>();

private Jedis jedis;

public RedisLockTest01(Jedis jedis) {
    this.jedis = jedis;
}

private boolean _lock(String key) {
    return jedis.set(key, "", "nx", "ex", 5L) != null;
}

private void _unlock(String key) {
    jedis.del(key);
}

private Map currentLockers() {
    Map refs = lockers.get();
    if (refs != null) {
        return refs;
    }
    lockers.set(new HashMap<>());
    return lockers.get();
}

public boolean lock(String key) {
    Map refs = currentLockers();
    Integer refCnt = (Integer) refs.get(key);
    if (refCnt != null) {
        refs.put(key, refCnt + 1);
        return true;
    }
    if (!this._lock(key)) {
        return false;
    }
    refs.put(key, 1);
    return true;
}

public boolean unlock(String key) {
    Map refs = currentLockers();
    Integer refCnt = (Integer) refs.get(key);
    if (refCnt == null) {
        return false;
    }
    refCnt -= 1;
    if (refCnt > 0) {
        refs.put(key, refCnt);
    } else {
        refs.remove(key);
        this._unlock(key);
    }
    return true;
}
}

当然setnx的加锁方式也是有缺陷的,完全可以编写Lua脚本来实现,用redis的connection对象去执行脚本

三 基于ZK的实现

实现思路:利用ZK的目录树特性结构,即同一目录下只能有唯一的文件名

1.  创建一个用来实现锁的目录lock
2.  A线程获取锁,就在lock目录下创建顺序节点
3.  遍历lock目录,获取所有的子节点,A发现自己就是最小节点,获得锁
4.  B线程遍历lock目录,发现自己不是最小节点,因为此时A才是最小节点,B对A设置监听
5.  A使用完之后,删除节点,此时B发现自己是最小节点,B获得锁

你可能感兴趣的:(分布式锁原理)