redis实现分布式锁原理解析

目录

  • 1、什么是分布式锁?
  • 2、redis实现的分布式锁
  • 3、内部实现解析
    • 3.1、redis中的数据变化
    • 3.2、redisson的实现方式

1、什么是分布式锁?

分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调各个系统之间的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

2、redis实现的分布式锁

使用的是redisson框架操作redis命令。redisson实现了redlock算法,提供了异步,公平性等高级功能。添加maven依赖。

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.11.1</version>
</dependency>

java实现的基于单机模式redis的分布式锁,集群模式于该模式类似,可靠性更高

public class RedisConfig {
    private static int count = 0; // 代表分布式环境下要争夺得资源
    private String address = "127.0.0.1:6379";
    private String password = "961113";
//    private String poolSize= "20";
//    private String database= "10";

    public static void main(String[] args) throws InterruptedException {
        String address = "redis://127.0.0.1:6379";

        Config config = new Config();
        config.useSingleServer().setAddress(address).setPassword("961113");
        RedissonClient redisson = Redisson.create(config);
        System.out.println(redisson);

        int nThreads = 500; // 测试并发数
        ExecutorService exec = Executors.newFixedThreadPool(nThreads);

        List tasks = new ArrayList(nThreads);
        for (int i = 0; i < nThreads; i++) {
            tasks.add((Callable) () -> {
                Thread.sleep(10); //代替处理业务的时间

                RLock lock = redisson.getLock("redis");
                try {
                    boolean b = lock.tryLock(60, TimeUnit.SECONDS);
                    if (b) {
                        ++RedisConfig.count;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 使用完之后一定要释放锁
                    lock.unlock();
                }
                return RedisConfig.count;
            });
        }
        exec.invokeAll(tasks);
        exec.shutdown();
        while (true) {
            if (exec.isTerminated()) {
                System.out.println("所有的子线程都结束了!");
                break;
            }
        }
        System.out.println("count = " + count);
        redisson.shutdown();
    }
}

output

[email protected]
所有的子线程都结束了!
count = 500

3、内部实现解析

3.1、redis中的数据变化

以org.redisson.RedissonLock的这一个实现来进行解读。上面的测试代码执行过程,redis中会出现一个hash类型的值,其中,“redis”是锁名称,“redis”中有一个键值对,键为:“7b319fef-0674-4a62-9a5b-13d786f4a99e:137”,值为“1”。

其中,键的格式RedissonLock类中如下所示,为UUID + threadId,value为重入值。redisson实现了可重入的分布式锁,此种情况value就对应实际的可重入值了。

protected String getLockName(long threadId) {
    return id + ":" + threadId;
}

redis中的数据

127.0.0.1:6379> TYPE redis
hash
127.0.0.1:6379> HGETALL redis
1) "7b319fef-0674-4a62-9a5b-13d786f4a99e:137"
2) "1"
127.0.0.1:6379> HGETALL redis
1) "7b319fef-0674-4a62-9a5b-13d786f4a99e:95"
2) "1"
127.0.0.1:6379> HGETALL redis
(empty list or set)
127.0.0.1:6379> HGETALL redis
1) "7b319fef-0674-4a62-9a5b-13d786f4a99e:93"
2) "1"

3.2、redisson的实现方式

主要实现是通过lua脚本实现,lua脚本本身支持原子性。分析org.redisson.RedissonLock中尝试加锁的过程。

加锁过程

// KEYS[1]是分布式锁的key,示例中的“redis”字符串
// ARGV[1]是所得租约时间,默认30s。
// ARGV[2]获取锁时set的唯一值(uuid + threadId),因为要确认一个锁是不是同一个线程操作。
// 如果分布式锁key不存在,那么执行hset命令(hset REDLOCK_KEY uuid+threadId 1),并通过pexpire设置失效时间(也是锁的租约时间)
// 如果分布式锁的KEY已经存在,并且value也匹配,表示是当前线程持有的锁,那么重入次数加1,并且设置失效时间
// 获取分布式锁的KEY的失效时间毫秒数
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    internalLockLeaseTime = unit.toMillis(leaseTime);

    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
              "if (redis.call('exists', KEYS[1]) == 0) then " +
                  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              "return redis.call('pttl', KEYS[1]);",
                Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

释放锁过程

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            // 如果锁存在,但是value不匹配,表示锁被占用,直接返回
            "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                "return nil;" +
            "end; " +
            // 执行到这一步,意味着锁存在,value也匹配,表示当前线程占有锁,重入数减一
            "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
            // 重入次数减一后是大于0的,则只设置失效时间,不能删除
            "if (counter > 0) then " +
                "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                "return 0; " +
            "else " +
                // 重入次数减一后是等于0的,则删除key,并发布解锁消息。
                "redis.call('del', KEYS[1]); " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; "+
            "end; " +
            "return nil;",
            // 这5个参数分别对应KEYS[1],KEYS[2],ARGV[1],ARGV[2]和ARGV[3]
            Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}
