APPEND

从 2.0.0 开始可用。

时间复杂度: O(1)。假设附加值很小并且已经存在的值是任意大小,摊销时间复杂度为 O(1),因为 Redis 使用的动态字符串库将在每次重新分配时使可用空间翻倍。

如果key已经存在并且是字符串,则此命令会value在字符串末尾附加 。如果key不存在,则创建它并将其设置为空字符串,因此在这种特殊情况下, APPEND 将类似于SET 。

返回值

整数回复:追加操作后的字符串长度。

例子

redis> EXISTS mykey
(integer) 0
redis> APPEND mykey "Hello"
(integer) 5
redis> APPEND mykey " World"
(integer) 11
redis> GET mykey
"Hello World"
redis> 

模式:时间序列

APPEND命令可用于创建固定大小样本列表的非常紧凑的表示,通常称为时间序列。每次新样本到达时,我们都可以使用命令存储它

APPEND timeseries "fixed-size sample"

访问时间序列中的单个元素并不难:

  • STRLEN可用于获取样本数。
  • GETRANGE允许随机访问元素。如果我们的时间序列具有相关的时间信息,我们可以轻松地实现二进制搜索以获取范围,将GETRANGE与 Redis 2.6 中可用的 Lua 脚本引擎相结合。
  • SETRANGE可用于覆盖现有的时间序列。

这种模式的局限性在于我们被迫进入仅附加操作模式,无法轻松地将时间序列切割为给定大小,因为 Redis 当前缺少能够修剪字符串对象的命令。然而,以这种方式存储的时间序列的空间效率是显着的。

提示:可以根据当前的 Unix 时间切换到不同的 key,这样每个 key 可能只有相对少量的样本,避免处理非常大的 key,并使这种模式更友好地分布在许多 Redis 实例中。

使用固定大小的字符串对传感器温度进行采样的示例(在实际实现中使用二进制格式更好)。

redis> APPEND ts "0043"
(integer) 4
redis> APPEND ts "0035"
(integer) 8
redis> GETRANGE ts 0 3
"0043"
redis> GETRANGE ts 4 7
"0035"
redis> 

DECR

从 1.0.0 开始可用。
段落引用时间复杂度: O(1)

将存储的数字减key一。如果该键不存在,则0在执行操作之前将其设置为。如果键包含错误类型的值或包含无法表示为整数的字符串,则会返回错误。此操作仅限于64 位有符号整数。

有关递增/递减操作的更多信息,请参见INCR。

返回值

整数回复:key减量后的值

例子

redis> SET mykey "10"
"OK"
redis> DECR mykey
(integer) 9
redis> SET mykey "234293482390480948029348230948"
"OK"
redis> DECR mykey
ERR ERR value is not an integer or out of range
redis> 

DECRBY

从 1.0.0 开始可用。
时间复杂度: O(1)

key将存储在的数字递减decrement. 如果该键不存在,则0在执行操作之前将其设置为。如果键包含错误类型的值或包含无法表示为整数的字符串,则会返回错误。此操作仅限于 64 位有符号整数。

有关递增/递减操作的更多信息,请参见INCR。

返回值

整数回复:key减量后的值

例子

redis> SET mykey "10"
"OK"
redis> DECRBY mykey 3
(integer) 7
redis> 

GET

从 1.0.0 开始可用。
时间复杂度: O(1)

获取 的值key。如果键不存在,nil则返回特殊值。如果存储的值key不是字符串,则会返回错误,因为GET 只处理字符串值。

返回值

批量字符串回复: 的值key,或者nil当key不存在时。

例子

redis> GET nonexisting
(nil)
redis> SET mykey "Hello"
"OK"
redis> GET mykey
"Hello"
redis> 

GETDEL

从 6.2.0 开始可用。
时间复杂度: O(1)

获取键的值key并删除。此命令类似于GET,除了它还会在成功时删除键(当且仅当键的值类型是字符串时)。

返回值

批量字符串回复: 的值key,nilwhenkey不存在,如果键的值类型不是字符串,则返回错误。

例子

redis> SET mykey "Hello"
"OK"
redis> GETDEL mykey
ERR Unknown or disabled command 'GETDEL'
redis> GET mykey
"Hello"
redis> 

GETEX

从 6.2.0 开始可用。
时间复杂度: O(1)

获取 的值key并可选择设置其过期时间。 GETEX类似于GET,但它是一个带有附加选项的写入命令。

