生产环境go-redsync使用示例

Written by

in

文章目录
  • go-redsync是go语言实现分布式锁的常用工具,但官方文档是的入门示例并不是一个可以直接用于生产环境的版本。很多人将官方文档中的入门示例使用到实际项目中导致了生产事故。故文本提供一个可以用于生产环境的使用示例。
  • 官方示例代码为: package main import ( goredislib “github.com/redis/go-redis/v9” “github.com/go-redsync/redsync/v4” “github.com/go-redsync/redsync/v4/redis/goredis/v9” ) func main() { // Create a pool with go-redis (or redigo) which is the pool redisync will // use while communicating with Redis. This can also be any pool that // implements the `redis.Pool` interface. client := goredislib.NewClient(&goredislib.Options{ Addr: “localhost:6379”, }) pool := goredis.NewPool(client) // or, pool := redigo.NewPool(…) // Create an instance of redisync to be used to obtain a mutual exclusion // lock. rs := redsync.New(pool) // Obtain a new mutex by using the same name for all instances wanting the // same lock. mutexname := “my-global-mutex” mutex := rs.NewMutex(mutexname) // Obtain a lock for our given mutex. After this is successful, no one else // can obtain the same lock (the same mutex name) until we unlock it. if err := mutex.Lock(); err != nil { panic(err) } // Do your work that requires the lock. // Release the lock so other processes or threads can obtain a lock. if ok, err := mutex.Unlock(); !ok || err != nil { panic(“unlock failed”) } } 接下来,我们开两个协程实测一下。 // 这里省去创建redis连接的操作 mutexname := “my-global-mutex” var wg sync.WaitGroup // 用于实现主函数等待所有子协程执行完毕之后再退出 wg.Add(2) go func() { defer wg.Done() mutex := rs.NewMutex(mutexname) // 开始尝试获得分布式锁 if err := mutex.Lock(); err != nil { log.Errorf(“failed to acquire lock in task1: %v”, err) return } // 执行一些任务 log.Info(“task1 start at “, time.Now().Format(“15:04:05.000”)) time.Sleep(time.Second * 10) // 模拟一个耗时的任务 log.Info(“task1 end at “, time.Now().Format(“15:04:05.000”)) // 执行完任务,释放锁 if _, err := mutex.Unlock(); err != nil { log.Errorf(“failed to release lock in task1: %v”, err) } }() go func() { defer wg.Done() mutex := rs.NewMutex(mutexname) // 开始尝试获得分布式锁 if err := mutex.Lock(); err != nil { log.Errorf(“failed to acquire lock in task2: %v”, err) return } // 执行一些任务 log.Info(“task2 start at “, time.Now().Format(“15:04:05.000”)) time.Sleep(time.Second * 10) // 模拟一个耗时的任务 log.Info(“task2 end at “, time.Now().Format(“15:04:05.000”)) // 执行完任务,释放锁 if _, err := mutex.Unlock(); err != nil { log.Errorf(“failed to release lock in task2: %v”, err) } }() wg.Wait() 程序执行结果如下: INFO msg=task2 start at 02:22:00.330INFO msg=task1 start at 02:22:08.508INFO msg=task2 end at 02:22:10.330ERROR msg=failed to release lock in task2: lock already taken, locked nodes: [0]INFO msg=task1 end at 02:22:18.508ERROR msg=failed to release lock in task1: lock already taken, locked nodes: [0] 可以看出,分布式锁并没有起作用,任务2还没执行完,任务1就已经获得锁并开始。原因是go-redsync默认的加锁时间只有8秒钟,如果一个任务执行时间超过8秒,则分布式锁会在任务执行结束前释放。
  • 生产环境中,任务没结束时需要调用mutex.Extend()方法延长锁的时间 mutexname := “my-global-mutex” var wg sync.WaitGroup // 用于实现主函数等待所有子协程执行完毕之后再退出 wg.Add(2) go func() { defer wg.Done() mutex := client.NewMutex(mutexname) // 开始尝试获得分布式锁 if err := mutex.Lock(); err != nil { log.Errorf(“failed to acquire lock in task1: %v”, err) return } var lockReleased atomic.Bool lockReleased.Store(false) go func() { // 只要当前任务还在执行,每过1秒就延长锁的过期时间 for { time.Sleep(time.Second) if lockReleased.Load() { return } _, err := mutex.Extend() if err != nil { log.Errorf(“extend lock in task1 fail: %v”, err) } } }() log.Info(“task1 start at “, time.Now().Format(“15:04:05.000”)) time.Sleep(time.Second * 10) // 模拟一个耗时的任务 log.Info(“task1 end at “, time.Now().Format(“15:04:05.000”)) // 执行完任务,释放锁 if _, err := mutex.Unlock(); err != nil { log.Errorf(“failed to release lock in task1: %v”, err) } lockReleased.Store(true) }() go func() { defer wg.Done() mutex := client.NewMutex(mutexname) // 开始尝试获得分布式锁 if err := mutex.Lock(); err != nil { log.Errorf(“failed to acquire lock in task2: %v”, err) return } var lockReleased atomic.Bool lockReleased.Store(false) go func() { // 只要当前任务还在执行,每过1秒就延长锁的过期时间 for { time.Sleep(time.Second) if lockReleased.Load() { return } _, err := mutex.Extend() if err != nil { log.Errorf(“extend lock in task2 fail: %v”, err) } } }() log.Info(“task2 start at “, time.Now().Format(“15:04:05.000”)) time.Sleep(time.Second * 10) // 模拟一个耗时的任务 log.Info(“task2 end at “, time.Now().Format(“15:04:05.000”)) // 执行完任务,释放锁 if _, err := mutex.Unlock(); err != nil { log.Errorf(“failed to release lock in task2: %v”, err) } lockReleased.Store(true) }() wg.Wait() 执行结果如下: INFO msg=task2 start at 02:31:06.973INFO msg=task2 end at 02:31:16.974INFO msg=task1 start at 02:31:17.471INFO msg=task1 end at 02:31:27.471 这一执行结果符合预期,任务1会在任务2执行完之后才能获得锁。
  • 前面的代码示例可用于生产,但代码过于冗长,每次使用分布式锁时都写那么多代码也太麻烦。我们可以将其封装为了个TryLock方法: type LockHolder interface { ReleaseLock() } type lockHolder struct { mutex *redsync.Mutex lockReleased atomic.Bool } func (h *lockHolder) ReleaseLock() { _, err := h.mutex.Unlock() if err != nil { log.Errorf(“failed to release lock: %v”, err) } h.lockReleased.Store(true) } func TryLock(mutexName string) (LockHolder, error) { rs := getRedsync() mutex := rs.NewMutex(mutexName) if err := mutex.Lock(); err != nil { log.Errorf(“failed to acquire lock: %v”, err) return nil, err } holder := &lockHolder{ mutex: mutex, lockReleased: atomic.Bool{}, } holder.lockReleased.Store(false) go func() { for { time.Sleep(time.Second) if holder.lockReleased.Load() { return } _, err := mutex.Extend() if err != nil { log.Errorf(“extend lock fail: %v”, err) } } }() return holder, nil } 接下来使用分布式锁的代码可以简化为: mutexname := “my-global-mutex” var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() lock, err := TryLock(mutexname) if err != nil { log.Errorf(“failed to acquire lock in task1: %v”, err) return } defer lock.ReleaseLock() log.Info(“task1 start at “, time.Now().Format(“15:04:05.000”)) time.Sleep(time.Second * 10) log.Info(“task1 end at “, time.Now().Format(“15:04:05.000”)) }() go func() { defer wg.Done() lock, err := TryLock(mutexname) if err != nil { log.Errorf(“failed to acquire lock in task2: %v”, err) return } defer lock.ReleaseLock() log.Info(“task2 start at “, time.Now().Format(“15:04:05.000”)) time.Sleep(time.Second * 10) log.Info(“task2 end at “, time.Now().Format(“15:04:05.000”)) }() wg.Wait() 到此这篇关于生产环境go-redsync使用示例的文章就介绍到这了,更多相关生产环境go-redsync使用内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: Golang实现Redis分布式锁(Lua脚本+可重入+自动续期) Go语言如何使用分布式锁解决并发问题 在Go语言开发中实现高性能的分布式日志收集的方法 Golang使用etcd构建分布式锁的示例分享 Go使用Redis实现分布式锁的常见方法 Go分布式链路追踪实战探索 分布式架构在Go语言网站的应用 Go语言使用Etcd实现分布式锁 Golang分布式注册中心实现流程讲解
  • 目录
    • 一、问题和意义
    • 二、官方入门示例存在的问题
    • 三、生产环境可用的版本
    • 四、 对go-redsync做封装

    go-redsync是go语言实现分布式锁的常用工具,但官方文档是的入门示例并不是一个可以直接用于生产环境的版本。很多人将官方文档中的入门示例使用到实际项目中导致了生产事故。故文本提供一个可以用于生产环境的使用示例。

    官方示例代码为:

    package main
    
    import (
    	goredislib "github.com/redis/go-redis/v9"
    	"github.com/go-redsync/redsync/v4"
    	"github.com/go-redsync/redsync/v4/redis/goredis/v9"
    )
    
    func main() {
    	// Create a pool with go-redis (or redigo) which is the pool redisync will
    	// use while communicating with Redis. This can also be any pool that
    	// implements the `redis.Pool` interface.
    	client := goredislib.NewClient(&goredislib.Options{
    		Addr: "localhost:6379",
    	})
    	pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
    
    	// Create an instance of redisync to be used to obtain a mutual exclusion
    	// lock.
    	rs := redsync.New(pool)
    
    	// Obtain a new mutex by using the same name for all instances wanting the
    	// same lock.
    	mutexname := "my-global-mutex"
    	mutex := rs.NewMutex(mutexname)
    
    	// Obtain a lock for our given mutex. After this is successful, no one else
    	// can obtain the same lock (the same mutex name) until we unlock it.
    	if err := mutex.Lock(); err != nil {
    		panic(err)
    	}
    
    	// Do your work that requires the lock.
    
    	// Release the lock so other processes or threads can obtain a lock.
    	if ok, err := mutex.Unlock(); !ok || err != nil {
    		panic("unlock failed")
    	}
    }
    

    接下来,我们开两个协程实测一下。

    // 这里省去创建redis连接的操作
    
    mutexname := "my-global-mutex"
    var wg sync.WaitGroup // 用于实现主函数等待所有子协程执行完毕之后再退出
    wg.Add(2)
    go func() {
       defer wg.Done()
       mutex := rs.NewMutex(mutexname)
       // 开始尝试获得分布式锁
       if err := mutex.Lock(); err != nil {
          log.Errorf("failed to acquire lock in task1: %v", err)
          return
       }
    
       // 执行一些任务
       log.Info("task1 start at ", time.Now().Format("15:04:05.000"))
       time.Sleep(time.Second * 10) // 模拟一个耗时的任务
       log.Info("task1 end at ", time.Now().Format("15:04:05.000"))
    
       // 执行完任务,释放锁
       if _, err := mutex.Unlock(); err != nil {
          log.Errorf("failed to release lock in task1: %v", err)
       }
    }()
    go func() {
       defer wg.Done()
       mutex := rs.NewMutex(mutexname)
       // 开始尝试获得分布式锁
       if err := mutex.Lock(); err != nil {
          log.Errorf("failed to acquire lock in task2: %v", err)
          return
       }
    
       // 执行一些任务
       log.Info("task2 start at ", time.Now().Format("15:04:05.000"))
       time.Sleep(time.Second * 10) // 模拟一个耗时的任务
       log.Info("task2 end at ", time.Now().Format("15:04:05.000"))
    
       // 执行完任务,释放锁
       if _, err := mutex.Unlock(); err != nil {
          log.Errorf("failed to release lock in task2: %v", err)
       }
    }()
    wg.Wait()
    

    程序执行结果如下:

    INFO msg=task2 start at 02:22:00.330
    INFO msg=task1 start at 02:22:08.508
    INFO msg=task2 end at 02:22:10.330
    ERROR msg=failed to release lock in task2: lock already taken, locked nodes: [0]
    INFO msg=task1 end at 02:22:18.508
    ERROR msg=failed to release lock in task1: lock already taken, locked nodes: [0]

    可以看出,分布式锁并没有起作用,任务2还没执行完,任务1就已经获得锁并开始。原因是go-redsync默认的加锁时间只有8秒钟,如果一个任务执行时间超过8秒,则分布式锁会在任务执行结束前释放。

    生产环境中,任务没结束时需要调用mutex.Extend()方法延长锁的时间

    mutexname := "my-global-mutex"
    var wg sync.WaitGroup // 用于实现主函数等待所有子协程执行完毕之后再退出
    wg.Add(2)
    go func() {
       defer wg.Done()
       mutex := client.NewMutex(mutexname)
       // 开始尝试获得分布式锁
       if err := mutex.Lock(); err != nil {
          log.Errorf("failed to acquire lock in task1: %v", err)
          return
       }
       var lockReleased atomic.Bool
       lockReleased.Store(false)
    
       go func() { // 只要当前任务还在执行,每过1秒就延长锁的过期时间
          for {
             time.Sleep(time.Second)
             if lockReleased.Load() {
                return
             }
             _, err := mutex.Extend()
             if err != nil {
                log.Errorf("extend lock in task1 fail: %v", err)
             }
          }
       }()
    
       log.Info("task1 start at ", time.Now().Format("15:04:05.000"))
       time.Sleep(time.Second * 10) // 模拟一个耗时的任务
       log.Info("task1 end at ", time.Now().Format("15:04:05.000"))
    
       // 执行完任务,释放锁
       if _, err := mutex.Unlock(); err != nil {
          log.Errorf("failed to release lock in task1: %v", err)
       }
       lockReleased.Store(true)
    }()
    go func() {
       defer wg.Done()
       mutex := client.NewMutex(mutexname)
       // 开始尝试获得分布式锁
       if err := mutex.Lock(); err != nil {
          log.Errorf("failed to acquire lock in task2: %v", err)
          return
       }
       var lockReleased atomic.Bool
       lockReleased.Store(false)
    
       go func() { // 只要当前任务还在执行,每过1秒就延长锁的过期时间
          for {
             time.Sleep(time.Second)
             if lockReleased.Load() {
                return
             }
             _, err := mutex.Extend()
             if err != nil {
                log.Errorf("extend lock in task2 fail: %v", err)
             }
          }
       }()
    
       log.Info("task2 start at ", time.Now().Format("15:04:05.000"))
       time.Sleep(time.Second * 10) // 模拟一个耗时的任务
       log.Info("task2 end at ", time.Now().Format("15:04:05.000"))
    
       // 执行完任务,释放锁
       if _, err := mutex.Unlock(); err != nil {
          log.Errorf("failed to release lock in task2: %v", err)
       }
       lockReleased.Store(true)
    }()
    wg.Wait()
    

    执行结果如下:

    INFO msg=task2 start at 02:31:06.973
    INFO msg=task2 end at 02:31:16.974
    INFO msg=task1 start at 02:31:17.471
    INFO msg=task1 end at 02:31:27.471

    这一执行结果符合预期,任务1会在任务2执行完之后才能获得锁。

    前面的代码示例可用于生产,但代码过于冗长,每次使用分布式锁时都写那么多代码也太麻烦。我们可以将其封装为了个TryLock方法:

    type LockHolder interface {
        ReleaseLock()
    }
    
    type lockHolder struct {
        mutex       *redsync.Mutex
        lockReleased atomic.Bool
    }
    
    func (h *lockHolder) ReleaseLock() {
        _, err := h.mutex.Unlock()
        if err != nil {
            log.Errorf("failed to release lock: %v", err)
        }
        h.lockReleased.Store(true)
    }
    
    func TryLock(mutexName string) (LockHolder, error) {
        rs := getRedsync()
        mutex := rs.NewMutex(mutexName)
        if err := mutex.Lock(); err != nil {
            log.Errorf("failed to acquire lock: %v", err)
            return nil, err
        }
    
        holder := &lockHolder{
            mutex:       mutex,
            lockReleased: atomic.Bool{},
        }
        holder.lockReleased.Store(false)
    
        go func() {
            for {
                time.Sleep(time.Second)
                if holder.lockReleased.Load() {
                    return
                }
                _, err := mutex.Extend()
                if err != nil {
                    log.Errorf("extend lock fail: %v", err)
                }
            }
        }()
        return holder, nil
    }
    

    接下来使用分布式锁的代码可以简化为:

    mutexname := "my-global-mutex"
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
       defer wg.Done()
       lock, err := TryLock(mutexname)
       if err != nil {
          log.Errorf("failed to acquire lock in task1: %v", err)
          return
       }
       defer lock.ReleaseLock()
       log.Info("task1 start at ", time.Now().Format("15:04:05.000"))
       time.Sleep(time.Second * 10)
       log.Info("task1 end at ", time.Now().Format("15:04:05.000"))
    }()
    go func() {
       defer wg.Done()
       lock, err := TryLock(mutexname)
       if err != nil {
          log.Errorf("failed to acquire lock in task2: %v", err)
          return
       }
       defer lock.ReleaseLock()
       log.Info("task2 start at ", time.Now().Format("15:04:05.000"))
       time.Sleep(time.Second * 10)
       log.Info("task2 end at ", time.Now().Format("15:04:05.000"))
    }()
    wg.Wait()
    

    到此这篇关于生产环境go-redsync使用示例的文章就介绍到这了,更多相关生产环境go-redsync使用内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • Golang实现Redis分布式锁(Lua脚本+可重入+自动续期)
    • Go语言如何使用分布式锁解决并发问题
    • 在Go语言开发中实现高性能的分布式日志收集的方法
    • Golang使用etcd构建分布式锁的示例分享
    • Go使用Redis实现分布式锁的常见方法
    • Go分布式链路追踪实战探索
    • 分布式架构在Go语言网站的应用
    • Go语言使用Etcd实现分布式锁
    • Golang分布式注册中心实现流程讲解

    站内搜索