基于redis的一种分布式锁

前言:本文介绍了一种基于redis的分布式锁,利用jedis实现应用(本文应用于多客户端+一个redis的架构,并未考虑在redis为主从架构时的情况)

文章理论来源部分引自:https://i.cnblogs.com/EditPosts.aspx?opt=1

一、基本原理

1、用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

2、redis采用单进程单线程模式,采用队列模式将并发访问变成串行访问,多客户端对Redis的连接并不存在竞争关系。

二、基本命令

1、setNX(SET if Not eXists)

语法:

SETNX key value

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写

返回值:

  设置成功,返回 1 。
  设置失败,返回 0

2、getSet

GETSET key value

将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

  当 key 存在但不是字符串类型时,返回一个错误。

返回值:

  返回给定 key 的旧值。
  当 key 没有旧值时,也即是, key 不存在时,返回 nil 。

3、get

GET key

  当 key 不存在时,返回 nil ,否则,返回 key 的值。
  如果 key 不是字符串类型,那么返回一个错误

三、取锁、解锁以及示例代码:

    /**
     * @Description:分布式锁,通过控制redis中key的过期时间来控制锁资源的分配
     * 实现思路: 主要是使用了redis 的setnx命令,缓存了锁.
     * reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间)
     * 执行过程:
     * 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁
     * 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值
     * @param key
     * @param expireTime 有效时间段长度
     * @return
     */
    public boolean getLockKey(String key, final long expireTime) {
        // 1.setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2
        if (getJedis().setnx(key, new Date().getTime() + expireTime + "") == 1)
            return true;
        String oldExpireTime = getJedis().get(key);
        // 2.get(lockkey)获取值oldExpireTime
        // ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向3
        if (null != oldExpireTime && "" !=oldExpireTime  && Long.parseLong(oldExpireTime) < new Date().getTime()) {
            // 3计算newExpireTime=当前时间+过期超时时间,然后getset(lockkey, newExpireTime)
            // 会返回当前lockkey的值currentExpireTime。
            Long newExpireTime = new Date().getTime() + expireTime;
            String currentExpireTime = getJedis().getSet(key, newExpireTime + "");
            // 4.判断currentExpireTime与oldExpireTime
            // 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,
            //那么当前请求可以直接返回失败,或者继续重试。防止java多个线程进入到该方法造成锁的获取混乱。
            if (!currentExpireTime.equals(oldExpireTime)) {
                return false;
            } else {
                return true;
            }
        } else {
            // 锁被占用
            return false;
        }
    }

    /**
     *
     * @Description: 如果业务处理完,key的时间还未到期,那么通过删除该key来释放锁
     * @param key
     * @param dealTime 处理业务的消耗时间
     * @param expireTime 失效时间
     */
    public void deleteLockKey(String key,long dealTime, final long expireTime) {
        if (dealTime < expireTime) {
            getJedis().del(key);
        }
    }