选项
GETEX命令支持一组修改其行为的选项:

  • EX seconds -- 设置指定的过期时间,以秒为单位。
  • PX 毫秒——设置指定的过期时间,以毫秒为单位。
  • EXAT timestamp-seconds -- 设置密钥过期的指定 Unix 时间,以秒为单位。
  • PXAT timestamp-milliseconds -- 设置密钥过期的指定 Unix 时间,以毫秒为单位。
  • PERSIST -- 删除与密钥关联的生存时间。

返回值

批量字符串回复: 的值key,或者nil当key不存在时。

例子

redis> SET mykey "Hello"
"OK"
redis> GETEX mykey
ERR Unknown or disabled command 'GETEX'
redis> TTL mykey
(integer) -1
redis> GETEX mykey EX 60
ERR Unknown or disabled command 'GETEX'
redis> TTL mykey
(integer) -1
redis> 

GETRANGE

自 2.4.0 起可用。
时间复杂度: O(N),其中 N 是返回字符串的长度。复杂度最终取决于返回的长度,但由于从现有字符串创建子字符串非常便宜,因此对于小字符串可以考虑 O(1)。

返回存储在 的字符串值的子字符串key,由偏移量start和end(都包括在内)确定。可以使用负偏移量来提供从字符串末尾开始的偏移量。所以 -1 表示最后一个字符,-2 表示倒数第二个字符,依此类推。

该函数通过将结果范围限制为字符串的实际长度来处理超出范围的请求。

返回值

批量字符串回复

例子

redis> SET mykey "This is a string"
"OK"
redis> GETRANGE mykey 0 3
"This"
redis> GETRANGE mykey -3 -1
"ing"
redis> GETRANGE mykey 0 -1
"This is a string"
redis> GETRANGE mykey 10 100
"string"
redis> 

GETSET

从 1.0.0 开始可用。
时间复杂度: O(1)

弃用通知: 从 Redis 版本 6.2.0 开始,此命令被视为已弃用。虽然它不太可能被完全删除,但更喜欢使用带有 !GET 参数的 SET 来代替它。

以原子方式设置并key返回value存储在的旧值key。key存在但不包含字符串值时返回错误。在成功的SET操作时,将丢弃与该键关联的任何先前的生存时间 。

设计模式

GETSET可以与INCR一起用于原子复位计数。例如:一个进程可能会在每次发生某些事件时针对键调用INCR ,但有时我们需要获取计数器的值并将其自动重置为零。mycounter这可以使用GETSET mycounter "0":

redis> INCR mycounter
(integer) 1
redis> GETSET mycounter "0"
"1"
redis> GET mycounter
"0"
redis> 

返回值

批量字符串回复: 存储在的旧值key,或者不存在的nil时候。key

例子

redis> SET mykey "Hello"
"OK"
redis> GETSET mykey "World"
"Hello"
redis> GET mykey
"World"
redis> 

INCR

从 1.0.0 开始可用。
时间复杂度: O(1)

将存储的数字key加一。如果该键不存在,则0在执行操作之前将其设置为。如果键包含错误类型的值或包含无法表示为整数的字符串,则会返回错误。此操作仅限于 64 位有符号整数。

注意:这是一个字符串操作,因为 Redis 没有专用的整数类型。存储在密钥中的字符串被解释为以 10 为底的64 位有符号整数以执行操作。

Redis 以整数表示形式存储整数,因此对于实际保存整数的字符串值,存储整数的字符串表示形式没有开销。

返回值

整数回复:key增量后的值

例子

redis> SET mykey "10"
"OK"
redis> INCR mykey
(integer) 11
redis> GET mykey
"11"
redis> 

图案:计数器

计数器模式是 Redis 原子增量操作可以做的最明显的事情。这个想法只是在每次操作发生时向 Redis发送一个INCR命令。例如,在 Web 应用程序中,我们可能想知道该用户一年中每天的页面浏览量。

为此,Web 应用程序可以在每次用户执行页面查看时简单地增加一个键,创建连接用户 ID 和表示当前日期的字符串的键名。

这个简单的模式可以通过多种方式进行扩展:

  • 可以在每次页面查看时一起使用INCR和EXPIRE,以使计数器仅计算最近的 N 次页面查看,间隔小于指定的秒数。
  • 客户端可以使用 GETSET 以原子方式获取当前计数器值并将其重置为零。
  • 使用其他原子递增/递减命令(如DECR或INCRBY),可以根据用户执行的操作处理可能变大或变小的值。例如,想象一下在线游戏中不同用户的得分。

