分布式锁的实现(redis)

这篇具有很好参考价值的文章主要介绍了分布式锁的实现(redis)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、单机锁

考虑在并发场景并且存在竞态的状况下,我们就要实现同步机制了,最简单的同步机制就是加锁。

加锁可以帮我们锁住资源,如内存中的变量,或者锁住临界区(线程中的一段代码),使得同一个时刻只有一个线程能访问某一个区域。

如果是单实例(单进程部署),那么单机锁就可以满足我们的要求了,如synchronized,ReentrantLock。

因为在一个进程中的不同线程可以共享这个锁。

2、分布式锁

但是如果场景来到了分布式系统呢?

分布式系统部署在不同的机器上,或者只是简单的多进程部署。这样各个进程之间无法共享同一个锁。

这时候我们要加分布式锁。

分布式锁大概就是这么一个东西:通过共享的存储缓存一个状态值,用状态值的变化标识锁的占用和释放。

可以通过mysql,redis,zk等实现分布式锁,这里我们实现一个redis的。如果你用java其实使用zk会很简单。

3、为什么redis能用来实现分布式锁?

1)Redis是单进程单线程模式

redis实现为单进程单线程模式,这样多个客户端并不存在竞态关系。

2)原子性原语

redis提供了可以实现原子操作的原语如setnx、getset等。

setnx

1)SETNX key value

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

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

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

可用版本:
>= 1.0.0
时间复杂度:
O(1)
返回值:
设置成功,返回 1 。
设置失败,返回 0 。

复制

getset

GETSET key value

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

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

可用版本:
>= 1.0.0
时间复杂度:
O(1)
返回值:
返回给定 key 的旧值。
当 key 没有旧值时,也即是, key 不存在时,返回 nil 。

复制

4、实现

package com.xiaoju.dqa.fusor.utils;

import com.xiaoju.dqa.fusor.client.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
public class DistributeLockUtil {

    // 锁超时时间, 防止死锁
    private static final long LOCK_TIMEOUT = 60;

    @Autowired
    private RedisClient redisClient;

    private boolean locked = false;

    public boolean lock(String key) {
        String expireTime = String.valueOf(System.currentTimeMillis() + LOCK_TIMEOUT * 1000);
        /*
        *   setnx 返回1
        *   说明: 1)key不存在, 2)成功写入锁, 并更新锁的生存时间
        *   也就是get锁
        * */
        if (redisClient.setnx(key, expireTime) == 1) {
            locked = true;
            return true;
        }
        /*
        *  没有get锁, 下面进入判断锁超时逻辑
        * */
        String currentExpireTime = redisClient.get(key);
        /*
        *   锁生存时间已经过了, 说明锁已经超时
        * */
        if (Long.parseLong(currentExpireTime) < System.currentTimeMillis()) {
            String oldValueStr = redisClient.getSet(key, expireTime);
            /*
            *   判断锁生存时间和你改的写那个时间是否相等
            *   相当于你竞争了一个更新锁
            * */
            if (oldValueStr.equals(currentExpireTime)) {
                locked = true;
                return true;
            }
        }
        return false;
    }

    public void release(String key) {
        if (locked) {
            redisClient.del(key);
            locked = false;
        }
    }

}

复制

5、死锁

为了解决死锁,这里设置了锁的超时时间。

    private static final long LOCK_TIMEOUT = 60;

复制

并通过setnx时更新锁生存时间来维护锁超时的判定。

String expireTime = String.valueOf(System.currentTimeMillis() + LOCK_TIMEOUT * 1000);
...
if (redisClient.setnx(key, expireTime) == 1) {
...
}
...
String oldValueStr = redisClient.getSet(key, expireTime);
...

复制

为什么要使用这种方式,而不是expire呢?

因为setnx和expire不能作为一个原子性的操作存在,设想如果setnx之后,在执行expire之前出现了异常,那么锁将没有超时时间。也就是死锁。

6、解决锁超时引入的竞态

设想三个客户端,C0,C1,C2

如果C0持有锁并且崩溃,锁没有释放。

C1和C2同时发现了锁超时。

然后都通过getset去拿到了旧值,在对比了旧值和之前值之后,如果相等,那么说明“我”成功修改了旧值,那么我就拿到了锁。

7、 时钟同步

我们看到foo.lock的value值为时间戳,所以要在多客户端情况下,保证锁有效,一定要同步各服务器的时间,如果各服务器间,时间有差异。时间不一致的客户端,在判断锁超时,就会出现偏差,从而产生竞争条件。 锁的超时与否,严格依赖时间戳,时间戳本身也是有精度限制,假如我们的时间精度为秒,从加锁到执行操作再到解锁,一般操作肯定都能在一秒内完成。这样的话,我们上面的CASE,就很容易出现。所以,最好把时间精度提升到毫秒级。这样的话,可以保证毫秒级别的锁是安全的。

8、一些处理不了的情况

设想三个客户端,C0,C1,C2

如果C0持有锁很长,锁已经超时。这时候有C1,C2判断锁超时了,然后通过超时竞争,C1拿到了锁。

