首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用SETNX的单实例Redis锁

使用SETNX的单实例Redis锁
EN

Stack Overflow用户
提问于 2019-06-13 22:40:06
回答 2查看 4.7K关注 0票数 13

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

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

在搜索和阅读之后,我将注意力集中在以下两个资源上:

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

下面的模式被禁止使用Redlock算法. 无论如何,我们记录旧的模式,因为某些现有的实现将链接到此页面作为引用。

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

为了更进一步,库红同步 (golang)声明了New函数如下:

代码语言:javascript
复制
func New(pools []Pool) *Redsync {
    return &Redsync{
        pools: pools,
    }
}

它看起来显然是为了支持在Redis集群上的锁定而设计的。

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

也许我可以只使用redsync包并传递一个长度的片段,但在我看来,SETNX模式在单个Redis实例上也可以很好地工作。

我看得对吗?谢谢

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-06-13 23:59:07

的确,Redlock算法是为分布式Redis系统设计的,如果您使用的是单个实例,那么可以使用设置SETNX文档中描述的更简单的锁定方法。

然而,更重要的一点是:您可能不需要使用锁来避免多个Redis客户机之间的冲突。Redis锁通常用于保护一些外部分布式资源(有关此的更多信息,请参见我的答案这里 )。在Redis内部,锁通常是不必要的;由于Redis的单线程特性,许多命令已经是原子的,并且您能够使用事务或Lua脚本来组成任意复杂的原子操作。

因此,我的建议是设计客户端代码,使用原子性来避免冲突,而不是试图使用锁(分布式或其他方式)。

票数 9
EN

Stack Overflow用户

发布于 2021-07-01 08:09:12

我想我可以发个答案作为参考。按照Kevin的建议,我最终使用了Lua脚本来确保原子性。

实现(Go)是这样的:

代码语言:javascript
复制
// import "github.com/gomodule/redigo/redis"

type redisService struct {
    pool      *redis.Pool
    lastLogin *redis.Script // Lua script initialized into this field
}

// Constructing a redis client
func 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脚本(注释解释了它的功能):

代码语言:javascript
复制
--[[
    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

用法:

代码语言:javascript
复制
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")
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56589374

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档