模式:速率限制器

速率限制器模式是一种特殊的计数器,用于限制可以执行操作的速率。这种模式的经典实现涉及限制可以针对公共 API 执行的请求数量。

我们使用INCR提供了这种模式的两种实现,我们假设要解决的问题是将 API 调用的数量限制为 每个 IP 地址每秒最多 10 个请求。

模式:速率限制器 1

这种模式更简单直接的实现如下:

FUNCTION LIMIT_API_CALL(ip)
ts = CURRENT_UNIX_TIME()
keyname = ip+":"+ts
MULTI
    INCR(keyname)
    EXPIRE(keyname,10)
EXEC
current = RESPONSE_OF_INCR_WITHIN_MULTI
IF current > 10 THEN
    ERROR "too many requests per second"
ELSE
    PERFORM_API_CALL()
END

基本上,我们对每个 IP 都有一个计数器,每秒钟都有一个计数器。但是这个计数器总是递增设置 10 秒的过期时间,这样当当前秒不同时,Redis 会自动删除它们。

注意MULTI和EXEC的使用,以确保我们在每次 API 调用时都会增加并设置过期时间。

模式:速率限制器 2

另一种实现使用单个计数器,但在没有竞争条件的情况下使其正确有点复杂。我们将检查不同的变体。

FUNCTION LIMIT_API_CALL(ip):
current = GET(ip)
IF current != NULL AND current > 10 THEN
    ERROR "too many requests per second"
ELSE
    value = INCR(ip)
    IF value == 1 THEN
        EXPIRE(ip,1)
    END
    PERFORM_API_CALL()
END

计数器的创建方式是它只能存活一秒,从当前秒执行的第一个请求开始。如果同一秒内有超过 10 个请求,则计数器将达到大于 10 的值,否则它将过期并从 0 重新开始。

在上面的代码中有一个竞争条件。如果由于某种原因客户端执行了INCR命令但没有执行EXPIRE,则密钥将被泄露,直到我们再次看到相同的 IP 地址。

这可以很容易地修复,将带有可选EXPIRE的INCR转换为使用EVAL命令发送的 Lua 脚本(仅从 Redis 版本 2.6 开始可用)。

local current
current = redis.call("incr",KEYS[1])
if current == 1 then
    redis.call("expire",KEYS[1],1)
end

有一种不同的方法可以在不使用脚本的情况下解决此问题,而是使用 Redis 列表而不是计数器。实现更复杂并使用更高级的功能,但具有记住当前执行 API 调用的客户端的 IP 地址的优势,这可能有用或不取决于应用程序。

FUNCTION LIMIT_API_CALL(ip)
current = LLEN(ip)
IF current > 10 THEN
    ERROR "too many requests per second"
ELSE
    IF EXISTS(ip) == FALSE
        MULTI
            RPUSH(ip,ip)
            EXPIRE(ip,1)
        EXEC
    ELSE
        RPUSHX(ip,ip)
    END
    PERFORM_API_CALL()
END

如果键已经存在,RPUSHX命令只会推送元素。

请注意,我们在这里有竞争,但这不是问题:EXISTS可能返回 false,但密钥可能由另一个客户端创建,然后我们在MULTI / EXEC块中创建它。然而,这场比赛在极少数情况下会错过 API 调用,因此速率限制仍然可以正常工作。

INCRBY

从 1.0.0 开始可用。
时间复杂度: O(1)

key将存储在的数字递增increment。如果该键不存在,则0在执行操作之前将其设置为。如果键包含错误类型的值或包含无法表示为整数的字符串,则会返回错误。此操作仅限于 64 位有符号整数。

有关递增/递减操作的更多信息,请参见INCR。

返回值

整数回复:key增量后的值

例子

redis> SET mykey "10"
"OK"
redis> INCRBY mykey 5
(integer) 15
redis> 

INCRBYFLOAT

从 2.6.0 开始可用。
时间复杂度: O(1)

key递增表示存储在指定处的浮点数的字符串increment。通过使用负值increment,结果是存储在键中的值减少(通过加法的明显属性)。如果该键不存在,则0在执行操作之前将其设置为。如果出现以下情况之一,则会返回错误:

  • 键包含错误类型的值(不是字符串)。
  • 当前键内容或指定增量不可解析为双精度浮点数。

