欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Redis Template实现分布式锁的实例代码

程序员文章站 2022-03-01 15:43:14
前言 分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于redis的分布式锁;3. 基于zookeeper的分布式锁。本篇博客将介绍第二种方式,基于redis实现...

前言

分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于redis的分布式锁;3. 基于zookeeper的分布式锁。本篇博客将介绍第二种方式,基于redis实现分布式锁。虽然网上已经有各种介绍redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现redis分布式锁。

可靠性

首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
1.互斥性。在任意时刻,只有一个客户端能持有锁。
2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3.具有容错性。只要大部分的redis节点正常运行,客户端就可以加锁和解锁。
4.解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

使用redis的setnx命令获取分布式锁的步骤:

•c1和c2线程同时检查时间戳获取锁,执行setnx命令并都返回0,此时锁仍被c3持有,并且c3已经崩溃
•c1 del锁
•c1 使用setnx命令获取锁,并且成功
•c2 del锁
•c2 使用setnx命令获取锁,并且成功
•error : 由于竞态条件,c1和c2都获取到了锁

幸运的是,以下面的步骤完全可以避免这种情况发生,看看c4线程如何操作

•c4使用setnx命令获取锁
•c3已经崩溃但是仍然持有锁,所以redis返回0给c4
•c4使用get命令获取锁并检查锁是否已经过期,如果没有过期,则继续等待一段时间并重新重试
•如果锁已经过期,c4尝试 getset lock.foo <current unix timestamp + lock timeout + 1>
•利用getset语法,c4可以检查旧时间是否仍然是过期时间,如果是,则获取锁
•如果另一个客户端c5率先获取到锁,c4执行getset命令后将返回非过期时间,然后c4继续从头开始重新尝试获取锁。此操作c4将延长一点c5获取到的锁的过期时间,不过这不是什么大问题。

接下来我们用代码的形式展现:

package com.shuige.components.cache.redis;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.data.redis.core.rediscallback;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.core.valueoperations;
import org.springframework.stereotype.component;
import java.util.objects;
import java.util.concurrent.timeunit;
/**
 * description: 通用redis帮助类
 * user: zhouzhou
 * date: 2018-09-05
 * time: 15:39
 */
@component
public class commonredishelper {
  public static final string lock_prefix = "redis_lock";
  public static final int lock_expire = 300; // ms
  @autowired
  redistemplate redistemplate;
  /**
   * 最终加强分布式锁
   *
   * @param key key值
   * @return 是否获取到
   */
  public boolean lock(string key){
    string lock = lock_prefix + key;
    // 利用lambda表达式
    return (boolean) redistemplate.execute((rediscallback) connection -> {
      long expireat = system.currenttimemillis() + lock_expire + 1;
      boolean acquire = connection.setnx(lock.getbytes(), string.valueof(expireat).getbytes());
      if (acquire) {
        return true;
      } else {
        byte[] value = connection.get(lock.getbytes());
        if (objects.nonnull(value) && value.length > 0) {
          long expiretime = long.parselong(new string(value));
          if (expiretime < system.currenttimemillis()) {
            // 如果锁已经过期
            byte[] oldvalue = connection.getset(lock.getbytes(), string.valueof(system.currenttimemillis() + lock_expire + 1).getbytes());
            // 防止死锁
            return long.parselong(new string(oldvalue)) < system.currenttimemillis();
          }
        }
      }
      return false;
    });
  }
  /**
   * 删除锁
   *
   * @param key
   */
  public void delete(string key) {
    redistemplate.delete(key);
  }
}

如何使用呢,导入工具类后:

boolean lock = redishelper.lock(key);
    if (lock) {
      // 执行逻辑操作
      redishelper.delete(key);
    } else {
      // 设置失败次数计数器, 当到达5次时, 返回失败
      int failcount = 1;
      while(failcount <= 5){
        // 等待100ms重试
        try {
          thread.sleep(100l);
        } catch (interruptedexception e) {
          e.printstacktrace();
        }
        if (redishelper.lock(key)){
          // 执行逻辑操作
          redishelper.delete(key);
        }else{
          failcount ++;
        }
      }
      throw new runtimeexception("现在创建的人太多了, 请稍等再试");
    }

加锁成功执行完逻辑后, 必须解锁, 否则只能靠锁机制来解锁了不建议这么做

总结

以上所述是小编给大家介绍的redis template实现分布式锁的实例代码,希望对大家有所帮助