我需要从应用程序客户端连接到单个Redis实例。
由于客户机将在Kubernetes中复制,我正在研究有关锁的Redis文档,以防止客户机副本之间的竞争。
在搜索和阅读之后,我将注意力集中在以下两个资源上:
SETNX命令:https://redis.io/commands/setnx有趣的是,SETNX文档明确建议不要使用SETNX实现锁,声明它基本上已经过时:
下面的模式被禁止使用Redlock算法. 无论如何,我们记录旧的模式,因为某些现有的实现将链接到此页面作为引用。
然而,Redlock算法是专门为分布式锁量身定制的,因此,当您试图锁定多个Redis实例时,它们实际上指的是多个主服务器。
为了更进一步,库红同步 (golang)声明了New函数如下:
func New(pools []Pool) *Redsync {
return &Redsync{
pools: pools,
}
}它看起来显然是为了支持在Redis集群上的锁定而设计的。
在我的用例中,我将只连接到一个Redis实例。
也许我可以只使用redsync包并传递一个长度的片段,但在我看来,SETNX模式在单个Redis实例上也可以很好地工作。
我看得对吗?谢谢
发布于 2019-06-13 23:59:07
发布于 2021-07-01 08:09:12
我想我可以发个答案作为参考。按照Kevin的建议,我最终使用了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 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脚本(注释解释了它的功能):
--[[
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")
}
}https://stackoverflow.com/questions/56589374
复制相似问题