如果命令成功,新的递增值将作为键的新值存储(替换旧值),并以字符串的形式返回给调用者。

字符串键中已经包含的值和增量参数都可以选择以指数表示法提供,但是增量后计算的值始终以相同的格式存储,即,一个整数后跟(如果需要)一个点,以及表示数字的小数部分的可变位数。始终删除尾随零。

无论计算的实际内部精度如何,输出的精度都固定在小数点后 17 位。

返回值

批量字符串回复:key增量后的值。

例子

redis> SET mykey 10.50
"OK"
redis> INCRBYFLOAT mykey 0.1
"10.6"
redis> INCRBYFLOAT mykey -5
"5.6"
redis> SET mykey 5.0e3
"OK"
redis> INCRBYFLOAT mykey 2.0e2
"5200"
redis> 

实施细节

该命令始终在复制链接和 Append Only File 作为SET操作中传播,因此底层浮点数学实现中的差异不会成为不一致的根源。

LCS

自 7.0.0 起可用。
时间复杂度: O(N*M) 其中 N 和 M 分别是 s1 和 s2 的长度

LCS 命令实现最长公共子序列算法。请注意,这与最长的常见字符串算法不同,因为字符串中的匹配字符不需要是连续的。

例如“foo”和“fao”之间的LCS是“fo”,因为从左到右扫描两个字符串,最长的公共字符集是由第一个“f”然后是“o”组成的。

LCS 对于评估两个字符串的相似程度非常有用。字符串可以代表很多东西。例如,如果两个字符串是 DNA 序列,LCS 将提供两个 DNA 序列之间的相似性度量。如果字符串表示某个用户编辑的某些文本,LCS 可以表示新文本与旧文本相比有何不同,等等。

请注意,此算法在O(N*M)时间上运行,其中 N 是第一个字符串的长度,M 是第二个字符串的长度。因此,要么旋转一个不同的 Redis 实例以运行此算法,要么确保针对非常小的字符串运行它。

> MSET key1 ohmytext key2 mynewtext
OK
> LCS key1 key2
"mytext"

有时我们只需要匹配的长度:

> LCS key1 key2 LEN
6

然而,通常非常有用的是知道每个字符串中的匹配位置:

> LCS key1 key2 IDX
1) "matches"
2) 1) 1) 1) (integer) 4
         2) (integer) 7
      2) 1) (integer) 5
         2) (integer) 8
   2) 1) 1) (integer) 2
         2) (integer) 3
      2) 1) (integer) 0
         2) (integer) 1
3) "len"
4) (integer) 6

匹配是从最后一个到第一个产生的,因为这就是算法的工作方式,并且以相同的顺序发出事物更有效。上面的数组意味着第一个匹配项(数组的第二个元素)在第一个字符串的位置 2-3 和第二个字符串的 0-1 之间。然后是 4-7 和 5-8 之间的另一场比赛。

要将匹配列表限制为给定最小长度的匹配列表:

> LCS key1 key2 IDX MINMATCHLEN 4
1) "matches"
2) 1) 1) 1) (integer) 4
         2) (integer) 7
      2) 1) (integer) 5
         2) (integer) 8
3) "len"
4) (integer) 6

最后还有匹配 len:

> LCS key1 key2 IDX MINMATCHLEN 4 WITHMATCHLEN
1) "matches"
2) 1) 1) 1) (integer) 4
         2) (integer) 7
      2) 1) (integer) 5
         2) (integer) 8
      3) (integer) 4
3) "len"
4) (integer) 6

返回值

  • 如果没有修饰符,则返回表示最长公共子字符串的字符串。
  • 当LEN给出命令返回最长公共子字符串的长度。
  • 当IDX给出该命令时,该命令返回一个数组,其中包含 LCS 长度和两个字符串中的所有范围,每个字符串的开始和结束偏移量,其中有匹配项。当WITHMATCHLEN给出每个表示匹配的数组时,也将具有匹配的长度(参见示例)。

MGET

从 1.0.0 开始可用。
时间复杂度: O(N),其中 N 是要检索的键的数量。

返回所有指定键的值。对于每个不包含字符串值或不存在的键,nil返回特殊值。因此,操作永远不会失败。

返回值

数组回复:指定键的值列表。

例子