示例:

    // 循环等待获取锁
            StringBuilder key = new StringBuilder(KEY_PRE);
            key.append(code).append("_");
            key.append(batchNum);
            long lockTime = 0;
            try {
                while (true) {
                    boolean locked = redisCacheClient.getLockKey(
                            key.toString(), 60000);
                    if (locked) {
                        lockTime = System.currentTimeMillis();
                        break;
                    }
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {

            }
    //业务逻辑...

    //业务逻辑进行完,解锁
            long delLockDateTime =System.currentTimeMillis();
            long dealTime = delLockDateTime - lockTime;
            deleteLockKey(key.toString(), dealTime, 60000);

四、一些问题

1、为什么不直接使用expire设置超时时间,而将时间的毫秒数其作为value放在redis中?

如下面的方式,把超时的交给redis处理:

lock(key, expireSec){
isSuccess = setnx key
if (isSuccess)
expire key expireSec
}

这种方式貌似没什么问题,但是假如在setnx后,redis崩溃了,expire就没有执行,结果就是死锁了。锁永远不会超时。

2、为什么前面的锁已经超时了,还要用getSet去设置新的时间戳的时间获取旧的值,然后和外面的判断超时时间的时间戳比较呢?

因为是分布式的环境下,可以在前一个锁失效的时候,有两个进程进入到锁超时的判断。如:

C0超时了,还持有锁,C1/C2同时请求进入了方法里面

C1/C2获取到了C0的超时时间

C1使用getSet方法

C2也执行了getSet方法

假如我们不加 oldValueStr.equals(currentValueStr) 的判断,将会C1/C2都将获得锁,加了之后,能保证C1和C2只能一个能获得锁,一个只能继续等待。

注意:这里可能导致超时时间不是其原本的超时时间,C1的超时时间可能被C2覆盖了,但是他们相差的毫秒及其小,这里忽略了

五、不完善之处

1、使用时需要预估业务逻辑处理时间,一旦业务逻辑发生错误,那么只能等到超时之后其他线程才能拿到锁,可能会出现问题

原文地址:https://www.cnblogs.com/lige-H/p/8195535.html

时间: 2024-11-05 12:28:09

基于redis的一种分布式锁的相关文章

基于Redis的三种分布式爬虫策略

前言: 爬虫是偏IO型的任务,分布式爬虫的实现难度比分布式计算和分布式存储简单得多. 个人以为分布式爬虫需要考虑的点主要有以下几个: 爬虫任务的统一调度 爬虫任务的统一去重 存储问题 速度问题 足够"健壮"的情况下实现起来越简单/方便越好 最好支持"断点续爬"功能 Python分布式爬虫比较常用的应该是scrapy框架加上Redis内存数据库,中间的调度任务等用scrapy-redis模块实现. 此处简单介绍一下基于Redis的三种分布式策略,其实它们之间还是很相似

【连载】redis库存操作,分布式锁的四种实现方式[三]--基于Redis watch机制实现分布式锁

一.redis的事务介绍 1. Redis保证一个事务中的所有命令要么都执行,要么都不执行.如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行.而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令. 2. 除此之外,Redis的事务还能保证一个事务内的命令依次执行而不被其他命令插入.试想客户端A需要执行几条命令,同时客户端B发送了一条命令,如果不使用事务,则客户端B的命令可能会插入

基于redis和zookeeper的分布式锁实现方式

先来说说什么是分布式锁,简单来说,分布式锁就是在分布式并发场景中,能够实现多节点的代码同步的一种机制.从实现角度来看,主要有两种方式:基于redis的方式和基于zookeeper的方式,下面分别简单介绍下这两种方式: 一.基于redis的分布式锁实现 1.获取锁 redis是一种key-value形式的NOSQL数据库,常用于作服务器的缓存.从redis v2.6.12开始,set命令开始变成如下格式: SET key value [EX seconds] [PX milliseconds] [

常用的分布式锁和redis和zk两种分布式锁的对比

常用的分布式锁 一..基于数据库实现分布式锁 1. 悲观锁 利用select … where … for update 排他锁 注意: 其他附加功能与实现一基本一致,这里需要注意的是“where name=lock ”,name字段必须要走索引,否则会锁表.有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题. 2. 乐观锁 所谓乐观锁与前边最大区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update version失败后才

Redis事务与可分布式锁

1    Redis事务 1.1   Redis事务介绍 l  Redis的事务是通过MULTI,EXEC,DISCARD和WATCH这四个命令来完成的. l  Redis的单个命令都是原子性的,所以这里确保事务性的对象是命令集合. l  Redis将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行 l  Redis不支持回滚操作 1.2   相关命令 l  MULTI 用于标记事务块的开始. Redis会将后续的命令逐个放入队列中,然后使用EXEC命令原子化地执行这个命令序列.

使用Redis SETNX 命令实现分布式锁

转自:http://blog.csdn.net/lihao21/article/details/49104695 使用Redis的 SETNX 命令可以实现分布式锁,下文介绍其实现方法. SETNX命令简介 命令格式 SETNX key value 将 key 的值设为 value,当且仅当 key 不存在. 若给定的 key 已经存在,则 SETNX 不做任何动作. SETNX 是SET if Not eXists的简写. 返回值 返回整数,具体为 - 1,当 key 的值被设置 - 0,当

redis击穿,穿透,雪崩,分布式锁,api(jedis,luttuce)

击穿:(redis做缓存用,肯定发生了高并发,到达数据库查询)设置key 的过期时间,过期后没有这个key,找不到了,就穿过了(其中一个key过期导致并发访问数据库)LRU (LRU,即:最近最少使用淘汰算法(Least Recently Used).LRU是淘汰最长时间没有被使用的页面.)LFU (LFU,即:最不经常使用淘汰算法(Least Frequently Used).LFU是淘汰一段时间内,使用次数最少的页面) 1.key为null 2.setnx 如果key存在,则什么都不做 在

两种分布式锁实现方案(一)

一.为何使用分布式锁?当应用服务器数量超过1台,对相同数据的访问可能造成访问冲突(特别是写冲突).单纯使用关系数据库比如MYSQL的应用可以借助于事务来实现锁,也可以使用版本号等实现乐观锁,最大的缺陷就是可用性降低(性能差).对于GLEASY这种满足大规模并发访问请求的应用来说,使用数据库事务来实现数据库就有些捉襟见肘了.另外对于一些不依赖数据库的应用,比如分布式文件系统,为了保证同一文件在大量读写操作情况下的正确性,必须引入分布式锁来约束对同一文件的并发操作. 二.对分布式锁的要求1.高性能(

深入Redis(一)分布式锁

分布式锁 由于分布式应用在逻辑处理时存在并发问题,比方修改数据,要先读取到内存,在内存中修改后再保存回去,这两个操作是单独的,如果同时进行,就会出现并发问题. 此时就要用到分布式锁来限制程序的并发执行. 本质 本质就是在Redis内占一个位置,若别的进程也想占用该位置,发现有进程在使用该位置,就放弃或等待. 在Redis中实现依靠setnx(set if not exists)指令,用完了再调用del指令来释放位置. 在1中,如果逻辑执行到中间出现异常,可能导致del未调用,这就陷入死锁,锁永远