文章有点长并且绕,先来个图片缓冲下!

前言

现在的业务场景越来越复杂,使用的架构也就越来越复杂,分布式、高并发已经是业务要求的常态。像腾讯系的不少服务,还有CDN优化、异地多备份等处理。
说到分布式,就必然涉及到分布式锁的概念,如何保证不同机器不同线程的分布式锁同步呢?

实现要点

  1. 互斥性,同一时刻,只能有一个客户端持有锁。

  2. 防止死锁发生,如果持有锁的客户端崩溃没有主动释放锁,也要保证锁可以正常释放及其他客户端可以正常加锁。

  3. 加锁和释放锁必须是同一个客户端。

  4. 容错性,只有redis还有节点存活,就可以进行正常的加锁解锁操作。

正确的redis分布式锁实现

错误加锁方式一

保证互斥和防止死锁,首先想到的使用redis的setnx命令保证互斥,为了防止死锁,锁需要设置一个超时时间。

public static void wrongLock(Jedis jedis, String key, String uniqueId, int expireTime) {

在多线程并发环境下,任何非原子性的操作,都可能导致问题。这段代码中,如果设置过期时间时,redis实例崩溃,就无法设置过期时间。如果客户端没有正确的释放锁,那么该锁(永远不会过期),就永远不会被释放。

错误加锁方式二

比较容易想到的就是设置值和超时时间为原子原子操作就可以解决问题。那使用setnx命令,将value设置为过期时间不就ok了吗?

public static boolean wrongLock(Jedis jedis, String key, int expireTime) {

乍看之下,没有什么问题。但仔细分析,有如下问题:

  1. value设置为过期时间,就要求各个客户端严格的时钟同步,这就需要使用到同步时钟。即使有同步时钟,分布式的服务器一般来说时间肯定是存在少许误差的。

  2. 锁过期时,使用 jedis.getSet虽然可以保证只有一个线程设置成功,但是不能保证加锁和解锁为同一个客户端,因为没有标志锁是哪个客户端设置的嘛。

错误解锁方式一

直接删除key

public static void wrongReleaseLock(Jedis jedis, String key) {

简单粗暴,直接解锁,但是不是自己加锁的,也会被删除,这好像有点太随意了吧!

错误解锁方式二

判断自己是不是锁的持有者,如果是,则只有持有者才可以释放锁。

public static void wrongReleaseLock(Jedis jedis, String key, String uniqueId) {

看起来很完美啊,但是如果你判断的时候锁是自己持有的,这时锁超时自动释放了。然后又被其他客户端重新上锁,然后当前线程执行到jedis.del(key),这样这个线程不就删除了其他线程上的锁嘛,好像有点乱套了哦!

正确的加解锁方式

基本上避免了以上几种错误方式之外,就是正确的方式了。要满足以下几个条件:

  1. 命令必须保证互斥

  2. 设置的key必须要有过期时间,防止崩溃时锁无法释放

  3. value使用唯一id标志每个客户端,保证只有锁的持有者才可以释放锁

加锁直接使用set命令同时设置唯一id和过期时间;其中解锁稍微复杂些,加锁之后可以返回唯一id,标志此锁是该客户端锁拥有;释放锁时要先判断拥有者是否是自己,然后删除,这个需要redis的lua脚本保证两个命令的原子性执行。
下面是具体的加锁和释放锁的代码:

@Slf4jpublic class RedisDistributedLock {    private static final String LOCK_SUCCESS = "OK";    private static final Long RELEASE_SUCCESS = 1L;    private static final String SET_IF_NOT_EXIST = "NX";    private static final String SET_WITH_EXPIRE_TIME = "PX";    // 锁的超时时间    private static int EXPIRE_TIME = 5 * 1000;    // 锁等待时间    private static int WAIT_TIME = 1 * 1000;

    private Jedis jedis;    private String key;

    public RedisDistributedLock(Jedis jedis, String key) {        this.jedis = jedis;        this.key = key;    }

    // 不断尝试加锁    public String lock() {        try {            // 超过等待时间,加锁失败            long waitEnd = System.currentTimeMillis() + WAIT_TIME;            String value = UUID.randomUUID().toString();            while (System.currentTimeMillis()                 String result = jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, EXPIRE_TIME);                if (LOCK_SUCCESS.equals(result)) {                    return value;                }                try {                    Thread.sleep(10);                } catch (InterruptedException e) {                    Thread.currentThread().interrupt();                }            }        } catch (Exception ex) {            log.error("lock error", ex);        }        return null;    }

    public boolean release(String value) {        if (value == null) {            return false;        }        // 判断key存在并且删除key必须是一个原子操作        // 且谁拥有锁,谁释放        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";        Object result = new Object();        try {            result = jedis.eval(script, Collections.singletonList(key),                    Collections.singletonList(value));            if (RELEASE_SUCCESS.equals(result)) {                log.info("release lock success, value:{}", value);                return true;            }        } catch (Exception e) {            log.error("release lock error", e);        } finally {            if (jedis != null) {                jedis.close();            }        }        log.info("release lock failed, value:{}, result:{}", value, result);        return false;    }}

单是一个redis的分布式锁就有这么多道道,不知道你是否看明白了?留言讨论下吧!

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

service层加需要加锁吗_Redis分布式锁,你真的用对了吗?相关推荐

  1. service层加需要加锁吗_面试官:了解乐观锁和悲观锁吗?

    为什么会使用到数据库级别的锁? 你可能会有这么一个疑问:现在的程序已经提供了很完善的锁机制来保证多线程的安全问题,还需要用到数据库级别的锁吗?我觉得还是需要的,为什么呢?理由很简单,我们再编程中使用的 ...

  2. redis分布式锁实现原理_redis分布式锁实现分析与实践

    前言: 在分布式环境中, 我们有些情况下需要使用到锁进行并发控制, 可供基于的 redis, zookeeper,mysql类数据库 基于数据库类的实现是乐观锁, 基于redis,zookeeper的 ...

  3. **Java有哪些悲观锁的实现_Redis 分布式锁的正确实现方式(Java版)

    前言 分布式锁一般有三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁 本篇博客将介绍第二种方式,基于Redis实现分布式锁. 虽然网上已经有各种介绍Redi ...

  4. redis 分布式锁 看门狗_redis分布式锁原理及实现

    一.写在前面 现在面试,一般都会聊聊分布式系统这块的东西.通常面试官都会从服务框架(Spring Cloud.Dubbo)聊起,一路聊到分布式事务.分布式锁.ZooKeeper等知识. 所以咱们这篇文 ...

  5. **Java有哪些悲观锁的实现_Redis 分布式锁的正确实现方式(Java 版)

    点击上方"Java基基",选择"设为星标" 做积极的人,而不是积极废人! 源码精品专栏 原创 | Java 2020 超神之路,很肝~ 中文详细注释的开源项目 ...

  6. redis 失效时间单位是秒还是毫秒_redis分布式锁的这些坑,我怀疑你是假的开发...

    摘要:用锁遇到过哪些问题? 一.白话分布式 什么是分布式,用最简单的话来说,就是为了较低单个服务器的压力,将功能分布在不同的机器上面:就比如: 本来一个程序员可以完成一个项目:需求->设计-&g ...

  7. redis mysql 解决超卖_Redis 分布式锁解决超卖问题

    Redis 分布式锁解决超卖问题 1,Redis 事物介绍 1. Redis 事物是可以一次执行多个命令, 本质是一组命令的集合. 2. 一个事务中的所有命令都会序列化, 按顺序串行化的执行而不会被其 ...

  8. 一个项目部署多个节点会导致锁失效么_Redis分布式锁

    分布式锁在很多场景中是非常有用的原语, 不同的进程必须以独占资源的方式实现资源共享就是一个典型的例子. 有很多分布式锁的库和描述怎么实现分布式锁管理器(DLM)的博客,但是每个库的实现方式都不太一样, ...

  9. redis 3.0 java 工具包_redis分布式锁工具包,提供纯Java方式调用

    redis-distributed-lock redis分布式锁工具包,提供纯Java方式调用,支持传统Spring工程, 为spring boot应用提供了starter,更方便快捷的调用. 项目结 ...

最新文章

  1. LeetCode Integer Replacement
  2. java代码上传到私服,java生成 java代码 上传maven私服
  3. 【收藏】生产订单业务流程
  4. OpenCV:在imshow() 之前使用namedWindow() 的必要性讨论?
  5. 搭建Maven私服那点事
  6. 路考计算机系统评判,科目三智能考试有效解决路考舞弊行为
  7. elementary os(ubuntu)开启ipv6 与走SwitchyOmega代理
  8. FormData对象
  9. Makefile:Makefile中的调试打印方法
  10. MTK手机平台充电原理
  11. Flash版Logo语言9.83
  12. ucore实验---Lab1
  13. 空间索引之网格与四叉树
  14. c++正则表达式regex_match和regex_seach使用
  15. 中文维基百科数据爬取与预处理
  16. java excel 数组公式_Apache poi中的数组公式
  17. python怎么读excel_python怎么读写excel文件
  18. 计算机一级ppt定位,课件全国计算机一级基础知识.ppt
  19. ICP波长及分析校正
  20. gephi软件_类似gephi的软件

热门文章

  1. 0寄存器与arm_如何在ARM下进行高效的C编程?
  2. 计算机领域中的牛人,计算机视觉领域的牛人 | 丕子
  3. git使用变基方式同步远程和本地副本的代码同步方式
  4. python动态规划组合数最大_编写用动态规划法求组合数()的算法。
  5. vue防抖和节流是什么_防抖和节流为什么重要!!!
  6. qtplaintextedit如何删除内容_(mac常见问题)如何删除 Mac 储存空间的其他选项?...
  7. 8086汇编4位bcd码_逆向工程——汇编基础[一]
  8. python如何离线安装第三方库_离线环境安装python第三方库
  9. python梯度下降法实现线性回归_【机器学习】线性回归——多变量向量化梯度下降算法实现(Python版)...
  10. keil5函数 默认返回值_C++的返回值return