redis> SET key1 "Hello"
"OK"
redis> SET key2 "World"
"OK"
redis> MGET key1 key2 nonexisting
1) "Hello"
2) "World"
3) (nil)
redis> 

MSET

从 1.0.1 开始可用。
时间复杂度: O(N),其中 N 是要设置的键的数量。

将给定的键设置为它们各自的值。 MSET用新值替换现有值,就像常规SET一样。如果您不想覆盖现有值,请参阅MSETNX 。

MSET是原子的,所以所有给定的键都是一次设置的。客户端不可能看到某些密钥已更新而其他密钥未更改。

返回值

简单的字符串回复:总是OK因为MSET不能失败。

例子

redis> MSET key1 "Hello" key2 "World"
"OK"
redis> GET key1
"Hello"
redis> GET key2
"World"
redis> 

MSETNX

从 1.0.1 开始可用。
时间复杂度: O(N),其中 N 是要设置的键的数量。

将给定的键设置为它们各自的值。 即使只存在一个键,MSETNX也不会执行任何操作。

由于这种语义,可以使用MSETNX来设置表示唯一逻辑对象的不同字段的不同键,以确保设置所有字段或根本没有设置。

MSETNX是原子的,所以所有给定的键都是一次设置的。客户端不可能看到某些密钥已更新而其他密钥未更改。

返回值

整数回复,具体来说:

  • 1如果设置了所有键。
  • 0如果没有设置键(至少一个键已经存在)。

例子

redis> MSETNX key1 "Hello" key2 "there"
(integer) 1
redis> MSETNX key2 "new" key3 "world"
(integer) 0
redis> MGET key1 key2 key3
1) "Hello"
2) "there"
3) (nil)
redis> 

PSETEX

从 2.6.0 开始可用。
时间复杂度: O(1)

PSETEX的工作方式与SETEX完全相同,唯一的区别是过期时间以毫秒而不是秒为单位指定。

例子

redis> PSETEX mykey 1000 "Hello"
"OK"
redis> PTTL mykey
(integer) 1000
redis> GET mykey
"Hello"
redis> 

SET

从 1.0.0 开始可用。
时间复杂度: O(1)

设置key为保存字符串value。如果key已经保存了一个值,则无论其类型如何,都会将其覆盖。在成功的SET操作时,将丢弃与该键关联的任何先前的生存时间。

选项

SET命令支持一组修改其行为的选项:

  • EX seconds -- 设置指定的过期时间,以秒为单位。
  • PX 毫秒——设置指定的过期时间,以毫秒为单位。
  • EXAT timestamp-seconds -- 设置密钥过期的指定 Unix 时间,以秒为单位。
  • PXAT timestamp-milliseconds -- 设置密钥过期的指定 Unix 时间,以毫秒为单位。
  • NX-- 仅当密钥不存在时才设置它。
  • XX-- 仅当密钥已存在时才设置它。
  • KEEPTTL-- 保留与密钥关联的生存时间。
  • GET-- 返回存储在 key 中的旧字符串,如果 key 不存在,则返回 nil。如果存储在 key 的值不是字符串,则返回错误并中止SET 。
    注意:由于SET命令选项可以替换SETNX、SETEX、PSETEX、GETSET,因此在未来的 Redis 版本中,这些命令可能会被弃用并最终被删除。

返回值

简单字符串回复:OK如果SET执行正确。

Null 回复:(nil)如果由于用户指定了or选项但未满足条件而未执行SET操作。NXXX

如果命令与GET选项一起发出,则上述内容不适用。它将改为如下回复,无论是否实际执行了SET :

批量字符串回复:存储在键中的旧字符串值。

空回复:(nil)如果密钥不存在。

例子

redis> SET mykey "Hello"
"OK"
redis> GET mykey
"Hello"
redis> SET anotherkey "will expire in a minute" EX 60
"OK"
redis> 

模式

注意:不鼓励使用以下模式,而支持Redlock 算法,该算法实现起来稍微复杂一些,但提供更好的保证并且具有容错性。

该命令SET resource-name anystring NX EX max-lock-time是使用 Redis 实现锁定系统的简单方法。

如果上述命令返回,客户端可以获取锁OK(或者如果命令返回 Nil,则在一段时间后重试),并仅使用DEL删除锁。

达到过期时间后,锁会自动释放。

