2 回答
TA贡献1744条经验 获得超4个赞
是的,Redlock 算法确实是为分布式 Redis 系统设计的,如果您使用单个实例,那么使用 SET 和 SETNX 文档中描述的更简单的锁定方法应该没问题。
然而,更重要的一点是:您可能不需要使用锁来避免多个 Redis 客户端之间的冲突。Redis 锁通常用于保护某些外部分布式资源(有关此问题的更多信息,请参阅我的回答)。Redis 本身通常不需要锁;由于 Redis 的单线程特性,许多命令已经是原子的,您可以使用事务或 Lua 脚本来组成任意复杂的原子操作。
因此,我的建议是设计您的客户端代码以使用原子性来避免冲突,而不是尝试使用锁(分布式锁或其他锁)。
TA贡献1827条经验 获得超7个赞
实现 (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")
}
}
- 2 回答
- 0 关注
- 161 浏览
添加回答
举报