/**
 * 欢迎评论、留言、发表看法。谢谢!
 */

原文地址:https://www.cnblogs.com/mengHeJiuQIan/p/11161321.html

时间: 2024-08-01 05:42:41

redis实现分布式锁原理解析的相关文章

利用多写Redis实现分布式锁原理与实现分析

在我写这篇文章的时候,其实我还是挺纠结的,因为我这个方案本身也是雕虫小技拿出来显眼肯定会被贻笑大方,但是我最终还是拿出来与大家分享,我本着学习的态度和精神,希望大家能够给与我指导和改进方案. 一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子: 场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能在某一个时刻会有二笔一样的单子同时到达系统后台. 场景二:在App中下订单的时候,点击确认之后,没反应,就又点击了几次.在这种情况下,如果无法保证该接口的幂

Redis实现分布式锁原理与实现分析

一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子: 场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能在某一个时刻会有二笔一样的单子同时到达系统后台. 场景二:在App中下订单的时候,点击确认之后,没反应,就又点击了几次.在这种情况下,如果无法保证该接口的幂等性,那么将会出现重复下单问题. 在接收消息的时候,消息推送重复.如果处理消息的接口无法保证幂等,那么重复消费消息产生的影响可能会非常大. 类似这种场景,我们有很多种方法,可以使用幂等操作,也

基于Redis的分布式锁和Redlock算法

1 前言 前面写了4篇Redis底层实现和工程架构相关文章,感兴趣的读者可以回顾一下: Redis面试热点之底层实现篇-1 Redis面试热点之底层实现篇-2 Redis面试热点之工程架构篇-1 Redis面试热点之工程架构篇-2 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手锏功能,是基于Redis支持的数据类型和分布式架构来实现的,属于小而美的应用. 结合笔者的日常工作,

身为一枚优秀的程序员必备的基于Redis的分布式锁和Redlock算法

1 前言 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手锏功能,是基于Redis支持的数据类型和分布式架构来实现的,属于小而美的应用. 结合笔者的日常工作,今天和大家一起研究下基于Redis的分布式锁和Redlock算法的一些事情. 2.初识锁 1. 锁的双面性 现在我们写的程序基本上都有一定的并发性,要么单台多进线程.要么多台机器集群化,在仅读的场景下是不需要加锁的,因为数

关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在jdk java.util 并发包中已经为我们提供了这些方法去加锁, 比如synchronized 关键字 或者Lock 锁,都可以处理. 但是我们现在的应用程序如果只部署一台服务器,那并发量是很差的,如果同时有上万的请求那么很有可能造成服务器压力过大,而瘫痪. 想想双十一 和 三十晚上十点分支付宝红

redis分布式锁原理与实现

分布式锁原理 分布式锁,是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁. 使用setnx.getset.expire.del这4个redis命令实现 setnx 是『SET if Not eXists』(如果不存在,则 SET)的简写. 命令格式:SETNX key value:使用:只在键 k

redisson实现分布式锁原理

Redisson分布式锁 之前的基于注解的锁有一种锁是基本redis的分布式锁,锁的实现我是基于redisson组件提供的RLock,这篇来看看redisson是如何实现锁的. 不同版本实现锁的机制并不相同 引用的redisson最近发布的版本3.2.3,不同的版本可能实现锁的机制并不相同,早期版本好像是采用简单的setnx,getset等常规命令来配置完成,而后期由于redis支持了脚本Lua变更了实现原理. <dependency> <groupId>org.redisson&

基于Redis实现分布式锁-Redisson使用及源码分析

在分布式场景下,有很多种情况都需要实现最终一致性.在设计远程上下文的领域事件的时候,为了保证最终一致性,在通过领域事件进行通讯的方式中,可以共享存储(领域模型和消息的持久化数据源),或者做全局XA事务(两阶段提交,数据源可分开),也可以借助消息中间件(消费者处理需要能幂等).通过Observer模式来发布领域事件可以提供很好的高并发性能,并且事件存储也能追溯更小粒度的事件数据,使各个应用系统拥有更好的自治性. 本文主要探讨另外一种实现分布式最终一致性的解决方案--采用分布式锁.基于分布式锁的解决

基于Redis实现分布式锁实战

背景在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等.大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系.其次Redis提供一些命令SETNX,GETSET,可以方便实现分布式锁机制. Redis命令介绍使用Redis实现分布式锁,有两个重要函数需要介绍 SETNX命令(SET if Not eXists)语法:SETNX key value功能:当且仅当 key 不