修改解锁模式可以使这个系统更加健壮,如下所示:

  • 不是设置固定字符串,而是设置一个不可猜测的大随机字符串,称为令牌。
  • 不要使用DEL释放锁,而是发送一个仅在值匹配时删除密钥的脚本。

这样可以避免客户端在过期时间后尝试释放锁,删除稍后获得锁的另一个客户端创建的密钥。

解锁脚本的示例类似于以下内容:

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end

该脚本应调用EVAL ...script... 1 resource-name token-value

历史

  • Redis 版本 >= 2.6.12:添加了 EXPXNXXX 选项。
  • Redis 版本 >= 6.0.0:添加了 KEEPTTL 选项。
  • Redis 版本 >= 6.2.0:添加了 GETEXATPXAT 选项。
  • Redis 版本 >= 7.0.0:允许一起使用 NXGET 选项。

SETEX

从 2.0.0 开始可用。
时间复杂度: O(1)

设置key为保存字符串value并key在给定的秒数后设置为超时。该命令相当于执行以下命令:

SET mykey value
EXPIRE mykey seconds

SETEX是原子的,可以通过在MULTI / EXEC块中使用前两个命令来重现。它是作为给定操作序列的更快替代方案提供的,因为当 Redis 用作缓存时,此操作非常常见。

无效时返回seconds错误。

返回值

简单的字符串回复

例子

redis> SETEX mykey 10 "Hello"
"OK"
redis> TTL mykey
(integer) 10
redis> GET mykey
"Hello"
redis> 

SETNX

从 1.0.0 开始可用。
时间复杂度: O(1)

如果不存在,则设置key为保存字符串。在这种情况下,它等于SET。当已经持有一个值时,不执行任何操作。 SETNX是“ SET if Not e Xists ”的缩写。valuekeykey

返回值

整数回复,具体来说:

  • 1如果设置了密钥
  • 0如果未设置密钥

例子

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis> 

设计模式:锁定与SETNX

请注意:

  1. 不鼓励使用以下模式以支持Redlock 算法,该算法实现起来稍微复杂一些,但提供了更好的保证并且具有容错性。
  2. 无论如何,我们都会记录旧模式,因为某些现有实现链接到此页面作为参考。此外,这是一个有趣的例子,说明了如何使用 Redis 命令来挂载编程原语。
  3. 无论如何,即使假设一个单实例锁定原语,从 2.6.12 开始,也可以创建一个更简单的锁定原语,与此处讨论的相同,使用SET命令获取锁,并使用简单的 Lua 脚本来释放锁。该模式记录在SET命令页面中。

也就是说,SETNX可以用作锁定原语,并且在历史上也被使用过。例如,要获取 key 的锁foo,客户端可以尝试以下操作:

SETNX lock.foo <current Unix time + lock timeout + 1>

如果SETNX返回1获取锁的客户端,则将lock.foo密钥设置为不再认为锁有效的 Unix 时间。客户端稍后将使用DEL lock.foo以释放锁。

如果SETNX返回0,则密钥已被其他客户端锁定。如果它是一个非阻塞锁,我们可以返回给调用者,或者进入一个循环重试持有锁,直到我们成功或某种超时到期。

处理死锁

在上述锁定算法中存在一个问题:如果客户端失败、崩溃或无法释放锁定会发生什么?可以检测到这种情况,因为锁定键包含 UNIX 时间戳。如果这样的时间戳等于当前的 Unix 时间,则锁不再有效。

发生这种情况时,我们不能只针对密钥调用DEL以移除锁,然后尝试发出SETNX,因为这里存在竞争条件,当多个客户端检测到过期锁并试图释放它时。

  • C1 和 C2 读取lock.foo以检查时间戳,因为它们都是 0在执行SETNX后收到的,因为锁仍然由 C3 持有,在持有锁后崩溃。
  • C1 发送DEL lock.foo
  • C1发送SETNX lock.foo并成功
  • C2 发送DEL lock.foo
  • C2发送SETNX lock.foo并成功
  • 错误:C1 和 C2 都因为竞争条件而获得了锁。

