MySQL实现分布式锁

MySQL实现分布式锁攻略

什么是分布式锁

分布式锁是分布式系统中用于在多个应用程序实例之间共享互斥访问资源的一种技术。

在分布式系统中,多个应用程序可能同时请求某个资源,如果没有同步机制,就可能会导致资源的竞争和冲突。分布式锁的作用就是限制在同一时间只有一个应用程序实例可以访问该资源,从而确保互斥访问。

实现分布式锁的几个要素

实现分布式锁,需要考虑以下几个要素:

  1. 互斥性:同一时间,只能有一个客户端获取分布式锁。

  2. 锁超时:分布式锁可以设置一个超时时间,当获取锁的客户端挂了或者由于其他原因没有主动释放锁时,等待超时后可以强制释放锁。

  3. 可重入性:同一个客户端可以多次获取分布式锁,但必须释放相同次数。

利用MySQL实现分布式锁

MySQL可以作为分布式锁的一种实现方式,思路是创建一个带唯一索引的表,当多个客户端通过向该表插入一条特定的记录,来竞争获取锁,成功插入记录的客户端获得锁,其他客户端则需要等待或者轮询操作。

具体实现步骤如下:

  1. 创建一张表(例如,命名为lock_table):

sql
CREATE TABLE `lock_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`resource` varchar(255) NOT NULL,
`expire` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`owner` char(36) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `resource` (`resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

其中,resource为表的唯一索引,用于限制同一时间只能有一个客户端获得锁。

  1. 获取分布式锁的客户端,通过向lock_table插入一条特定的记录来获取锁,例如:

sql
INSERT INTO `lock_table` (`resource`, `expire`, `owner`)
VALUES ('my_lock', DATE_ADD(NOW(), INTERVAL 10 SECOND), UUID());

上述SQL中,my_lock即为需要锁定的资源名(可以根据具体情况进行修改),10 SECOND表示锁的过期时间为10秒,UUID()生成一个唯一的字符串,作为当前客户端的标识。

  1. 客户端在释放锁之前,需要判断当前锁是否为自己持有,例如:

sql
DELETE FROM `lock_table` WHERE `resource` = 'my_lock' AND `owner` = 'clientId';

上述SQL中,my_lock为需要释放的资源,clientId为当前客户端的标识,只有当该记录的owner字段与当前客户端的标识相同时,才可以删除该记录,从而释放锁。

如果当前锁已经过期,则可以强制释放锁:

sql
DELETE FROM `lock_table` WHERE `resource` = 'my_lock' AND `expire` < NOW();

上述SQL中,仅删除过期的记录,其他客户端不能通过插入相同的记录来获得锁。

示例1:PHP实现MySQL分布式锁

下面是一个PHP示例,通过封装MySQL的加锁和解锁操作,来实现分布式锁的获取和释放:

class MysqlLock {
    protected $pdo;
    protected $isLocked = false;

    public function __construct($config) {
        $dsn = "mysql:host={$config['host']};dbname={$config['database']};charset=utf8mb4";
        $this->pdo = new \PDO($dsn, $config['username'], $config['password'], [
            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
            \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
            \PDO::ATTR_EMULATE_PREPARES => false,
            \PDO::ATTR_TIMEOUT => 1, // 单位为秒
        ]);
    }

    public function lock($key, $ttl) {
        if ($this->isLocked) {
            return true;
        }

        $sql = "INSERT INTO `lock_table` (`resource`, `expire`, `owner`) VALUES (?, ?, ?)";
        $expire = time() + $ttl;
        $owner = uniqid();

        $sth = $this->pdo->prepare($sql);
        $result = $sth->execute([$key, date('Y-m-d H:i:s', $expire), $owner]);

        if ($result) {
            $this->isLocked = true;
            return true;
        } else {
            return false;
        }
    }

    public function unlock($key) {
        $sql = "DELETE FROM `lock_table` WHERE `resource` = ? AND `owner` = ?";
        $owner = uniqid();
        $sth = $this->pdo->prepare($sql);
        $result = $sth->execute([$key, $owner]);

        if ($result) {
            $this->isLocked = false;
        }

        return $result;
    }
}

示例2:Go实现MySQL分布式锁

下面是一个Go示例,通过使用MySQL事务来实现分布式锁的获取和释放:

type MysqlLock struct {
    db *gorm.DB
}

func NewMysqlLock(config DBConfig) *MysqlLock {
    db, err := gorm.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", config.Username, config.Password, config.Host, config.Port, config.Database))
    if err != nil {
        panic(err)
    }

    db.AutoMigrate(&Lock{})

    return &MysqlLock{db: db}
}

type Lock struct {
    ID        uint      `gorm:"primary_key"`
    Resource  string    `gorm:"unique_index"`
    Expire    time.Time ``
    Owner     string    ``
    IsExpired bool      `gorm:"type:boolean;default:false"`
}

func (MysqlLock) TableName() string {
    return "lock_table"
}

func (l *MysqlLock) Acquire(key string, ttl int64) bool {
    tx := l.db.Begin()

    lock := Lock{}
    now := time.Now()
    expire := now.Add(time.Duration(ttl) * time.Second)

    query := tx.Model(&Lock{}).Where("resource = ?", key).Where("is_expired = ?", false).Order("id ASC").First(&lock)
    if query.RecordNotFound() {
        // 如果锁不存在,则创建新锁
        lock = Lock{
            Resource: key,
            Expire:   expire,
            Owner:    util.UUID(),
        }
        if err := tx.Create(&lock).Error; err != nil {
            tx.Rollback()
            return false
        }
    } else {
        // 如果锁存在且未过期,则返回false
        if lock.Expire.After(now) {
            tx.Rollback()
            return false
        }

        // 如果锁存在但已过期,则尝试获取锁
        lock.Expire = expire
        lock.Owner = util.UUID()
        lock.IsExpired = false
        if err := tx.Save(&lock).Error; err != nil {
            tx.Rollback()
            return false
        }
    }

    tx.Commit()
    return true
}

