美文网首页
分布式锁原理

分布式锁原理

作者: 一起DP吧 | 来源:发表于2019-08-22 17:13 被阅读0次

    应用场景

    在业务传统场景下,单机版本的并发控制我们可以利用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<String>(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<Map> 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<String, Integer> currentLockers() {
        Map<String, Integer> 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获得锁
    

    相关文章

      网友评论

          本文标题:分布式锁原理

          本文链接:https://www.haomeiwen.com/subject/zdkosctx.html