golang基于Redis的简化分布式锁实现插件库redis-lock的使用

golang基于Redis的简化分布式锁实现插件库redis-lock的使用

简介

redislock是一个基于Redis的简化分布式锁实现库。它提供了简单易用的API来实现分布式锁功能。

安装

go get github.com/bsm/redislock

使用示例

下面是一个完整的示例代码,展示了如何使用redislock库:

import (
  "context"
  "fmt"
  "log"
  "time"

  "github.com/bsm/redislock"
  "github.com/redis/go-redis/v9"
)

func main() {
	// 连接到Redis
	client := redis.NewClient(&redis.Options{
		Network:	"tcp",
		Addr:		"127.0.0.1:6379",
	})
	defer client.Close()

	// 创建一个新的锁客户端
	locker := redislock.New(client)

	ctx := context.Background()

	// 尝试获取锁
	lock, err := locker.Obtain(ctx, "my-key", 100*time.Millisecond, nil)
	if err == redislock.ErrNotObtained {
		fmt.Println("无法获取锁!")
		// 默认情况下不会重试,下一步应该是返回或在循环中继续尝试
		return
	} else if err != nil {
		log.Fatalln(err)
	}

	// 不要忘记在结束时释放锁
	defer lock.Release(ctx)
	fmt.Println("我获得了锁!")

	// 休眠并检查剩余的TTL
	time.Sleep(50 * time.Millisecond)
	if ttl, err := lock.TTL(ctx); err != nil {
		log.Fatalln(err)
	} else if ttl > 0 {
		fmt.Println("太好了,我仍然持有锁!")
	}

	// 延长我的锁
	if err := lock.Refresh(ctx, 100*time.Millisecond, nil); err != nil {
		log.Fatalln(err)
	}

	// 再休眠一会儿,然后检查
	time.Sleep(100 * time.Millisecond)
	if ttl, err := lock.TTL(ctx); err != nil {
		log.Fatalln(err)
	} else if ttl == 0 {
		fmt.Println("现在,我的锁已经过期了!")
	}
}

主要功能

  1. 获取锁:使用Obtain方法尝试获取锁
  2. 释放锁:使用Release方法释放锁
  3. 检查锁剩余时间:使用TTL方法检查锁的剩余时间
  4. 刷新锁:使用Refresh方法延长锁的持有时间

注意事项

  • 默认情况下,获取锁失败不会自动重试
  • 必须记得在结束时释放锁,否则可能导致死锁
  • 锁有过期时间,可以防止死锁情况

这个库提供了简单而强大的分布式锁实现,适合在需要协调多个进程或服务的场景中使用。


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

1 回复

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


Golang基于Redis的简化分布式锁实现:redis-lock使用指南

概述

在分布式系统中,分布式锁是协调多个服务/进程对共享资源访问的重要机制。Redis因其高性能和原子性操作成为实现分布式锁的常用选择。下面我将介绍一个简化版的基于Redis的分布式锁实现方案。

基本实现原理

Redis分布式锁的核心原理是利用Redis的SETNX(SET if Not eXists)命令或带有NX选项的SET命令来实现原子性的锁获取。一个完善的分布式锁需要考虑:

  1. 互斥性:同一时刻只有一个客户端能持有锁
  2. 避免死锁:锁必须有超时机制
  3. 释放锁的安全性:只能由锁的持有者释放锁
  4. 容错性:Redis节点宕机时锁能自动释放

简化实现代码

以下是使用Go语言实现的简化版Redis分布式锁:

package redislock

import (
	"context"
	"errors"
	"time"

	"github.com/go-redis/redis/v8"
)

var (
	ErrLockFailed = errors.New("failed to acquire lock")
	ErrUnlockFailed = errors.New("failed to unlock")
)

type RedisLock struct {
	client *redis.Client
	key    string
	value  string
	ttl    time.Duration
}

func New(client *redis.Client, key string, ttl time.Duration) *RedisLock {
	return &RedisLock{
		client: client,
		key:    key,
		value:  generateRandomValue(), // 生成随机值确保安全释放
		ttl:    ttl,
	}
}

