0
点赞
收藏
分享

微信扫一扫

Python自动化测试+邮件推送+企业微信推送+Jenkins

JamFF 2024-11-06 阅读 20

体样式**redis中,不同的问题有不一样的解决办法,那么锁也有不同的锁来解决不一样的问题,下面将举出几个常用的redis锁。

1. SETNX锁(简单独占锁)

  • 原理
    • SETNX(SET if Not eXists)是Redis实现简单锁的命令。它的操作是原子性的,当尝试设置一个键值对时,如果键不存在,则设置成功并返回1,表示获取锁成功;如果键已经存在,则返回0,表示锁已被其他客户端占用。
  • 示例
    • 假设使用lock_key作为锁的键,locked_value作为锁的值,在Java中可以这样实现:
      import redis.clients.jedis.Jedis;
      
      public class SimpleRedisLock {
          public static void main(String[] args) {
              Jedis jedis = new Jedis("localhost", 6379);
              String lock_key = "my_lock";
              String locked_value = "locked";
              long result = jedis.setnx(lock_key, locked_value);
              if (result == 1) {
                  System.out.println("获取锁成功");
                  // 执行业务逻辑
                  try {
                      // 模拟业务逻辑执行时间
                      Thread.sleep(5000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  // 释放锁,这里简单地删除键
                  jedis.del(lock_key);
              } else {
                  System.out.println("获取锁失败");
              }
              jedis.close();
          }
      }
      
  • 局限性
    • 没有自动释放机制。如果获取锁的客户端崩溃或者出现异常没有执行释放锁的操作,这个锁将一直被占用,导致死锁。

2. 带有过期时间的SET锁

  • 原理
    • Redis的SET命令可以在设置键值对的同时设置过期时间,并且这个操作是原子性的。例如,SET key value EX seconds NX这样的命令格式,其中EX seconds表示设置过期时间为seconds秒,NX表示只有当键不存在时才设置。
  • 示例
    • 在Python中,可以这样实现:
      import redis
      import time
      
      r = redis.Redis(host='localhost', port=6379)
      lock_key = "my_lock"
      locked_value = "locked"
      expire_time = 10
      result = r.set(lock_key, locked_value, ex=expire_time, nx=True)
      if result:
          print("获取锁成功")
          try:
              # 执行业务逻辑
              time.sleep(7)
          finally:
              # 不需要手动删除锁,过期后自动释放
              pass
      else:
          print("获取锁失败")
      
  • 注意事项
    • 业务逻辑执行时间如果超过了锁的过期时间,可能会出现数据不一致的情况。例如,在锁过期后,其他客户端获取了锁,然后原来的客户端完成业务逻辑并释放锁,这可能会导致新获取锁的客户端的操作受到干扰。

3. RedLock(分布式锁算法)

  • 原理
    • RedLock算法用于在多个Redis节点组成的分布式环境中获取高可靠的锁。它要求在多个(通常是奇数个)独立的Redis节点上尝试获取锁,只有当大多数节点(例如,总共5个节点,至少3个节点)成功获取锁时,才认为锁获取成功。
  • 示例流程(简化)
    • 假设有3个Redis节点node1node2node3,在Python中实现RedLock的基本步骤如下:
      import redis
      import time
      
      # 定义Redis节点列表
      redis_nodes = [
          redis.Redis(host='node1', port=6379),
          redis.Redis(host='node2', port=6379),
          redis.Redis(host='node3', port=6379)
      ]
      
      lock_key = "my_redlock"
      locked_value = "unique_client_id"
      expire_time = 10
      quorum = len(redis_nodes) // 2 + 1
      acquired_locks = 0
      
      # 尝试在多个节点上获取锁
      for node in redis_nodes:
          try:
              result = node.set(lock_key, locked_value, ex=expire_time, nx=True)
              if result:
                  acquired_locks += 1
          except:
              pass
      if acquired_locks >= quorum:
          print("RedLock获取成功")
          try:
              # 执行业务逻辑
              time.sleep(7)
          finally:
              # 释放锁,需要向所有节点发送释放请求
              for node in redis_nodes:
                  try:
                      node.delete(lock_key)
                  except:
                      pass
      else:
          print("RedLock获取失败")
      
  • 优势与应用场景
    • 提供了更高的可靠性,能够抵抗单个Redis节点故障、网络分区等问题,适用于对数据一致性要求极高的分布式系统,如金融交易系统、分布式任务调度系统等。

4. 基于Lua脚本实现的复杂锁(如可重入锁)

  • 原理
    • Lua脚本在Redis中可以原子地执行多个命令,通过编写Lua脚本可以实现复杂的锁逻辑。例如,实现可重入锁时,可以在脚本中检查锁的持有者是否为当前客户端,如果是,则增加重入次数;如果不是,则判断是否可以获取锁。
  • 示例(简单的可重入锁检查脚本)
    • 以下是一个简单的Redis Lua脚本用于检查可重入锁(假设锁的信息存储在一个哈希表中,键为lock_key,字段owner存储持有者,count存储重入次数):
      -- 检查锁是否存在且持有者是当前客户端
      local lock_key = KEYS[1]
      local client_id = ARGV[1]
      local lock_info = redis.call('hgetall', lock_key)
      if #lock_info == 0 then
          -- 锁不存在,设置持有者为当前客户端,重入次数为1
          redis.call('hmset', lock_key, 'owner', client_id, 'count', 1)
          return 1
      elseif lock_info[2] == client_id then
          -- 持有者是当前客户端,增加重入次数
          redis.call('hincrby', lock_key, 'count', 1)
          return 1
      else
          return 0
      end
      
    • 在Java中调用这个Lua脚本可以这样实现:
      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.JedisPool;
      import redis.clients.jedis.JedisPoolConfig;
      import redis.clients.jedis.Response;
      import redis.clients.jedis.ScriptingCommands;
      
      public class ReentrantRedisLock {
          private static final String LUA_SCRIPT = "local lock_key = KEYS[1]\n" +
                  "local client_id = ARGV[1]\n" +
                  "local lock_info = redis.call('hgetall', lock_key)\n" +
                  "if #lock_info == 0 then\n" +
                  "    -- 锁不存在,设置持有者为当前客户端,重入次数为1\n" +
                  "    redis.call('hmset', lock_key, 'owner', client_id, 'count', 1)\n" +
                  "    return 1\n" +
                  "elseif lock_info[2] == client_id then\n" +
                  "    -- 持有者是当前客户端,增加重入次数\n" +
                  "    redis.call('hincrby', lock_key, 'count', 1)\n" +
                  "    return 1\n" +
                  "else\n" +
                  "    return 0\n" +
                  "end";
          private JedisPool jedisPool;
      
          public ReentrantRedisLock() {
              JedisPoolConfig poolConfig = new JedisPoolConfig();
              jedisPool = new JedisPool(poolConfig, "localhost", 6379);
          }
      
          public boolean tryLock(String lock_key, String client_id) {
              try (Jedis jedis = jedisPool.getResource()) {
                  ScriptingCommands scriptingCommands = jedis.scriptingCommands();
                  Response<Long> response = scriptingCommands.evalsha(scriptingCommands.scriptLoad(LUA_SCRIPT), 1, lock_key, client_id);
                  return response.get() == 1;
              }
          }
      
          public void unlock(String lock_key, String client_id) {
              try (Jedis jedis = jedisPool.getResource()) {
                  // 这里还需要编写释放锁的Lua脚本,此处省略
                  // 基本思路是检查重入次数,减1后如果为0则删除锁的记录
              }
          }
      }
      
  • 优势与应用场景
    • 可以实现更灵活、复杂的锁机制,如可重入锁、公平锁等,满足特定业务场景下对锁的高级需求,如在递归调用函数中需要获取同一把锁,或者需要按照请求顺序公平地获取锁的场景。
举报

相关推荐

0 条评论