专栏首页玩转JavaEERedis 做分布式锁你会几种姿势?

Redis 做分布式锁你会几种姿势?

今日干货

本文大纲:

  • 1.什么是分布式锁
  • 2.解决方案
    • 2.1 整体思路
    • 2.2 解决方案一
    • 2.3 解决方案二
  • 3.小结

Redis 简简单单的几种数据类型,一个 key/value 数据库,现在又是分布式锁、又是限流工具、又是消息队列......,感觉都要被玩坏了。不过话说回来,Redis 在这么多场合被开发者们喜欢,还是得益于它极高的性能与使用的简洁性。

在面试的时候,说到 Redis ,很多人第一反应就是缓存,其实除了缓存,Redis 还有非常多丰富的使用场景,这些使用场景,松哥在未来都会和大家一一分享。

今天就先来看一个简单的,用 Redis 做分布式锁。

1.什么是分布式锁

首先我们来看一个问题场景:

❝例如一个简单的用户操作,一个线程去修改用户的状态,首先从数据库中读出用户的状态,然后在内存中进行修改,修改完成后,再存回去。在单线程中,这个操作没有问题,但是在多线程中,由于读取、修改、存 这是三个操作,不是原子操作,所以在多线程中,这样会出问题。 ❞

解决这个问题,我们就需要锁,对于锁,大家应该不会陌生,在 Java 中的 synchronized 以及 ReentrantLock 可重入锁都是我们比较常见的,但是这种锁都是本地锁,现在微服务、分布式系统思想大行其道,在这样的系统中,本地锁显然是不够用的,于是大家纷纷想办法,如何在分布式环境下解决锁的问题。想出来的办法很多,我们可以通过 MySQL、可以通过 ZK、也可以通过 Redis ,都可以用来解决分布式锁的问题,这里我们主要来看看如何通过 Redis 解决分布式锁问题。

2.解决方案

2.1 整体思路

「分布式锁实现的思路很简单,就是进来一个线城先占位,当别的线城进来操作时,发现已经有人占位了,就会放弃或者稍后再试。」

在 Redis 中,占位一般使用 setnx 指令,先进来的线程先占位,线程的操作执行完成后,再调用 del 指令释放位子。同时为了防止死锁,我们一般还要给锁加一个过期时间,到期了自动释放。

基于这样的思路,我们来看两种不同的实现方式:

2.2 解决方案一

基于我们前面所说的思路,可以使用 setnx 和 expire 实现分布式锁,但是 setnx 和设置过期时间 expire 这是两个操作,这两个操作一起的话就不具备原子性(除非自己写 Lua 脚本),为了解决这个问题,从 Redis2.8 开始,setnx 和 expire 可以通过一个命令一起来执行了,这个命令就是 set,set 中多了一个参数

在 key/value 之后,还有一个 EX 5 表示以秒计的过期时间(PX 表示以毫秒计的过期时间),最后的 NX 就是说如果 k1 不存在,这条命令执行成功,否则执行失败,这就相当于 setnx 的效果了。

因此,我们封装的锁如下:

public class LockTest {
    public static void main(String[] args) {
        Redis redis = new Redis();
        redis.execute(jedis->{
            String set = jedis.set("k1", "v1", new SetParams().nx().ex(5));
            if (set !=null && "OK".equals(set)) {
                //没人占位
                jedis.set("name", "javaboy");
                String name = jedis.get("name");
                System.out.println(name);
                jedis.del("k1");//释放资源
            }else{
                //有人占位,停止/暂缓 操作
            }
        });
    }
}

对于上面这段代码,大家重点看思路,不必深究代码细节:

  1. 首先构造一个 Redis 实例,然后调用 execute 方法,这个是我自己封装的方法,目的是为了配置 Jedis 连接池并及时回收使用过的资源。这一块小伙伴们测试的时候可以直接使用自己创建的 Jedis 实例,效果是一样的。
  2. 调用 jedis 中的 set 方法,注意第三个参数,我们设置了 nx 同时 设置了过期时间为 5 秒,这就相当于 setnx 和 expire 两个命令的结合体。
  3. 如果成功执行了 set 命令,在 if 中就可以去写自己的业务了。如果没能抢到锁,则可以进入到一个延迟消息队列中,停一会再去尝试(用 Redis 实现延迟消息队列,松哥会在后面的文章中向大家介绍)。

但是这样的封装,又带来了一个新的问题,那就是超时问题,关于超时问题,松哥通过一个视频教程来和大家分享(本视频节选自松哥自制的 Spring Boot+Vue+Spring Cloud+Redis 系列视频教程):

