golang基于MySQL实现分布式锁插件库go-mysql-lock的使用

golang基于MySQL实现分布式锁插件库go-mysql-lock的使用

go-mysql-lock提供了基于MySQL的GET_LOCK函数的锁原语。锁名称是字符串,MySQL强制锁名称的最大长度为64个字符。

使用场景

虽然Zookeeper和etcd等系统提供了成熟的锁原语,但当你的应用程序主要依赖MySQL的正常运行和健康时,这些系统增加的弹性并没有带来太多好处。go-mysql-lock在你有多个应用程序实例且这些实例都依赖同一个MySQL实例时很有用,你希望这些应用程序实例中只有一个能持有锁并执行某些任务。

安装

go get github.com/sanketplus/go-mysql-lock

示例

package main

import (
    "context"
    "database/sql"
    
    _ "github.com/go-sql-driver/mysql"
    "github.com/sanketplus/go-mysql-lock"
)

func main() {
    // 连接MySQL数据库
    db, _ := sql.Open("mysql", "root@tcp(localhost:3306)/dyno_test")

    // 创建锁实例
    locker := gomysqllock.NewMysqlLocker(db)

    // 获取锁
    lock, _ := locker.Obtain("foo")
    
    // 释放锁
    lock.Release()
}

特性

可定制的刷新周期

一旦获得锁,一个goroutine会定期(默认每秒)在连接上执行ping操作,因为锁在连接(会话)上是有效的。可以配置刷新间隔:

locker := gomysqllock.NewMysqlLocker(db, gomysqllock.WithRefreshInterval(time.Millisecond*500))

使用上下文获取锁

默认情况下,获取锁的尝试是由后台上下文支持的。这意味着Obtain调用将无限期阻塞。可以选择使用用户给定的上下文进行Obtain调用,该上下文将与给定的上下文一起取消。

以下调用将在1秒后放弃,如果没有获得锁:

locker := gomysqllock.NewMysqlLocker(db)
ctxShort, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second))
lock, err := locker.ObtainContext(ctxShort, "key")

使用(MySQL)超时获取锁

MySQL能够在给定的秒数内超时并返回,如果锁不能被获取。在使用ObtainTimeoutObtainTimeoutContext时可以指定这个超时。超时时,返回ErrMySQLTimeout,并且没有获得锁。

以下调用将在1秒后放弃,如果没有获得锁,使用MySQL超时选项:

locker := gomysqllock.NewMysqlLocker(db)
lock, err := locker.ObtainTimeout("key", 1)

知道锁何时丢失

获取的锁有一个上下文,如果锁丢失,该上下文将被取消。这是在一个goroutine不断ping连接时确定的。如果在ping时出现错误,假设连接有错误,上下文将被取消。锁的所有者会收到锁丢失的通知。

context := lock.GetContext()

兼容性

这个库已经针对MySQL 8和MariaDB 10.1进行了测试(自动),它应该适用于MariaDB版本>=10.1,MySQL版本>=5.6,以及Vitess版本>=15.0。

注意,在MariaDB 10.1/MySQL 5.6及更早版本上,GET_LOCK函数不会无限期锁定,因为这些版本不接受0或负值的超时。这意味着在MySQL <=5.6/MariaDB <=10.1中,你不能使用ObtainObtainContext。为了实现类似的目标,你可以使用ObtainTimeout(和ObtainTimeoutContext)并使用非常高的超时值。


更多关于golang基于MySQL实现分布式锁插件库go-mysql-lock的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang基于MySQL实现分布式锁插件库go-mysql-lock的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用go-mysql-lock实现Golang分布式锁

分布式锁是在分布式系统中协调多个进程/服务对共享资源访问的重要机制。go-mysql-lock是一个基于MySQL实现的轻量级分布式锁库,它利用MySQL的行锁特性实现可靠的分布式锁。

安装

go get github.com/sanketplus/go-mysql-lock

基本使用

1. 初始化锁

