返回

在 Go 中使用 Redis 实现轻量级、高性能的分布式锁

后端

在分布式系统中,协调对共享资源的并发访问至关重要。分布式锁提供了一种机制,用于确保只有单个进程或线程在任何给定时间访问资源。本文将探讨如何使用 Go 和 Redis 来实现轻量级、高性能的分布式锁。

基于 Redis 的分布式锁

Redis 是一种流行的键值存储,提供原子的 SETNX(设置不存在)操作,可用于实现分布式锁。当需要获取锁时,客户端可以执行以下步骤:

  1. 使用 SETNX 为锁键设置一个值,该值通常是一个唯一标识符。
  2. 如果 SETNX 成功,则客户端已获取锁。
  3. 为锁键设置一个过期时间,以防止死锁。

Go 中的 Redis 分布式锁

以下 Go 代码演示了如何使用 Redis EXPIRE 命令实现分布式锁:

import (
    "context"
    "fmt"
    "time"

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

// getLock 获取分布式锁
func getLock(ctx context.Context, client *redis.Client, key string) (bool, error) {
    // 设置锁的过期时间为 10 秒
    expiration := time.Second * 10

    // 使用 SETNX 原子地设置锁
    ok, err := client.SetNX(ctx, key, "locked", expiration).Result()
    if err != nil {
        return false, fmt.Errorf("error setting lock: %w", err)
    }

    // 如果 SETNX 成功,则获取锁
    return ok, nil
}

// releaseLock 释放分布式锁
func releaseLock(ctx context.Context, client *redis.Client, key string) error {
    // 使用 DEL 原子地释放锁
    err := client.Del(ctx, key).Err()
    if err != nil {
        return fmt.Errorf("error releasing lock: %w", err)
    }

    return nil
}

使用 Redis Lua 脚本改进

Redis Lua 脚本提供了比 SETNX 更高级别的原子操作。以下脚本可以原子地获取和释放锁:

-- 获取锁
local success = redis.call("setnx", KEYS[1], ARGV[1])
if success == 1 then
    redis.call("expire", KEYS[1], ARGV[2])
end
return success

-- 释放锁
local value = redis.call("get", KEYS[1])
if value == ARGV[1] then
    return redis.call("del", KEYS[1])
end
return 0

使用 Redis WATCH 增强

Redis WATCH 命令可以防止在执行 Lua 脚本时锁被其他客户端修改。以下 Go 代码演示了如何使用 WATCH:

import (
    "context"
    "fmt"
    "time"

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

// getLockWithWatch 获取分布式锁,使用 Redis WATCH 增强
func getLockWithWatch(ctx context.Context, client *redis.Client, key string) (bool, error) {
    // 设置锁的过期时间为 10 秒
    expiration := time.Second * 10

    // 监控锁键的变化
    if _, err := client.Watch(ctx, key).Result(); err != nil {
        return false, fmt.Errorf("error watching key: %w", err)
    }

    // 使用 Lua 脚本原子地获取锁
    ok, err := client.EvalSha(ctx, "getLockLuaScript", []string{key}, []string{"locked", expiration.String()}).Int64()
    if err != nil {
        return false, fmt.Errorf("error running Lua script: %w", err)
    }

    // 如果获取锁失败,则说明锁在 WATCH 期间已被修改
    if ok == 0 {
        return false, nil
    }

    return true, nil
}

结论

本文介绍了如何使用 Go 和 Redis 实现轻量级、高性能的分布式锁。通过结合 Redis EXPIRE、Lua 脚本和 WATCH,我们可以创建可靠且可扩展的分布式锁机制,以确保在分布式系统中对共享资源的并发访问。