幸运的是,使用以下算法可以避免这个问题。让我们看看我们理智的客户端 C4 是如何使用好的算法的:

  • C4 发送SETNX lock.foo以获取锁
  • 崩溃的客户端 C3 仍然持有它,因此 Redis 将回复0C4。
  • C4 发送GET lock.foo检查锁是否过期。如果不是,它将休眠一段时间并从头开始重试。
  • 相反,如果锁因 Unix 时间lock.foo早于当前 Unix 时间而过期,C4 会尝试执行:
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
  • 由于GETSET语义,C4 可以检查存储的旧值 key是否仍然是过期的时间戳。如果是,则获得了锁。
  • 如果另一个客户端,例如 C5,比 C4 快,并且通过 GETSET 操作获得了锁,那么 C4 GETSET操作将返回一个未过期的时间戳。C4 将简单地从第一步重新开始。请注意,即使 C4 在未来几秒钟内设置密钥,这也不是问题。

为了使这种锁定算法更加健壮,持有锁的客户端在使用DEL解锁密钥之前应始终检查超时没有过期,因为客户端故障可能很复杂,不仅崩溃而且还会阻塞大量时间来执行某些操作并在很长一段时间后尝试发出DEL(当 LOCK 已被另一个客户端持有时)。

SETRANGE

从 2.2.0 开始可用。
时间复杂度: O(1),不计算复制新字符串所需的时间。通常,该字符串非常小,因此摊销复杂度为 O(1)。否则,复杂度为 O(M),其中 M 是 value 参数的长度。

覆盖存储在key的部分字符串,从指定的偏移量开始,覆盖value的整个长度。如果偏移量大于key处字符串的当前长度,则用零字节填充字符串以使偏移量适合。不存在的键被视为空字符串,因此此命令将确保它包含一个足够大的字符串,以便能够在offset处设置值。

请注意,您可以设置的最大偏移量为 2 29 -1 (536870911),因为 Redis 字符串限制为 512 兆字节。如果您需要超过这个大小,您可以使用多个键。

警告:当设置最后一个可能的字节并且存储在 key的字符串值还没有保存字符串值,或者保存一个小的字符串值时,Redis 需要分配所有可能阻塞服务器一段时间的中间内存。在 2010 MacBook Pro 上,设置字节数 536870911(分配 512MB)大约需要 300 毫秒,设置字节数 134217728(分配 128MB)大约需要 80 毫秒,设置位数 33554432(分配 32MB)大约需要 30 毫秒,设置位数 8388608(分配 8MB)大约需要 8 毫秒。请注意,一旦完成第一次分配,随后对同一键的SETRANGE调用不会有分配开销。

模式

感谢SETRANGE和类似的GETRANGE命令,您可以将 Redis 字符串用作具有O(1)随机访问的线性数组。在许多现实世界的用例中,这是一种非常快速和高效的存储。

返回值

整数回复:被命令修改后的字符串长度。

例子

基本用法:

redis> SET key1 "Hello World"
"OK"
redis> SETRANGE key1 6 "Redis"
(integer) 11
redis> GET key1
"Hello Redis"
redis> 

零填充示例:

redis> SETRANGE key2 6 "Redis"
(integer) 11
redis> GET key2
"\u0000\u0000\u0000\u0000\u0000\u0000Redis"
redis> 

STRLEN

从 2.2.0 开始可用。
时间复杂度: O(1)

返回存储在的字符串值的长度key。key持有非字符串值时返回错误。

返回值

整数回复:字符串的长度 atkey或0whenkey不存在。

例子

redis> SET mykey "Hello world"
"OK"
redis> STRLEN mykey
(integer) 11
redis> STRLEN nonexisting
(integer) 0
redis> 

SUBSTR

从 1.0.0 开始可用。
时间复杂度: O(N),其中 N 是返回字符串的长度。复杂度最终取决于返回的长度,但由于从现有字符串创建子字符串非常便宜,因此对于小字符串可以考虑 O(1)。

弃用通知: 从 Redis 版本 2.0.0 开始,此命令被视为已弃用。虽然它不太可能被完全删除,但更喜欢使用 GETRANGE 来代替它。

返回存储在 的字符串值的子字符串key,由偏移量start和end(都包括在内)确定。可以使用负偏移量来提供从字符串末尾开始的偏移量。所以 -1 表示最后一个字符,-2 表示倒数第二个字符,依此类推。

该函数通过将结果范围限制为字符串的实际长度来处理超出范围的请求。

返回值

批量字符串回复

例子

redis> SET mykey "This is a string"
"OK"
redis> GETRANGE mykey 0 3
"This"
redis> GETRANGE mykey -3 -1
"ing"
redis> GETRANGE mykey 0 -1
"This is a string"
redis> GETRANGE mykey 10 100
"string"
redis>