• 基于数据库实现分布式锁


    1.基于数据库表的实现

    首先创建一张锁表

    这里将方法名做唯一性约束

    当我们想锁住某个方法时,可以根据方法名insert一条语句

    INSERT INTO `aa`.`t_lock` (`t_id`, `t_method`, `t_created_time`, `f_memo`) VALUES ('1', 'payMethod', '2019-01-09 15:26:27', '支付方法');

    这时如果有另外一个线程要调用支付的方法,由于违反了数据库唯一性的约束,会抛出异常,因此可以认定为拿不到锁,当拿到锁的方法执行完业务代码后,最后释放锁的时候

    再执行一条delete语句就行。

    delete from t_lock where t_method = "payMethod"

    这个案例中使用method作为唯一性约束,实际总可根据具体业务自行定义,例如排期案例中,即可选取日期作为主键id,主键本身满足唯一性,同时每天的日期也是不一样的。

    问题

    1、数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。

    2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。

    3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。

    4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。

    解决

    1.数据库可以考虑主备方式,主的一旦挂了,即可切换到从库。

    2.可以在表里加一个失效时间的字段,通过另外一个定时任务工程扫表,判断过期时间来释放锁。

    3.可以采用while循环方式,如果插入失败,捕获异常,同时在外层加个判断,如果没有拿到锁,继续执行,直到拿到锁的线程的释放锁,继续执行业务。

    4.可以在表里再加个2个字段,譬如1个是主机ip,和当前线程,当线程锁住时同时插入这两个字段,如果要想拿到锁,根据线程名字和主机ip来查询,如果查询到了,直接分配。

    最终数据表方案设计如下

    CREATE TABLE `t_lock` (
      `t_id` int(11) NOT NULL,
      `t_method` varchar(255) NOT NULL COMMENT '锁定的方法名',
      `t_created_time` datetime NOT NULL COMMENT '创建时间',
      `f_memo` varchar(255) DEFAULT NULL COMMENT '备注',
      `f_host_ip` varchar(255) NOT NULL COMMENT '主机ip',
      `f_thread_name` varchar(255) NOT NULL COMMENT '线程名',
      PRIMARY KEY (`t_id`),
      UNIQUE KEY `method_index` (`t_method`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    2.基于数据库排他锁

    利用数据库的唯一性约束,通过增删来加锁解锁是一种方案,同时利用数据库自带的排他锁也能实现分布式锁。

    同样参考上表

    原始sql

    select  * from  t_lock where t_method = "payMethod";

    现在后面加上 for  update

    public boolean lock() {
       //开启事务
         begin;
        
        while (true) {
           try {
          
           string result = "select  * from  t_lock where t_method = "payMethod" for update;
    
            if (result == null) {
          
                return true;
           }
    
        
           }
           catch(Exception e) {
    
           }
    
        }
    }
    public void unock() {
    //提交事务,释放排他锁
    commit();
    }

    问题:

    阻塞锁? for update语句会在执行成功后立即返回,在执行失败时一直处于阻塞状态,直到成功。

    锁定之后服务器宕机,无法释放?使用这种方式,服务宕机之后数据库会自己把锁释放掉。

    同样还是无法直接解决数据库单点 还有可重入问题。

     总结:

    优点

                   直接借助数据库,容易理解。

    缺点

                会出现的问题多,在解决问题的过程中会使整个方案变得越来越复杂。

               操作数据库需要一定的开销,性能问题需要考虑。不建议和业务表放在一个数据库实例

                使用数据库的行级锁并不一定靠谱,尤其是当我们的锁表并不大的时候。

  • 相关阅读:
    Java修改excel内容
    text标签onchang事件
    shh将数据导出excel
    正则表达式0到200以内的数
    虚拟内存与物理内存
    捕获内核的异常事件
    linux内存(三)内核与用户空间交互
    linux内存(二)高端内存
    linux内存(一) 内核空间与用户空间
    使用tc配置后端设备,来限制虚拟机网卡带宽
  • 原文地址:https://www.cnblogs.com/edison20161121/p/10295087.html
Copyright © 2020-2023  润新知