基于数据库的分布式锁

使用场景:

  某大型网站部署是分布式的,订单系统有三台服务器响应用户请求,生成订单后统一存放到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释放锁后才拿到了锁进行了业务操作。

原文地址:https://www.cnblogs.com/nevermorewang/p/8284132.html

时间: 2024-08-08 16:51:31

基于数据库的分布式锁的相关文章

分布式锁(1) ----- 介绍和基于数据库的分布式锁

线程锁与分布式锁 1.java的synchronize和Lock都是属于线程锁,只能保证同一个进程内的多线程对共享变量修改访问同步.它们的原理都是设置一个可以让所有线程访问到标记,如synchronize是设置对象头的Mark Word,而Lock类是基于AQS的volatile修饰的state. 2.分布式锁是属于进程范畴的,而且进程可以在不同的机器上.它要保证的是多个进程对共享变量修改访问同步(例如集群环境下同时修改缓存和数据库).分布式锁也同样需要一个可以让所有进程访问到的标记(如数据库的

基于redis的分布式锁实现

关于分布式锁 很久之前有讲过并发编程中的锁并发编程的锁机制:synchronized和lock.在单进程的系统中,当存在多个线程可以同时改变某个变量时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量.而同步的本质是通过锁来实现的.为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记.

数据库实现分布式锁

数据库实现分布式锁 基于数据库实现分布式锁 上面已经分析了基于数据库实现分布式锁的基本原理:通过唯一索引保持排他性,加锁时插入一条记录,解锁是删除这条记录.下面我们就简要实现一下基于数据库的分布式锁. 表设计 CREATE TABLE `distributed_lock` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `unique_mutex` varchar(255) NOT NULL COMMENT '业务防重id', `holder_id` var

基于Redis的分布式锁到底安全吗(上)?

网上有关Redis分布式锁的文章可谓多如牛毛了,不信的话你可以拿关键词"Redis 分布式锁"随便到哪个搜索引擎上去搜索一下就知道了.这些文章的思路大体相近,给出的实现算法也看似合乎逻辑,但当我们着手去实现它们的时候,却发现如果你越是仔细推敲,疑虑也就越来越多. 实际上,大概在一年以前,关于Redis分布式锁的安全性问题,在分布式系统专家Martin Kleppmann和Redis的作者antirez之间就发生过一场争论.由于对这个问题一直以来比较关注,所以我前些日子仔细阅读了与这场争

基于redis的分布式锁(不适合用于生产环境)

基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境,另一个是集群环境下的Redis锁实现.在介绍分布式锁的实现之前,先来了解下分布式锁的一些信息. 2 分布式锁 2.1 什么是分布式锁? 分布式锁是控制分布式系统或不同系统之间共同访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥

[Redis] 基于redis的分布式锁

前言分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁. 可靠性首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 互斥性.在任意时刻,只有一个客户端能持有锁.不会发生死锁.即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁.具有容错性.只要大部分的Redis节点正常运行,客户端就可以加锁和解锁.解铃还须系铃人.加锁和解锁必须

基于redis的分布式锁的分析与实践

转:https://my.oschina.net/wnjustdoit/blog/1606215 前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了.乐观锁和悲观锁最根本的区别在于线程之间是否相互阻塞. 那么,本文主要来讨论基于redis的分布式锁算法问题. 从2.6.12版本开始,redis为SET命令增加了一系列选项(SET key value [EX seconds] [PX

基于Redis的分布式锁和Redlock算法

1 前言 前面写了4篇Redis底层实现和工程架构相关文章,感兴趣的读者可以回顾一下: Redis面试热点之底层实现篇-1 Redis面试热点之底层实现篇-2 Redis面试热点之工程架构篇-1 Redis面试热点之工程架构篇-2 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手锏功能,是基于Redis支持的数据类型和分布式架构来实现的,属于小而美的应用. 结合笔者的日常工作,

身为一枚优秀的程序员必备的基于Redis的分布式锁和Redlock算法

1 前言 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手锏功能,是基于Redis支持的数据类型和分布式架构来实现的,属于小而美的应用. 结合笔者的日常工作,今天和大家一起研究下基于Redis的分布式锁和Redlock算法的一些事情. 2.初识锁 1. 锁的双面性 现在我们写的程序基本上都有一定的并发性,要么单台多进线程.要么多台机器集群化,在仅读的场景下是不需要加锁的,因为数