首页 > 其他分享 >分布式锁

分布式锁

时间:2024-10-08 14:11:09浏览次数:7  
标签:key KEYS redis name call EVAL 分布式

单体应用可以使用 synchronized 或 Lock 来加锁,synchronized 推荐使用类锁,也就是字节码锁,这样保证是全局唯一的,如果使用对象锁,要根据业务确定这个对象锁在这个业务中是唯一的。

对于微服务架构下,单体应用锁就不合适了,每个服务多个节点部署,虚拟机都不是用一个,肯定保证不了唯一性

LUA 脚本

redis 通过 EVAL 来执行 lua 脚本,格式为:EVAL <lua_script> <numkeys> <key1> <key2> ... <keyN> <value1> <value2> ... <valueM>

  • numkeys:键的个数

    1. 如果 numkeys 为 0,不用传参,比如:EVAL "return redis.call('set', 'name', 'jack')" 0
    2. 如果 numkeys 不为 0,需要传入对应数量的键,比如 :EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 name Marry
  • [key valye ...]:参数,键是必须的,值是可选的(值个数可以大于键个数,也可以小于键个数,也可以等于键个数)

    1. EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 name Marry abc 合法的,abc 不会被使用
    2. EVAL "return redis.call('set', KEYS[1], KEYS[2])" 2 name Marry 合法的,两个参数都视为键
    3. EVAL "redis.call('lpush', KEYS[1], ARGV[1]) redis.call('lpush', KEYS[1], ARGV[2])" 1 mylist value1 value2 合法的,1个键,两个值
  • lua_script:lua 脚本

    1. 取参数的 key:KEYS[下标],lua 第一项是 1(不是从 0 开始,取第一个 key 要写成 KEYS[1]

    2. 取参数的 value:ARGV[下标]

    3. 判断参数规则

      • 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
    4. 条件判断

      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

  1. SETNX key value 当 key 不存在时添加缓存,等同于 SET key value NX。最简易版的锁
  2. EXPIRE key seconds 单独给 key 指定过期时间。给锁加过期时间,避免死锁
  3. SET key value NX EX seconds 当 key 不存在时添加,并设置过期时间(具有原子性,如果要使用简易的锁用这个)
  4. SETEX 添加一个 key 并设置过期时间。这个不适合用作锁,因为当锁存在就不允许添加,这个命令会覆盖原来的值

SETNX 问题

  • 不支持重入
  • 不支持续期
  • 锁误删除(业务时间大于锁时间时可能发生)
  • 删除锁时【判断是否存在】和【如果存在要删除】这两个步骤不是原子的
  • ......

锁重入解决方案

结合程序,判断获取锁的线程是否是拥有锁的线程,并且数据类型要使用 hash(锁名、值、重入次数),也就是 setnx 换成 hset

锁续期解决方案

结合程序,参考 redisson 看门狗

锁误删解决方案

给 value 设置一个特定的值,释放的时候不是直接删除锁,而是先获取锁,根据值对比一下,如果值相等允许释放,如果不相等不能释放

判断和删除不是原子操作解决方案

使用 lua 脚本

标签:key,KEYS,redis,name,call,EVAL,分布式
From: https://www.cnblogs.com/cyrushuang/p/18451531

相关文章