生产环境go-redsync使用示例

 更新时间:2025年10月01日 11:34:30   作者:qinyuan15  
go-redsync是go语言实现分布式锁的常用工具,但官方文档是的入门示例并不是一个可以直接用于生产环境的版本,本文提供一个可以用于生产环境的使用示例,感兴趣的可以了解一下

一、问题和意义

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执行完之后才能获得锁。

四、 对go-redsync做封装

前面的代码示例可用于生产,但代码过于冗长,每次使用分布式锁时都写那么多代码也太麻烦。我们可以将其封装为了个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使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go 常见设计模式之单例模式详解

    Go 常见设计模式之单例模式详解

    单例模式是设计模式中最简单的一种模式,单例模式能够确保无论对象被实例化多少次,全局都只有一个实例存在,在Go 语言有多种方式可以实现单例模式,所以我们今天就来一起学习下吧
    2023-07-07
  • Go源码分析之预分配slice内存

    Go源码分析之预分配slice内存

    这篇文章主要从Go语言源码带大家分析一下预分配slice内存的相关知识,文中的示例代码简洁易懂,对我们深入了解go有一定的帮助,需要的可以学习一下
    2023-08-08
  • 基于Go语言开发一个JSON格式校验工具

    基于Go语言开发一个JSON格式校验工具

    在日常开发中,经常会遇到需要校验 JSON 格式是否正确的场景,如果 JSON 格式不正确,系统往往会报错甚至崩溃,因此开发一个 命令行 JSON 格式校验工具 非常实用,所以本文给大家介绍了如何基于Go语言开发一个JSON格式校验工具,需要的朋友可以参考下
    2025-09-09
  • sublime安装支持go和html的插件

    sublime安装支持go和html的插件

    这篇文章主要介绍了sublime安装支持go和html的插件,需要的朋友可以参考下
    2015-01-01
  • golang中使用mongo的方法介绍

    golang中使用mongo的方法介绍

    这篇文章主要给大家介绍了关于golang中使用mongo的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-08-08
  • 浅谈Go 语言中逃逸分析是怎么进行的

    浅谈Go 语言中逃逸分析是怎么进行的

    Go 语言的逃逸分析是在编译阶段进行的静态分析过程,用于确定变量的内存分配位置,本文就来介绍一下逃逸分析是怎么进行的,具有一定的参考价值,感兴趣的可以了解一下
    2025-10-10
  • Go中GoFrameMap转换详解

    Go中GoFrameMap转换详解

    本文详细解析了Go语言中gconv.Map()方法的实现原理,该方法通过递归处理将各种数据类型转换为map[string]any格式,感兴趣的可以了解一下
    2026-05-05
  • Golang通道channel的源码分析

    Golang通道channel的源码分析

    channel(通道),顾名思义,是一种通道,一种用于并发环境中数据传递的通道。channel是golang中标志性的概念之一,很好很强大!本文将从源码带大家了解一下channel的使用,希望对大家有所帮助
    2022-12-12
  • go语言字符串的拼接和切片方法总结

    go语言字符串的拼接和切片方法总结

    在go语言中,因为字符串只能被访问,不能被修改,所以进行字符串拼接的时候,golang都需要进行内存拷贝,造成一定的性能消耗,这篇文章主要给大家介绍了关于go语言字符串的拼接和切片的相关资料,需要的朋友可以参考下
    2022-11-11
  • Go语言排序算法之插入排序与生成随机数详解

    Go语言排序算法之插入排序与生成随机数详解

    从这篇文章开始将带领大家学习Go语言的经典排序算法,比如插入排序、选择排序、冒泡排序、希尔排序、归并排序、堆排序和快排,二分搜索,外部排序和MapReduce等,本文将先详细介绍插入排序,并给大家分享了go语言生成随机数的方法,下面来一起看看吧。
    2017-11-11

最新评论