// Lock 尝试获取锁
func (rl *RedisLock) Lock(ctx context.Context) error {
	ok, err := rl.client.SetNX(ctx, rl.key, rl.value, rl.ttl).Result()
	if err != nil {
		return err
	}
	if !ok {
		return ErrLockFailed
	}
	return nil
}

// Unlock 释放锁
func (rl *RedisLock) Unlock(ctx context.Context) error {
	// 使用Lua脚本确保原子性的"获取-比较-删除"操作
	script := `
	if redis.call("get", KEYS[1]) == ARGV[1] then
		return redis.call("del", KEYS[1])
	else
		return 0
	end
	`
	result, err := rl.client.Eval(ctx, script, []string{rl.key}, rl.value).Int64()
	if err != nil {
		return err
	}
	if result == 0 {
		return ErrUnlockFailed
	}
	return nil
}

// TryLock 尝试获取锁,带重试机制
func (rl *RedisLock) TryLock(ctx context.Context, retryCount int, retryDelay time.Duration) error {
	for i := 0; i < retryCount; i++ {
		err := rl.Lock(ctx)
		if err == nil {
			return nil
		}
		if i < retryCount-1 {
			select {
			case <-time.After(retryDelay):
			case <-ctx.Done():
				return ctx.Err()
			}
		}
	}
	return ErrLockFailed
}

func generateRandomValue() string {
	// 实际实现中应使用更安全的随机值生成方式
	return time.Now().String() // 简化示例
}

使用示例

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/go-redis/redis/v8"
	"./redislock" // 假设上面的代码在redislock包中
)

func main() {
	// 初始化Redis客户端
	rdb := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // 无密码
		DB:       0,  // 默认DB
	})

	// 创建分布式锁实例
	lock := redislock.New(rdb, "my_resource_lock", 10*time.Second)

	// 尝试获取锁
	ctx := context.Background()
	err := lock.Lock(ctx)
	if err != nil {
		log.Fatal("获取锁失败:", err)
	}
	fmt.Println("成功获取锁")

	// 执行业务逻辑
	fmt.Println("执行业务逻辑...")
	time.Sleep(5 * time.Second)

	// 释放锁
	err = lock.Unlock(ctx)
	if err != nil {
		log.Fatal("释放锁失败:", err)
	}
	fmt.Println("成功释放锁")
}

高级特性实现

在实际应用中,你可能还需要实现以下高级特性:

1. 自动续期

// Refresh 刷新锁的TTL
func (rl *RedisLock) Refresh(ctx context.Context) error {
	script := `
	if redis.call("get", KEYS[1]) == ARGV[1] then
		return redis.call("pexpire", KEYS[1], ARGV[2])
	else
		return 0
	end
	`
	result, err := rl.client.Eval(ctx, script, []string{rl.key}, rl.value, rl.ttl.Milliseconds()).Int64()
	if err != nil {
		return err
	}
	if result == 0 {
		return ErrUnlockFailed
	}
	return nil
}

2. 带自动续期的锁获取

func (rl *RedisLock) LockWithRefresh(ctx context.Context, refreshInterval time.Duration) (stopRefresh func(), err error) {
	if err := rl.Lock(ctx); err != nil {
		return nil, err
	}

	stopChan := make(chan struct{})
	go func() {
		ticker := time.NewTicker(refreshInterval)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				if err := rl.Refresh(ctx); err != nil {
					return
				}
			case <-stopChan:
				return
			}
		}
	}()

	return func() {
		close(stopChan)
		_ = rl.Unlock(ctx)
	}, nil
}

注意事项

  1. 锁的粒度:锁的粒度应该尽可能小,只锁定必要的资源
  2. 超时设置:TTL设置要合理,太短可能导致业务未完成锁就释放,太长可能导致死锁
  3. 网络分区:Redis集群发生网络分区时可能导致锁失效,需要考虑Redlock等更复杂的算法
  4. 性能影响:频繁的锁操作会影响系统性能,应尽量减少锁的持有时间

替代方案

如果你不想自己实现,可以考虑以下成熟的Go Redis锁库:

  1. go-redsync - 实现了Redlock算法
  2. redis-lock - 简单的Redis锁实现

这些库提供了更多高级功能和更好的稳定性,适合生产环境使用。

回到顶部