func (l *MysqlLock) Release(key string) bool {
    tx := l.db.Begin()

    now := time.Now()

    lock := Lock{}
    query := tx.Model(&Lock{}).Where("resource = ?", key).First(&lock)
    if query.RecordNotFound() {
        // 如果锁不存在,则直接返回true
        tx.Commit()
        return true
    }

    // 如果锁存在且是自己持有,则释放锁
    if lock.Owner == util.UUID() {
        lock.IsExpired = true
        lock.Expire = now
        if err := tx.Save(&lock).Error; err != nil {
            tx.Rollback()
            return false
        }
    }

    tx.Commit()
    return true
}

总结

通过利用MySQL实现分布式锁,可以在分布式系统中实现对共享资源的互斥访问。需要注意的是,使用MySQL实现分布式锁并不是最优的选择,因为MySQL本身的性能和可靠性限制了锁的并发数,且在网络不稳定或者系统崩溃等异常情况下,可能会导致锁无法释放或者死锁等问题。因此,在实际应用中,需要根据具体情况,考虑是否使用MySQL实现分布式锁,或者采用其他更加可靠和高效的技术方案。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:MySQL实现分布式锁 - Python技术站

(0)
上一篇 2023年5月22日
下一篇 2023年5月22日

相关文章

  • MySQL存储过程例子(包含事务,输出参数,嵌套调用)

    下面是关于“MySQL存储过程例子(包含事务,输出参数,嵌套调用)”的完整攻略: 什么是MySQL存储过程 MySQL存储过程是指一段交由MySQL服务器管理的、预编译的、可重复使用的SQL代码,可以在MySQL环境中执行。存储过程通常用于实现复杂的数据处理,或者对应用程序提供一致的接口。 如何创建MySQL存储过程 以下是创建存储过程的一般模板: CREA…

    database 2023年5月21日
    00
  • SQL 插入新记录

    当我们需要往数据库中插入新记录时,就需要使用 SQL 的 INSERT INTO 语句。下面给出 SQL 插入新记录的完整攻略: 语法格式 INSERT INTO table_name (column1, column2, column3, …) VALUES (value1, value2, value3, …); 其中,table_name 是要…

    database 2023年3月27日
    00
  • laravel5使用freetds连接sql server的方法

    下面就给您讲解一下如何使用 Laravel5 和 freetds 连接 SQL Server 的方法: 1、安装相关扩展 首先,需要安装两个扩展 laravel-mssql 和 pdo-dblib。他们可以通过 composer 进行安装,执行以下命令即可: composer require jamesdb/laravel-mssql composer re…

    database 2023年5月22日
    00
  • 万能密码的SQL注入漏洞其PHP环境搭建及防御手段

    当网站应用程序开发者没有使用正确的输入验证操作和安全措施时,SQL注入漏洞可以发生。恶意攻击者可以使用在输入字段中插入的SQL代码来操作正在运行的网站的数据库。此漏洞可以允许攻击者以管理员身份执行命令、修改/删除数据和窃取数据。以下是建立PHP环境以及防止“万能密码的SQL注入漏洞”的完整攻略: PHP环境的搭建 安装PHP环境 在Linux中,可以使用ap…

    database 2023年5月21日
    00
  • Spring Boot如何解决Mysql断连问题

    当使用Spring Boot连接Mysql数据库时,有时会出现Mysql断连的问题,需要通过一些配置和优化来解决。 以下是解决Mysql断连问题的完整攻略: 1. 关闭Mysql的连接超时机制 默认情况下,Mysql会设置一个“wait_timeout”参数,用于控制MySQL服务器主动断开闲置连接的时间。默认值为8小时,即8 * 3600秒。 这个超时机制…

    database 2023年5月22日
    00
  • MySQL TRUNCATE:清空表记录详解

    在MySQL中,TRUNCATE用于清空表中的记录,但该操作会将表结构保留。 与DELETE相比,TRUNCATE对于删除大量数据的情况下可以更高效,因为它不会在日志中保存每行操作。但是,由于它直接清空了整个表,所以在执行TRUNCATE之后将无法恢复数据。 语法: TRUNCATE TABLE table_name; 需要注意的是,TRUNCATE只能用于…

    MySQL 2023年3月9日
    00
  • Redis之key的淘汰策略

    淘汰策略概述 redis作为缓存使用时,在添加新数据的同时自动清理旧的数据。这种行为在开发者社区众所周知,也是流行的memcached系统的默认行为。 redis中使用的LRU淘汰算法是一种近似LRU的算法。 淘汰策略 针对淘汰策略,redis有一下几种配置方案: 1、noeviction:当触发内存阈值时,redis只读不写; 2、allkeys-lru:…

    Redis 2023年4月11日
    00
  • linux 基础命令大全

    Linux 基础命令大全攻略 Linux 操作系统作为一款开源的操作系统,拥有很多强大的命令行工具,可以让 Linux 用户更加高效地进行操作。在本攻略中,我们将为大家介绍一些常用的 Linux 命令,并提供一些示例说明。 目录 常用命令 文件和目录 文本编辑器 常用命令 pwd 描述:显示当前工作目录的路径。 示例: $ pwd /home/user cd…

    database 2023年5月22日
    00
合作推广
合作推广
分享本页
返回顶部