首先需要建立MySQL连接并创建锁实例:

package main

import (
	"database/sql"
	"fmt"
	"time"

	mysqllock "github.com/sanketplus/go-mysql-lock"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	// 创建MySQL连接
	db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// 创建锁实例
	locker := mysqllock.NewMysqlLock(db)
}

2. 获取锁

// 尝试获取名为"my_lock"的锁,设置10秒超时
lock, err := locker.Obtain("my_lock", 10*time.Second)
if err != nil {
	fmt.Printf("获取锁失败: %v\n", err)
	return
}

// 确保在函数结束时释放锁
defer lock.Release()

3. 检查锁状态

if lock.IsLocked() {
	fmt.Println("成功获取锁")
	// 执行需要锁保护的代码
} else {
	fmt.Println("未能获取锁")
}

高级用法

1. 带重试机制的锁获取

func acquireLockWithRetry(locker *mysqllock.MysqlLock, lockName string, maxRetries int) (*mysqllock.Lock, error) {
	var lock *mysqllock.Lock
	var err error
	
	for i := 0; i < maxRetries; i++ {
		lock, err = locker.Obtain(lockName, 5*time.Second)
		if err == nil {
			return lock, nil
		}
		
		time.Sleep(1 * time.Second)
	}
	
	return nil, fmt.Errorf("获取锁失败,重试%d次后放弃", maxRetries)
}

// 使用示例
lock, err := acquireLockWithRetry(locker, "important_lock", 3)
if err != nil {
	panic(err)
}
defer lock.Release()

2. 自动续期锁

func autoRenewLock(lock *mysqllock.Lock, interval time.Duration, stopChan chan struct{}) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()
	
	for {
		select {
		case <-ticker.C:
			if err := lock.Renew(); err != nil {
				fmt.Printf("续期锁失败: %v\n", err)
				return
			}
		case <-stopChan:
			return
		}
	}
}

// 使用示例
lock, err := locker.Obtain("long_running_lock", 30*time.Second)
if err != nil {
	panic(err)
}
defer lock.Release()

stopChan := make(chan struct{})
go autoRenewLock(lock, 10*time.Second, stopChan)

// 执行长时间任务
time.Sleep(2 * time.Minute)
close(stopChan)

注意事项

  1. MySQL表结构:go-mysql-lock需要一个名为locks的表,结构如下:

    CREATE TABLE IF NOT EXISTS `locks` (
      `id` varchar(255) NOT NULL,
      `owner` varchar(255) NOT NULL,
      `expires_at` timestamp NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
  2. 性能考虑:虽然MySQL分布式锁实现简单,但在高并发场景下可能会成为性能瓶颈。

  3. 锁超时:务必设置合理的锁超时时间,防止死锁。

  4. 连接池:确保MySQL连接池配置合理,避免连接耗尽。

完整示例

package main

import (
	"database/sql"
	"fmt"
	"log"
	"time"

	mysqllock "github.com/sanketplus/go-mysql-lock"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	// 初始化MySQL连接
	db, err := sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/testdb")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// 设置连接池参数
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(5)
	db.SetConnMaxLifetime(5 * time.Minute)

	// 创建锁实例
	locker := mysqllock.NewMysqlLock(db)

	// 尝试获取锁
	lock, err := locker.Obtain("order_processing_lock", 30*time.Second)
	if err != nil {
		log.Fatalf("获取锁失败: %v", err)
	}
	defer lock.Release()

	if !lock.IsLocked() {
		log.Fatal("未能获取锁")
	}

	// 执行需要锁保护的代码
	fmt.Println("成功获取锁,开始处理订单...")
	time.Sleep(10 * time.Second)
	fmt.Println("订单处理完成")
}

go-mysql-lock提供了一种简单可靠的方式在Golang应用中实现分布式锁,特别适合已经使用MySQL作为基础设施的项目。对于更高性能要求的场景,可以考虑基于Redis或Zookeeper的分布式锁实现。

回到顶部