单体应用可以使用 synchronized 或 Lock 来加锁,synchronized 推荐使用类锁,也就是字节码锁,这样保证是全局唯一的,如果使用对象锁,要根据业务确定这个对象锁在这个业务中是唯一的。
对于微服务架构下,单体应用锁就不合适了,每个服务多个节点部署,虚拟机都不是用一个,肯定保证不了唯一性
LUA 脚本
redis 通过 EVAL
来执行 lua 脚本,格式为:EVAL <lua_script> <numkeys> <key1> <key2> ... <keyN> <value1> <value2> ... <valueM>
-
numkeys:键的个数
- 如果
numkeys
为 0,不用传参,比如:EVAL "return redis.call('set', 'name', 'jack')" 0
- 如果
numkeys
不为 0,需要传入对应数量的键,比如 :EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 name Marry
- 如果
-
[key valye ...]:参数,键是必须的,值是可选的(值个数可以大于键个数,也可以小于键个数,也可以等于键个数)
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 name Marry abc
合法的,abc
不会被使用EVAL "return redis.call('set', KEYS[1], KEYS[2])" 2 name Marry
合法的,两个参数都视为键EVAL "redis.call('lpush', KEYS[1], ARGV[1]) redis.call('lpush', KEYS[1], ARGV[2])" 1 mylist value1 value2
合法的,1个键,两个值
-
lua_script:lua 脚本
-
取参数的 key:
KEYS[下标]
,lua 第一项是 1(不是从 0 开始,取第一个 key 要写成KEYS[1]
) -
取参数的 value:
ARGV[下标]
-
判断参数规则
EVAL "lua_script" 1 xxx xxx
,1 个键,第一个是键,第二个是值 ==>key value
EVAL "lua_script" 1 xxx xxx xxx
,1 个键,第一个是键,后面两个都是值 ==>key value value
EVAL "lua_script" 2 xxx xxx
,2 个键,参数是 2 个键,没有值 ==>key key
EVAL "lua_script" 2 xxx xxx xxx
,2 个键,前面两个是键,第三个是值 ==>key key valye
EVAL "lua_script" 2 xxx xxx xxx xxx xxx
,2 个键,前面两个是键,后面三个是值 ==>key key value value value
-
条件判断
EVAL " local score = tonumber(redis.call('get', KEYS[1])) -- tonumber 是 lua 内置函数,把一个值转成数字 if score >= 90 then -- 每一个 if 后面要跟一个 then(不是花括号) redis.call('set', KEYS[2], '优秀') -- 设置 90+ 的等级 elseif score >= 80 then -- 每一个 if 后面要跟一个 then redis.call('set', KEYS[2], '良好') -- 设置 80+ 的等级 elseif score >= 70 then -- 每一个 if 后面要跟一个 then redis.call('set', KEYS[2], '中等') -- 设置 70+ 的等级 else -- 结尾的 else 不跟 then redis.call('set', KEYS[2], '需要改进') -- 设置小于 70 的等级 end -- if 代码块用 end 结尾 return redis.call('get', KEYS[2]) -- 返回设置的等级 " 2 user:score user:status -- 参数:2个参数,只有 key 没有 value
-
-
示例
# 执行 set name "jack"(0 表示没有参数传进脚本) EVAL "return redis.call('set', 'name', 'jack')" 0 # 执行 mset name "Rose" age 22(设置多个值,也没有参数) EVAL "return redis.call('mset', 'name', 'Rose', 'age', 22)" 0 # 带参数的示例,执行命令: mset name "Marry" age 23。2 表示 2 个键,因为脚本有 ARGV 所以参数被视为键值对 EVAL "return redis.call('mset', KEYS[1], ARGV[1], KEYS[2], ARGV[2])" 2 name age Marry 23 # 判断 key 再删除(官网示例) # if redis.call('get', 'name') == 'Rose' then ---- 如果 name 的值是 Rose # return redis.call('del', name) ---- 删除 name,删除成功会返回 1 # else ---- 如果条件不成立 # return 0 ---- 就不删除,返回0 # end ---- 结束 EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 name Rose
SETNX
SETNX key value
当 key 不存在时添加缓存,等同于SET key value NX
。最简易版的锁EXPIRE key seconds
单独给 key 指定过期时间。给锁加过期时间,避免死锁SET key value NX EX seconds
当 key 不存在时添加,并设置过期时间(具有原子性,如果要使用简易的锁用这个)SETEX
添加一个 key 并设置过期时间。这个不适合用作锁,因为当锁存在就不允许添加,这个命令会覆盖原来的值
SETNX 问题
- 不支持重入
- 不支持续期
- 锁误删除(业务时间大于锁时间时可能发生)
- 删除锁时【判断是否存在】和【如果存在要删除】这两个步骤不是原子的
- ......
锁重入解决方案
结合程序,判断获取锁的线程是否是拥有锁的线程,并且数据类型要使用 hash(锁名、值、重入次数),也就是 setnx
换成 hset
锁续期解决方案
结合程序,参考 redisson 看门狗
锁误删解决方案
给 value 设置一个特定的值,释放的时候不是直接删除锁,而是先获取锁,根据值对比一下,如果值相等允许释放,如果不相等不能释放
判断和删除不是原子操作解决方案
使用 lua 脚本
标签:key,KEYS,redis,name,call,EVAL,分布式 From: https://www.cnblogs.com/cyrushuang/p/18451531