zoukankan      html  css  js  c++  java
  • 基于数据库的分布式锁

    使用场景:
      某大型网站部署是分布式的,订单系统有三台服务器响应用户请求,生成订单后统一存放到order_info表;order_info表要求订单id(order_id)必须是唯一的,那么三台服务器怎么协同工作来确认order_id的唯一性呢?这时候就要用到分布式锁了。
    分布式锁的要求:
      在了解了使用场景之后,再看一下我们需要的分布式锁应该是怎样的(以方法锁为例)
      这把锁要可重入(防止死锁)
      这把锁最好是一个阻塞锁(根据业务考虑是否需要这条)
      有高可用的获取锁跟释放锁的功能
      获取锁跟释放锁的性能要好
    实现方式:
      分布式锁的实现分为3种,基于数据库的,基于缓存的跟基于zookeeper的。接下来我们对这三种方式进行实现。
    基于数据库的分布式锁:
      大概原理:直接创建一张锁表,当要锁住某个方法或者资源时,就在该表中增加一条记录,想要释放的时候就删除这条记录。
    CREATE TABLE `methodLock` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
      `mydesc` varchar(1024) NOT NULL COMMENT '备注信息',
      `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      PRIMARY KEY (`id`),
      UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

      当我们想要锁住某个方法时,执行以下sql:

    insert into methodLock(method_name,desc) values ('具体方法名','描述');
    

      以上的简单实现有几个问题:

      1、这把锁依赖数据库的可用性,如果数据库是一个单点,一旦挂掉,会导致业务系统不可用;
      2、这把锁没有失效时间,一旦解锁操作失败,会导致锁一直存留在数据库中,其它线程无法获得锁;
      3、这把锁只能是非阻塞的,因为数据的insert操作一旦插入失败就直接报错,没有获得锁的线程不会进入排队队列,想要再次获得锁就要再次触发获得锁的操作;
      4、这把锁是非重入的,同一线程在没有释放锁之前无法再次获得该锁,因为表中数据已经存在了。
      当然上面的问题也是可以解决的:
      1、单点问题,两个数据库,双向同步,一旦挂掉切换到另一个上;
      2、失效时间,做一个定时任务,每隔多长时间清理超时数据;
      3、非阻塞问题,程序写for循环多次尝试,直至获取到锁为止;
      4、非重入,增加一个字段,记录获取所的ip跟线程信息,下次查询的时候如果有,则直接给锁;
    示例代码:
      获取锁:
     public boolean lock(){
            int result = 0;
            try {
                result = jdbcTemplate.update("insert into methodLock(method_name,mydesc)values(?,?)", new Object[]{"com.wzy.home.study.distributedlock.MyResources.getNextId()", "获取orderId"});
            }catch (Exception e){
                //todo nothing
            }
            if(result == 1){
                return true;
            }
            return false;
        }
    

      释放锁:

    public boolean unLock(){
            int rows = jdbcTemplate.update("delete from methodLock where method_name = ?",new Object[]{"com.wzy.home.study.distributedlock.MyResources.getNextId()"});
            if(rows == 1){
                return true;
            }else {
                return false;
            }
        }
    

      两个线程,模拟两个客户端进行测试:

    public void testLock(){
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    boolean flag = mysqlLock.lock();
                    if(flag){//有锁
                        int orderId = MyResources.getInstance().getNextId();
                        System.out.println("t1拿到锁,获取的订单id为:"+orderId);
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("t1释放锁了");
                        mysqlLock.unLock();
                    }
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    tryLock();
                }
                //多次尝试获取锁
                private boolean tryLock(){
                    boolean flag = mysqlLock.lock();
                    if(flag){
                        int orderId = MyResources.getInstance().getNextId();
                        System.out.println("t2拿到锁,获取订单id为:"+orderId);
                    }else {
                        System.out.println("t2获取锁失败,再次尝试");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        tryLock();
                        mysqlLock.unLock();
                    }
                    return flag;
                }
            });
    
            t1.start();
            t2.start();
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    

    输出结果:

    t1拿到锁,获取的订单id为:1
    t2获取锁失败,再次尝试
    t2获取锁失败,再次尝试
    t2获取锁失败,再次尝试
    t2获取锁失败,再次尝试
    t2获取锁失败,再次尝试
    t1释放锁了
    t2拿到锁,获取订单id为:2
    可以看到,的确是t2等t1释放锁后才拿到了锁进行了业务操作。

  • 相关阅读:
    Python-装饰器进阶
    JavaScript-CasperJs使用教程
    Python-第三方库requests详解
    PHP-PHP程序员的技术成长规划(By黑夜路人)
    Bootstrap-学习系列
    CSS-常用媒体查询
    Git-随笔
    工具-各种开源
    PHP-PHP5.3及以上版本中检查json格式的方法
    VIM-技巧
  • 原文地址:https://www.cnblogs.com/nevermorewang/p/8284132.html
Copyright © 2011-2022 走看看