2.3 解决方案二

上面的代码写着还是蛮长的,那么有没有简单一点的办法呢?当然是有的!那就是 Redisson。

相对于 Jedis 这种原生态的应用,Redisson 对 Redis 请求做了较多的封装,对于锁,也提供了对应的方法可以直接使用:

Config config = new Config();
//配置 Redis 基本连接信息
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123");
//获取一个 RedissonClient 对象
RedissonClient redisson = Redisson.create(config);
//获取一个锁对象实例
RLock lock = redisson.getLock("lock");
try {
    //获取锁
    boolean b = lock.tryLock(500, 1000, TimeUnit.MILLISECONDS);
    if (b) {
        //获取到锁了,开始写业务
        RBucket<Object> bucket = redisson.getBucket("javaboy");
        bucket.set("www.javaboy.org");
        Object o = bucket.get();
        System.out.println(o);
    }else{
        System.out.println("没拿到锁");
    }
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    //释放锁
    lock.unlock();
}

在这段代码中,核心的就是 lock.tryLock(500, 1000, TimeUnit.MILLISECONDS);,第一个参数是尝试加锁的等待时间为 500 毫秒,第二个参数表示锁的超时时间为 1000 毫秒,也就是这个锁在 1000 毫秒后会自动失效。

小伙伴们发现,这和我们在方案一里边配置的参数是一样的,其实思路是不变的,Redisson 只不过是将我们写的和锁相关的方法封装起来了而已。

3.小结

当然,这里我只是先简单介绍下加锁的思路以及在 Redis 单机中如何加锁,后面再和大家分享 Redis 集群中如何加锁。

本文分享自微信公众号 - 牧码小子(a_javaboy),作者:江南一点雨

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Redis教程

    断断续续,Redis教程总算告一段落了,最后一篇其实很早就写好了,一直忘了发,现在总算凑齐了,发出来,做成一个完整的教程,可能还会有遗漏的点,遇到了再慢慢补上。...

    江南一点雨
  • Linux上安装Redis

    hello,各位小伙伴们好久不见!那么从今天开始,我想和各位小伙伴分享下Redis的用法,本文我们就先来看看什么是Redis以及如何安装Redis。 什么是Re...

    江南一点雨
  • Spring Boot2 系列教程(二十六)Spring Boot 整合 Redis

    本系列第 24、25 两篇文章,分别是 Spring Boot+Jpa 以及 Spring Boot+Jpa 多数据源,不知触发了什么关键字,一直无法发送,找了...

    江南一点雨
  • Redis面试高频45题笔记:基础+缓存雪崩+哨兵+集群+Reids学习笔记

    欢迎关注专栏:Java架构技术进阶。里面有大量batj面试题集锦,还有各种技术分享,如有好文章也欢迎投稿哦。

    慕容千语
  • 【redis从入门到上线(4)】- redis高可用架构横向对比分析

    上篇我们讲解完 Redis Sentinel 原理之后,接下来讲解常用的 Redis 高可用架构。

    一条老狗
  • Redis 创始人宣布退居二线:我写代码只是为了表达自己!

    前几日,Redis 创始人 Salvatore Sanfilippo 在他的个人博客(http://antirez.com/)上宣布将结束自己的 Redis 之...

    程序猿DD
  • 如何在云开发Cloudbase中使用Redis?

    云开发 Cloudbase 是腾讯云为移动开发者提供的云原生一体化应用开发平台,可用于开发多种客户端,它帮助开发者统一构建和管理资源,免去了应用开发过程中繁琐的...

    腾讯云开发TCB
  • Gavin老师:分布式缓存Redis高级应用实战(艾编程精品视频)

    【学完本节课你将掌握如下知识】 1、分布式缓存中间件选型 2、Redis作为单线程模式为什么效能还这么高? 3、Redis服务安装机常用命令解析 4、如...

    艾编程
  • 有了它,Redis不再是你技术面试中的阻碍

    Redis在互联网技术存储方面使用非常广泛,几乎所有的后端技术面试官都要在Redis的使用和原理方面对求职小伙伴们进行各种刁难,特别是那些优秀的、竞争激烈的大型...

    博文视点Broadview
  • 大厂和初创公司都在用!Redis好在哪?终于有人讲明白了

    Redis从一个不为人熟知、只有少量应用的崭新数据库,逐渐变成了内存数据库领域的事实标准。时至今日,经过大量的实践应用,Redis简洁高效、安全稳定的特性已经深...

    华章科技

扫码关注云+社区

领取腾讯云代金券