使用SETNX的单实例Redis锁

我需要从应用程序客户端连接到单个 Redis 实例。

由于客户端将在 Kubernetes 中进行复制,因此我正在研究有关锁的 Redis 文档,以防止客户端副本之间的竞争。

有趣的是,SETNX文档明确建议不要使用SETNX锁来实现,并指出它基本上已经过时了:

为了支持 Redlock 算法,不鼓励使用以下模式 [...]

无论如何,我们都会记录旧模式,因为某些现有实现链接到此页面作为参考。

然而,Redlock 算法是专门为分布式锁量身定制的,因此当一个人试图锁定多个 Redis 实例时,它们实际上是指多个主实例

更进一步,库redsync (golang) 声明该New函数如下:

func New(pools []Pool) *Redsync {

    return &Redsync{

        pools: pools,

    }

}

它看起来毫无疑问是为了支持 Redis 集群上的锁定而设计的。

在我的用例中,我将仅连接到一个 Redis 实例。

也许我可以只使用 redsync 包并传递长度为 1 的切片,但对我来说,该SETNX模式似乎可以在单个 Redis 实例上同样正常工作。

我的看法正确吗?谢谢


慕尼黑5688855
浏览 244回答 2
2回答

慕无忌1623718

是的,Redlock 算法确实是为分布式 Redis 系统设计的,如果您使用单个实例,那么使用 SET 和 SETNX 文档中描述的更简单的锁定方法应该没问题。然而,更重要的一点是:您可能不需要使用锁来避免多个 Redis 客户端之间的冲突。Redis 锁通常用于保护某些外部分布式资源(有关此问题的更多信息,请参阅我的回答)。Redis 本身通常不需要锁;由于 Redis 的单线程特性,许多命令已经是原子的,您可以使用事务或 Lua 脚本来组成任意复杂的原子操作。因此,我的建议是设计您的客户端代码以使用原子性来避免冲突,而不是尝试使用锁(分布式锁或其他锁)。

慕仙森

实现 (Go) 如下所示:// import "github.com/gomodule/redigo/redis"type redisService struct {    pool      *redis.Pool    lastLogin *redis.Script // Lua script initialized into this field}// Constructing a redis clientfunc NewRedisService(config *config.RedisClientConfig) RedisService {    return &redisService{        pool: &redis.Pool{            MaxIdle:     10,            IdleTimeout: 120 * time.Second,            Dial: func() (redis.Conn, error) {                return redis.Dial("tcp", config.BaseURL)            },            TestOnBorrow: func(c redis.Conn, t time.Time) error {                if time.Since(t) < time.Minute {                    return nil                }                _, err := c.Do("PING")                return err            },        },        // initialize Lua script object        // lastLoginLuaScript is a Go const with the script literal        lastLogin: redis.NewScript(1, lastLoginLuaScript),    }}Lua脚本(注释解释了它的作用):--[[    Check if key exists, if it exists, update the value without changing the remaining TTL.    If it doesn't exist, create it.    Script params    KEYS[1] = the account id used as key    ARGV[1] = the key TTL in seconds    ARGV[2] = the value]]--local errorKeyExpired = 'KEXP'local statusKeyUpdated = 'KUPD'local statusKeyCreated = 'KCRE'if redis.call('exists', KEYS[1]) == 1 then    local ttl = redis.call('ttl', KEYS[1])    if ttl < 0 then --[[ no expiry ]]--        redis.call('setex', KEYS[1], ARGV[1], ARGV[2])        return redis.status_reply(statusKeyCreated)    end    if ttl == 0 then --[[ expired ]]--        return redis.error_reply(errorKeyExpired)    end    redis.call('setex', KEYS[1], ttl, ARGV[2])    return redis.status_reply(statusKeyUpdated)else    redis.call('setex', KEYS[1], ARGV[1], ARGV[2])    return redis.status_reply(statusKeyCreated)end用法:func (rs *redisService) UpsertLastLoginTime(key string, ttl uint, value int64) (bool, error) {    conn := rs.pool.Get()    defer conn.Close()    // call Do on the script object    resp, err := rs.lastLogin.Do(conn, key, ttl, value)    switch resp {    case statusKeyCreated:        return true, nil    case statusKeyUpdated:        return false, nil    case errorKeyExpired:        return false, ErrKeyExpired    default:        return false, errors.Wrap(err, "script execution error")    }}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go