MySQL实现分布式锁攻略
什么是分布式锁
分布式锁是分布式系统中用于在多个应用程序实例之间共享互斥访问资源的一种技术。
在分布式系统中,多个应用程序可能同时请求某个资源,如果没有同步机制,就可能会导致资源的竞争和冲突。分布式锁的作用就是限制在同一时间只有一个应用程序实例可以访问该资源,从而确保互斥访问。
实现分布式锁的几个要素
实现分布式锁,需要考虑以下几个要素:
-
互斥性:同一时间,只能有一个客户端获取分布式锁。
-
锁超时:分布式锁可以设置一个超时时间,当获取锁的客户端挂了或者由于其他原因没有主动释放锁时,等待超时后可以强制释放锁。
-
可重入性:同一个客户端可以多次获取分布式锁,但必须释放相同次数。
利用MySQL实现分布式锁
MySQL可以作为分布式锁的一种实现方式,思路是创建一个带唯一索引的表,当多个客户端通过向该表插入一条特定的记录,来竞争获取锁,成功插入记录的客户端获得锁,其他客户端则需要等待或者轮询操作。
具体实现步骤如下:
- 创建一张表(例如,命名为
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
为表的唯一索引,用于限制同一时间只能有一个客户端获得锁。
- 获取分布式锁的客户端,通过向
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()
生成一个唯一的字符串,作为当前客户端的标识。
- 客户端在释放锁之前,需要判断当前锁是否为自己持有,例如:
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技术站