分布式锁

单体应用可以使用 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 问题

  1. 锁误删除

    当业务时间大于锁过期时间时会发生。假设锁 tll 是 10 秒,但是业务需要 20s。10 秒后锁自动删除,业务结束时自己家的锁过期了,再删除的锁就是别的线程加的

    解决方案是给 value 设置特殊的值,当删除锁时,先获取锁的值,对比一样才删除,反之不删除(不一样说明自己加的锁过期自动删除了)

  2. 删除锁不是原子操作

    当删除锁时,是先判断锁,然后存在再删除(避免误删),这是两条命令,不具有原子性

    解决方案是使用 lua 脚本

  3. 不支持重入

    • 不能使用 String 数据类型,要使用 Hash 数据类型。key - field - value ==> 锁 - 值 - 重入次数
    • Hash 的命令要灵活使用,比如 HINCRBY 不存在会自动创建
    • 要结合程序编码,当同一个线程获取锁,使用 lua 脚本让 hash 的 filed 的值 +1
  4. 不支持续期

    • 结合程序编码,每添加锁后同时创建一个循环的定时任务
    • 每当 ttl 时间过去 1/3,触发一次定时任务的调用
    • 定时任务就是检查锁是否还存在,如果存在就把 ttl 恢复成初始时间
    • 如果定时任务发现锁不存在了,说明业务结束删除了锁,定时任务也就终止了

RedLock

名字叫红锁,这其实是一种算法

解决分布式缓存系统中锁丢失的方案(锁丢失就是锁不存在了,所以能重新加锁,也可以理解为锁失效)

产生原因:当加锁成功后,主节点还没来得及同步给从节点,主节点宕机,锁就丢失了

解决方案

  1. 锁和缓存的 redis 实例要区分开来,缓存的 redis 不保存锁
  2. 用于锁的 redis 实例都是单独的 master(没有主从,也不是集群)
  3. 加锁和释放锁要在所有的实例都操作,大部分实例操作成功,才算成功
posted @   CyrusHuang  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示