这时C0醒了过来,删除了C1的锁。

这时,C1认为自己独占了锁,其他的进程也进入了竞争锁的情况

对于这种情况,这里是没有提供解决办法的。

思路是:你降级你的锁,比如给你的锁加上uuid,对不同的业务或者不同的session加上对应粒度的锁。文章来源地址https://www.toymoban.com/news/detail-518333.html

到了这里,关于分布式锁的实现(redis)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Zookeeper 和 Redis 哪种更好? 为什么使用分布式锁? 1. 利用 Redis 提供的 第二种,基于 ZK 实现分布式锁的落地方案 对于 redis 的分布式锁而言,它有以下缺点:

    关于这个问题,我们 可以从 3 个方面来说: 为什么使用分布式锁? 使用分布式锁的目的,是为了保证同一时间只有一个 JVM 进程可以对共享资源进行操作。 根据锁的用途可以细分为以下两类: 允许多个客户端操作共享资源,我们称为共享锁 这种锁的一般是对共享资源具有

    2024年01月16日
    浏览(49)
  • 【Redis】Redis分布式锁的10个坑

    日常开发中,经常会碰到秒杀抢购等业务。为了避免并发请求造成的库存超卖等问题,我们一般会用到Redis分布式锁。但是使用Redis分布式锁,很容易踩坑哦~ 本文将给大家分析阐述,Redis分布式锁的10个坑~ 一说到实现Redis的分布式锁,很多小伙伴马上就会想到setnx+ expire命令。

    2024年02月05日
    浏览(41)
  • Redis——》Redis的部署方式对分布式锁的影响

    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结——》【SpringBoot】     总结——》【MyBatis、MyBatis-Plus】     总结——》【Linux】     总结——》【MongoDB】    

    2024年02月10日
    浏览(52)
  • 【面试题24】你是如何使用Redis分布式锁的

    本文已收录于PHP全栈系列专栏:PHP面试专区。 计划将全覆盖PHP开发领域所有的面试题, 对标资深工程师/架构师序列 ,欢迎大家提前关注锁定。 Redis分布式锁是一种利用Redis实现的分布式锁机制。它通过在共享的Redis实例上设置一个特定的键值对来实现对资源的互斥访问。今

    2024年02月11日
    浏览(39)
  • 在Spring中,可以使用不同的方式来实现分布式锁,例如基于数据库、Redis、ZooKeeper等

    在Spring中,可以使用不同的方式来实现分布式锁,例如基于数据库、Redis、ZooKeeper等。下面是两种常见的实现方式: 使用Redis实现分布式锁: 使用自定义注解实现本地锁: 以上是两种常见的在Spring中实现分布式锁的方式。第一种方式使用Redis作为分布式锁的存储介质,通过

    2024年03月17日
    浏览(46)
  • 【征服redis15】分布式锁的功能与整体设计方案

    目录  1. 分布式锁的概念 2.基于数据库做分布式锁 2.1 基于表主键唯一做分布式锁 2.2 基于表字段版本号做分布式锁 2.3 基于数据库排他锁做分布式锁 3.使用Redis做分布式锁 3.1 redis实现分布式锁的基本原理 3.2 问题一:增加超时机制,防止长期持有的情况 3.3 问题2:重入的问题

    2024年01月22日
    浏览(39)
  • 分布式锁的几种实现方式:

    redis是基于单线程,在某个时刻只会有一个线程执行命令,可以利用set原子性的操作,配合set nx(RedisStringCommands.SetOption.SET_IF_ABSENT) ,这样,当多个线程或多个节点尝试获取锁时,只有一个可以成功,其他的会因为锁已存在而获取失败。这种方式通过 Redis 来实现分布式锁,

    2024年01月18日
    浏览(43)
  • 分布式天梯图算法在 Redis 图数据库中的应用

    Redis是一个高性能的键值对数据库,支持常用的数据结构和分布式操作,被广泛应用于缓存、消息队列和排行榜等场景。除了基本的数据结构,Redis还支持图数据结构并提供了一些算法支持。 天梯图算法是一种基于贪心的图搜索算法,在寻找最短路径问题中具有很高的效率。

    2024年02月14日
    浏览(35)
  • ZooKeeper分布式锁的实现与应用

    ZooKeeper是一种分布式应用程序协调服务,它可以管理大规模的集群,并提供可靠的、有序的、高效的数据通信。其中,ZooKeeper提供的分布式锁是一种常见的分布式锁实现,本文将对其进行详细介绍。 在分布式系统中,多个进程或节点可能需要同时访问共享资源。为了确保数据

    2024年02月02日
    浏览(41)
  • 高并发缓存问题分析以及分布式锁的实现

    在高并发的环境下,比如淘宝,京东不定时的促销活动,大量的用户访问会导致数据库的性能下降,进而有可能数据库宕机从而不能产生正常的服务,一般一个系统最大的性能瓶颈,就是数据库的io操作,如果发生大量的io那么他的问题也会随之而来。从数据库入手也是调优性价比最高

    2024年01月19日
    浏览(72)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包