文章来源:https://c1n.cn/OZvGN

目录

  • 背景

  • 问题分析

  • 解决方案

  • 总结

背景

Hollis的新书限时折扣中,一本深入讲解Java基础的干货笔记!

企微报警群里连续发出生产环境报错警告,报错核心信息如下:

redis setNX error java.lang.NumberFormatException: For input string: "null"at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)at java.lang.Long.parseLong(Long.java:589)at java.lang.Long.parseLong(Long.java:631)
......

经异常信息定位,发现是项目中自定义的 Redis 分布式锁报错,并且该异常是在最近需求上线后突然出现,并且伴随该异常出现的,还有需求涉及的业务数据出现部分错乱的问题。

问题分析

老规矩,先贴涉及代码:

//切面
public class RedisLockAspect{public void around(ProceedingJoinPoint pjp) {String key = "...";try {//阻塞,直到获取锁为止while (!JedisUtil.lock(key, timeOut)) {Thread.sleep(10);}//执行业务逻辑pjp.proceed();}finally {JedisUtil.unLock(key);}}
}

以上为自定义 Redis 分布式锁的切面,不看细节,只看整体逻辑,问题不大。

那再看实际加锁方法:

public class JedisUtil{public static boolean lock(String key, long timeOut){long currentTimeMillis = System.currentTimeMillis();long newExpireTime = currentTimeMillis + timeOut;RedisConnection connection = null;try {connection = getRedisTemplate().getConnectionFactory().getConnection();Boolean setNxResult = connection.setNX(key.getBytes(StandardCharsets.UTF_8), String.valueOf(newExpireTime).getBytes(StandardCharsets.UTF_8));//位置1if(setNxResult){expire(key,timeOut, TimeUnit.MILLISECONDS);return true;}//位置2Object objVal = getRedisTemplate().opsForValue().get(key);String currentValue  = String.valueOf(objVal);//位置3,异常位置为if判断中Long.parseLong(currentValue),currentValue为null的字符串if (currentValue != null && Long.parseLong(currentValue) < currentTimeMillis)  {String oldExpireTime = (String) getAndSet(key, String.valueOf(newExpireTime));if (oldExpireTime != null && oldExpireTime.equals(currentValue)) {return true;}}}return false;}public static void unLock(String key){getRedisTemplate().delete(key);}
}

有经验的大佬看到这段代码,估计会忍不住爆粗,但咱先不管,先看错误位置。

异常信息可以看出,currentValue 的值为字符串“null”,即 String.valueOf(objVal) 中的 objVal 对象为 null,也就是在 Redis 中,key 对应的 value 不存在。

此时思考一下,key 对应的 value 不存在,无非以下两种情况:

  • key 被主动删除

  • key 过期了

继续跟着代码往上走,发现前面执行了 setNx 命令,并且返回 setNxResult 表示是否成功。

正常来说,当 setNxResult 为 false 的时候,加锁失败,此时代码时不应该往下走的,但在本段代码中,却继续往下走!

问了下相关同事,说是为了做可重入锁......(弱弱吐槽下,可重入锁也不是这样干的啊...)

其实分析到这,已经可以知道是什么原因导致的异常故障了,即上面说的,key 被主动删除、key 过期导致。

下面假设有两个线程,对同一个 key 加锁,分别对应以上两种情况:

①key 被主动删除的情况,发生于分布式锁加锁逻辑执行完后,调用 unlock 方法,见以上 RedisLockAspect 类中 finally 部分,如下图:

②key 过期的情况,主要在线程加锁并设置过期时间后,执行业务代码耗费的时间超过设置的锁过期时间,并且在锁过期前,未对锁进行续期:

解决方案

从上面的代码看来,这已经不是简单的 Long.parseLong("null") 问题了,这是整个 Redis 分布式锁实现的问题。

并且该分布式锁在整个项目中大量使用,可想而知其实问题非常严重,如果只是解决 Long.parseLong("null") 的问题,无疑就是隔靴挠痒,没有任何意义的。

一般情况下,自定义 Redis 分布式锁容易出现以下几大问题:

  • setNx 锁释放问题

  • setNx Expire 原子性问题

  • 锁过期问题

  • 多线程释放锁问题

  • 可重入问题

  • 大量失败时自旋锁问题

  • 主从架构下锁数据同步问题

结合以上故障代码,可以发现项目中的 Redis 分布式锁实现几乎未对 Redis 分布式锁问题进行考虑。

以下为主要问题以及对应解决方案:

  • setNx 和 expire 原子操作:使用 Lua 脚本,在一次 Lua 脚本命令中,执行 setNx  与 expire 命令,保证原子性。

  • 锁过期问题:为防止锁自动过期,可在锁过期前,定时对锁过期时间进行续期。

  • 可重入问题:可重入设计粒度需到线程级别,可在锁上加上线程唯一 id。

  • 锁自旋问题:参考 JDK 中 AQS 设计,实现获取锁时最大等待时长。

对于项目中的问题以及每个问题的解决方案实现,baidu 一下就有大量参考,此处不再介绍。

目前比较成熟的综合解决方案为使用 Redisson 客户端,以下为简单伪代码 demo:

public class RedisLockAspect{@Autowiredprivate Redisson redisson;public void around(ProceedingJoinPoint pjp) {String key = "...";Long waitTime = 3000L;//获取锁RLock lock = redisson.getLock(key);boolean lockSuccess = false;try {//加锁设置超时时间,防止无限自旋。默认启用看门狗功能(自动对锁进行续期)lockSuccess = lock.tryLock(waitTime);//执行业务逻辑pjp.proceed();}finally {//解锁,防止释放其他线程锁if (lock.isLocked() && lock.isHeldByCurrentThread() && lockSuccess){lock.unlock();}}}
}

使用 Redisson 可以快速解决目前项目中 Redis 分布式锁存在的问题。除此之外,对于 Redis 主从架构下数据同步导致的锁问题,对应的解决方案 RedLock,也提供了相应的实现。

更多使用文档详见官方文档:

https://github.com/liulongbiao/redisson-doc-cn

总结

对于分布式锁来说,可实现方案其实远远不止 Redis 这个实现途径,比如基于 Zookeeper、基于 Etcd 等方案。

但其实对于目的来说,都是殊途同归,重点在于,如何安全、正确的使用这些方案,保证业务正常。

对于研发团队来说,针对类似的问题,需要对技术小伙伴进行培训,不断提升技术,更需要重视 codereview 工作,及时识别风险,避免发生故障造成严重损失(本次故障造成脏数据修复耗时一个多星期)。

敬畏技术,忠于业务。

我的新书《深入理解Java核心技术》已经上市了,上市后一直蝉联京东畅销榜中,目前正在6折优惠中,想要入手的朋友千万不要错过哦~长按二维码即可购买~

长按扫码享受6折优惠

往期推荐

为防止被00后整顿,一公司招聘要求员工不能起诉公司

4年工作经验,多线程间的5种通信方式都说不出来,你敢信?

社招两年半10个公司28轮面试面经

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

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

好文章,我在看❤️

Redis分布式锁故障,我忍不住想爆粗...相关推荐

  1. 记一次自定义 Redis 分布式锁导致的故障

    点击上方关注 "终端研发部" 设为"星标",和你一起掌握更多数据库知识 背景 企微报警群里连续发出生产环境报错警告,报错核心信息如下: redis setNX ...

  2. 记一次自定义Redis分布式锁导致的生产事件时间

    背景 企微报警群里连续发出生产环境报错警告,报错核心信息如下: redis setNX error java.lang.NumberFormatException: For input string: ...

  3. 记一次自定义Redis分布式锁导致的生产事件

    背景 企微报警群里连续发出生产环境报错警告,报错核心信息如下: redis setNX error java.lang.NumberFormatException: For input string: ...

  4. Redis 分布式锁使用不当,酿成一个重大事故,超卖了100瓶飞天茅台!!!

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 基于Redis使用分布式锁在当今已经不是什么新鲜事了. 本 ...

  5. 深度剖析:Redis分布式锁到底安全吗?看完这篇文章彻底懂了!

