`

谈谈Redis的SETNX

 
阅读更多

在 Redis 里,所谓 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果,不过很多人没有意识到 SETNX 有陷阱!

 

比如说:某个查询数据库的接口,因为调用量比较大,所以加了缓存,并设定缓存过期后刷新,问题是当并发量比较大的时候,如果没有锁机制,那么缓存过期的瞬间,大量并发请求会穿透缓存直接查询数据库,造成雪崩效应,如果有锁机制,那么就可以控制只有一个请求去更新缓存,其它的请求视情况要么等待,要么使用过期的缓存。

下面以目前 PHP 社区里最流行的 PHPRedis 扩展为例,实现一段演示代码:

<?php

$ok = $redis->setNX($key, $value);

if ($ok) {
    $cache->update();
    $redis->del($key);
}

?>

缓存过期时,通过 SetNX  获取锁,如果成功了,那么更新缓存,然后删除锁。看上去逻辑非常简单,可惜有问题:如果请求执行因为某些原因意外退出了,导致创建了锁但是没有删除锁,那么这个锁将一直存在,以至于以后缓存再也得不到更新。于是乎我们需要给锁加一个过期时间以防不测:

<?php

$redis->multi();
$redis->setNX($key, $value);
$redis->expire($key, $ttl);
$redis->exec();

?>

因为 SetNX 不具备设置过期时间的功能,所以我们需要借助 Expire 来设置,同时我们需要把两者用 Multi/Exec 包裹起来以确保请求的原子性,以免 SetNX 成功了 Expire 却失败了。 可惜还有问题:当多个请求到达时,虽然只有一个请求的 SetNX 可以成功,但是任何一个请求的 Expire 却都可以成功,如此就意味着即便获取不到锁,也可以刷新过期时间,如果请求比较密集的话,那么过期时间会一直被刷新,导致锁一直有效。于是乎我们需要在保证原子性的同时,有条件的执行 Expire,接着便有了如下 Lua 代码:

local key   = KEYS[1]
local value = KEYS[2]
local ttl   = KEYS[3]

local ok = redis.call('setnx', key, value)
 
if ok == 1 then
  redis.call('expire', key, ttl)
end
 
return ok

没想到实现一个看起来很简单的功能还要用到 Lua 脚本,着实有些麻烦。其实 Redis 已经考虑到了大家的疾苦,从 2.6.12 起,SET 涵盖了 SETEX 的功能,并且 SET 本身已经包含了设置过期时间的功能,也就是说,我们前面需要的功能只用 SET 就可以实现。

<?php

$ok = $redis->set($key, $value, array('nx', 'ex' => $ttl));

if ($ok) {
    $cache->update();
    $redis->del($key);
}

?>

如上代码是完美的吗?答案是还差一点!设想一下,如果一个请求更新缓存的时间比较长,甚至比锁的有效期还要长,导致在缓存更新过程中,锁就失效了,此时另一个请求会获取锁,但前一个请求在缓存更新完毕的时候,如果不加以判断直接删除锁,就会出现误删除其它请求创建的锁的情况,所以我们在创建锁的时候需要引入一个随机值:

<?php

$ok = $redis->set($key, $random, array('nx', 'ex' => $ttl));

if ($ok) {
    $cache->update();

    if ($redis->get($key) == $random) {
        $redis->del($key);
    }
}

?>

补充:本文在删除锁的时候,实际上是有问题的,没有考虑到 GC pause 之类的问题造成的影响,比如 A 请求在 DEL 之前卡住了,然后锁过期了,这时候 B 请求又成功获取到了锁,此时 A 请求缓过来了,就会 DEL 掉 B 请求创建的锁,此问题远比想象的要复杂,具体解决方案参见本文最后关于锁的若干个参考链接。

如此基本实现了单机锁,假如要实现分布锁,请参考:Distributed locks with Redis,不过分布式锁需要注意的地方更多:How to do distributed lockingIs Redlock safe。此外,还有中文版:基于Redis的分布式锁到底安全吗()。

分享到:
评论

相关推荐

    php redis setnx分布式锁简单原理解析

    首先,`setnx`是Redis中的一个命令,全称为“Set if Not Exists”,即如果键(key)不存在,则设置键值对。它的返回值是布尔类型,如果设置成功(键不存在),则返回`true`;如果设置失败(键已存在),则返回`false`...

    详解使用Redis SETNX 命令实现分布式锁

    使用Redis的 SETNX 命令可以实现分布式锁,下文介绍其实现方法。 SETNX命令简介 命令格式 SETNX key value 将 key 的值设为 value,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作。 ...

    Redis的SETNX的使用方法1

    "Redis的SETNX的使用方法详解" Redis的SETNX命令是Redis中的一种原子操作命令,它可以实现对key的设置,只有当key不存在时才设置成功。SETNX是“SET if Not eXists”的缩写,也就是只有当key不存在时才设置,可以...

    redis-distributed-lock:Python3的redis分布式锁,使用setnx和lua脚本,提供块和无块函数

    Python3的redis分布式锁,使用setnx和lua脚本,提供块和无块函数 用法 import redis from .redis_lock import RedisLock redis_client = redis.Redis(host="127.0.0.1", port=6379, db=0) lock = RedisLock(redis_...

    使用redis分布式锁解决并发线程资源共享问题

    但是在分布式架构中,我们的服务可能会有n个实例,但线程锁只对同一个实例有效,就需要用到分布式锁—-redis setnx 原理 修改某个资源时, 在redis中设置一个key,value根据实际情况自行决定如何表示 我们既然要通过...

    redis分布式锁及会出现的问题解决

    Redis 分布式锁是分布式系统中用于解决并发问题的重要工具,尤其在高并发场景下,如秒杀活动、限流控制等。它基于单机的 Redis 数据存储来实现跨节点的锁服务。以下是对Redis分布式锁及其可能出现的问题的详细解释:...

    通过 redis SET NX EX 和 BLPOP 实现的锁上下文管理器 .zip

    概述文档 测试 包裹 通过 redis SETNX/BLPOP 实现的锁上下文管理器。自由软件BSD 2 条款许可证接口目标与threading.Lock完全一样。用法因为我们不想要求用户跨进程共享锁实例,所以您必须为他们命名。from redis ...

    Redis的Expire与Setex区别说明

    本文主要探讨了Redis中两个关键的命令:`Expire`和`Setex`,以及它们的区别,并简要介绍了相关的命令如`setnx`, `setrange`, `mset`和`msetnx`。 1. **Expire**: `Expire`命令用于为已存在的键设置过期时间。通过`...

    PHP+redis实现的限制抢购防止商品超发功能详解

    通过连接、认证、选择数据库,然后执行具体的Redis命令(如setnx和incrby)来操作Redis数据库中的数据。PHP代码通过实例化自定义类,调用相应的方法,实现对Redis的写入和读取操作。 结合文章提供的PHP代码实例,...

    Java基于redis实现分布式锁代码实例

    通过使用 Redis 的 setnx 命令和 expire 命令,可以实现分布式锁的功能。但是,需要注意的是,在实现分布式锁时,需要考虑到各种可能的异常情况,例如服务器挂掉等,并且需要对分布式锁进行优化,例如加过期时间,...

    php 使用redis锁限制并发访问类

    Redis是一种高效的键值存储系统,支持丰富的数据类型和操作,其中就包括用于创建锁的`SETNX`命令。 标题中的“php 使用redis锁限制并发访问类”指的是一个PHP类,它实现了基于Redis的锁机制来限制并行访问。这个类...

    redis-3.0.7.tar

    接下来,我们来谈谈如何安装和配置Redis 3.0.7。通常,你可以从Redis官方网站下载源码包`redis-3.0.7.tar`,然后解压并编译安装。基本步骤包括: 1. 解压文件:`tar -zxvf redis-3.0.7.tar` 2. 进入解压后的目录:`...

    php-redis 中文文档

    - **设置非重复键值**: `$redis-&gt;setnx($key, $value)`,仅当键不存在时设置键值。 - **示例**: ```php $redis-&gt;setnx('unique_key', 'unique_value'); ``` ##### 4. 删除键值 - **方法**: `$redis-&gt;delete($...

    redis 可视化工具以及免安装redis 绿色版

    接下来,我们谈谈免安装的Redis绿色版。免安装版通常是指不需要通过传统安装过程即可使用的软件版本,它们通常被打包为一个自包含的文件夹,包含了所有运行所需依赖。对于Redis而言,这意味着你可以下载一个包含所有...

    python-redis-lock:通过redis SET NX EX和BLPOP实现的锁上下文管理器

    概述docs 测试 包裹 通过redis SETNX / BLPOP实现的锁上下文管理器。 免费软件:BSD 2条款许可目标接口完全类似于 。用法因为我们不想要求用户跨进程共享锁实例,所以您必须给他们起名字。 from redis import Redis...

    redis 免安装 redis客户端 redis-desktop-manager-0.8.8.384

    Redis 是一个高性能的键值数据库,它以键值对的形式存储数据,广泛应用于缓存、消息中间件、实时分析等领域。在 Windows 环境下,通常需要通过安装过程来设置 Redis 服务,但这里提供的资源是“redis 免安装”,意味...

    redis在win上的运行脚本redis.bat

    Redis是一款高性能的键值对数据库,常用于缓存、消息队列等场景。在Windows操作系统上运行Redis,通常需要借助一些额外的工具。标题提到的"redis在win上的运行脚本redis.bat"就是一个帮助用户在Windows环境下启动...

    redis-windows-Redis7.0.0.zip

    Redis,全称Remote Dictionary Server,是一款开源的、高性能的键值存储系统,广泛应用于缓存、消息队列、数据持久化等多种场景。它以其高效、轻量级的特性,在IT行业中备受青睐,尤其是在互联网领域。在Windows环境...

    Windows版 Redis 5.0.14

    Redis 是一个开源的内存数据结构存储系统,常被用作数据库、缓存和消息代理。在Windows环境下,Redis 的安装和使用与在Linux系统中有所不同。这里我们将详细讨论Windows版Redis 5.0.14的相关知识点。 1. **Redis ...

Global site tag (gtag.js) - Google Analytics