企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] ## 实现一: SETNX实现的分布式锁 setnx用法参考redis[官方文档](https://link.zhihu.com/?target=https%3A//redis.io/commands/setnx) ### 语法 ``` SETNX key value ``` >将`key`设置值为`value`,如果`key`不存在,这种情况下等同SET命令。 当`key`存在时,什么也不做。`SETNX`是”**SET**if**N**ot e**X**ists”的简写。 ### 返回值 设置成功,返回 1 。 设置失败,返回 0 。 ### 加锁步骤 1. `SETNX lock.foo <current Unix time + lock timeout + 1>` 如果客户端获得锁,`SETNX`返回`1`,加锁成功。 如果`SETNX`返回`0`,那么该键已经被其他的客户端锁定。 2. 接上一步,`SETNX`返回`0`加锁失败,此时,调用`GET lock.foo`获取时间戳检查该锁是否已经过期 * 如果没有过期,则休眠一会重试。 * 如果已经过期,则可以获取该锁。具体的:调用`GETSET lock.foo <current Unix timestamp + lock timeout + 1>`基于当前时间设置新的过期时间。 **注意**: 这里设置的时候因为在`SETNX`与`GETSET`之间有个窗口期,在这期间锁可能已被其他客户端抢去,所以这里需要判断`GETSET`的返回值,他的返回值是SET之前旧的时间戳: * 若旧的时间戳已过期,则表示加锁成功。 * 若旧的时间戳还未过期(说明被其他客户端抢去并设置了时间戳),代表加锁失败,需要等待重试。 ### 解锁步骤 解锁相对简单,只需`GET lock.foo`时间戳,**判断是否过期**,过期就调用删除`DEL lock.foo` ## 实现二:SET实现的分布式锁 set用法参考[官方文档](https://link.zhihu.com/?target=https%3A//redis.io/commands/set) ### 语法 `SET key value [EX seconds|PX milliseconds] [NX|XX]` 将键`key`设定为指定的“字符串”值。如果`key`已经保存了一个值,那么这个操作会直接覆盖原来的值,并且忽略原始类型。当`set`命令执行成功之后,之前设置的过期时间都将失效。 从2.6.12版本开始,redis为`SET`命令增加了一系列选项: * `EX`*seconds*– Set the specified expire time, in seconds. * `PX`*milliseconds*– Set the specified expire time, in milliseconds. * `NX`– Only set the key if it does not already exist. * `XX`– Only set the key if it already exist. * `EX`*seconds*– 设置键key的过期时间,单位时秒 * `PX`*milliseconds*– 设置键key的过期时间,单位是毫秒 * `NX`– 只有键key不存在的时候才会设置key的值 * `XX`– 只有键key存在的时候才会设置key的值 版本>= 6.0 * `KEEPTTL`\-- 保持 key 之前的有效时间TTL ### 加锁步骤 一条命令即可加锁:`SET resource_name my_random_value NX PX 30000` The command will set the key only if it does not already exist (NX option), with an expire of 30000 milliseconds (PX option). The key is set to a value “my*random*value”. This value must be unique across all clients and all lock requests. 这个命令只有当`key`对应的键不存在resource\_name时(NX选项的作用)才生效,同时设置30000毫秒的超时,成功设置其值为my\_random\_value,这是个在所有redis客户端加锁请求中全局唯一的随机值。 ### 解锁步骤 解锁时需要确保my\_random\_value和加锁的时候一致。下面的Lua脚本可以完成 ``` if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end ``` 这段Lua脚本在执行的时候要把前面的`my_random_value`作为`ARGV[1]`的值传进去,把`resource_name`作为`KEYS[1]`的值传进去。释放锁其实包含三步操作:’GET’、判断和’DEL’,用Lua脚本来实现能保证这三步的原子性。