    ‍‍‍‍‍‍‍‍‍‍‍‍阅读本文大约需要 20 分钟. 大家好,我是 Kaito. 这篇文章我想和你聊一聊,关于 Redis 分布式锁的「安全性」问题. Redis 分布式锁的话题,很多文章已经写烂了 ...

  6. Redlock:Redis分布式锁最牛逼的实现

    普通实现 说道Redis分布式锁大部分人都会想到:setnx+lua,或者知道set key value px milliseconds nx.后一种方式的核心实现命令如下: - 获取锁(unique ...

  7. Redis分布式锁—SETNX+Lua脚本实现篇

    前言 平时的工作中,由于生产环境中的项目是需要部署在多台服务器中的,所以经常会面临解决分布式场景下数据一致性的问题,那么就需要引入分布式锁来解决这一问题. 针对分布式锁的实现,目前比较常用的就如下几种 ...

  8. 深度剖析:Redis 分布式锁到底安全吗?看完这篇文章彻底懂了!

    作者 | Kaito 来源 | 水滴与银弹 阅读本文大约需要 20 分钟. 大家好,我是 Kaito. 这篇文章我想和你聊一聊,关于 Redis 分布式锁的「安全性」问题. Redis 分布式锁的话题 ...

  9. redis 分布式锁的 5个坑,真是又大又深

    引言 最近项目上线的频率颇高,连着几天加班熬夜,身体有点吃不消精神也有些萎靡,无奈业务方催的紧,工期就在眼前只能硬着头皮上了.脑子浑浑噩噩的时候,写的就不能叫代码,可以直接叫做Bug.我就熬夜写了一个 ...

  10. 万字长文剖析Redis分布式锁到底安不安全

    ‍‍‍‍‍‍‍‍‍‍‍‍阅读本文大约需要 20 分钟. 这篇文章我想和你聊一聊,关于 Redis 分布式锁的「安全性」问题. Redis 分布式锁的话题,很多文章已经写烂了,我为什么还要写这篇文章呢? ...

最新文章

  1. Nginx配置段(3)
  2. win7网络适配器_Win7系统笔记本电脑连接蓝牙音箱的操作方法
  3. 学习SpringMVC——说说视图解析器
  4. 【转载】基于AFNetWorking3.0的图片缓存分析
  5. 这是我见过解释java内部类最详细的一篇文章了
  6. tensorflow调用问题解决
  7. linux源码_从linux源码看epoll及epoll实战揭秘
  8. 【转】Linux 移动或重命名文件/目录-mv 的10个实用例子
  9. 数据链路层之差错控制(检错编码和纠错编码)-(奇偶校验码、CRC循环冗余码、海明码)...
  10. LR监控linux系统资源
  11. 使用Docker镜像部署Coupons淘宝客项目
  12. matlab 矩阵逻辑与,MATLAB矩阵的寻访与赋值
  13. 使用百度 AI 进行智能写诗 智能春联
  14. 百练_1664:放苹果_递归
  15. 必须要知道的多媒体知识-音视频编解码-h265、h264-直播-点播
  16. 中科大计算机与华科,2021全国理工科大学排名!打破传统模式,华科第二,哈工大第五...
  17. ethtool查看网卡统计信息的流程
  18. 电脑护眼设置---保护你的眼睛(转载)
  19. Axure RP9——【图片放大预览效果】
  20. Spring的sessionFactory配置详解

热门文章

  1. springboot实现条形码_java生成条形码(多种条码类型生成)
  2. PowerDesigner关联表结构表示一对一或一对多
  3. 苹果手机如何分享wifi密码_WiFi密码破解器 v5.1.3手机版
  4. 一步一步SharePoint 2007之七:改变导航栏中项目的标题和内容
  5. 多元线性回归—异方差
  6. php元换成万元,元换算成万元(元与万元的换算器)
  7. matlab x对数坐标,matlab 对数坐标
  8. 如何经营好自己的朋友圈
  9. matlab怎么做线性插值,[MATLAB]领域插值和线性插值
  10. 360安全浏览器强制使用极速模式打开