别再随意说 Redis 的 SET 保障原子性,在客户端不一定

news2024/11/25 14:39:42

分布式系统有一个特点,就是无论你学习积累多少知识点,只要在分布式的战线中,总能遇到各种超出主观意识的神奇问题。比如前文使用Jedis来实现分布式锁的技术知识点储备,本以为很稳不会再遇到什么问题,但实际情况却是啪啪打脸。

二、技术背景同步

为了照顾一些同学不喜欢看连载,这里就必须把上下文再粘贴过来,否则内容不连贯,看起来不流畅。

2.1 如何使用 SET 指令来加锁

我们使用的是 SET 指令来实现加锁的逻辑,指令形式如下:

SET键值[NX | XX] [GET] [EX 秒 | PX 毫秒 |  EXAT unix 时间秒 | PXAT unix 时间毫秒 | 保持]
复制代码

1)加锁成功的逻辑是这样:

  1. 判断 key 是否存在
  2. 若 key 不存在,就设置 key
  3. 给 key 指定过期时间

2)加锁不成功的逻辑是这样:

  1. 判断 key 是否存在
  2. 若 key 已存在,则返回
SetParams params = SetParams.setParams().nx().ex(lockState.getLeaseTTL());
String result = client.set(lockState.getLockKey(), lockState.getLockValue(), params);
复制代码

上边代码是之前《分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇》中写的加锁逻辑,其中只根据正常加锁的返回值来判断是否加锁成功,即 result 是不是 "OK",但 key 已存在导致加锁不成功的返回值到底是什么,应该如何判断呢?

2.2 SET 的返回值都有什么

在官网中,查看 SET 返回值的描述,为方便大家,这里直接贴出结果,应该很多同学都没看过这段描述吧。

简单字符串回复:OK如果SET正确执行。

空回复:(nil)如果SET由于用户指定了NXXX选项但不满足条件而未执行操作。

如果命令与GET选项一起发出,则上述内容不适用。它会改为如下回复,无论是否SET实际执行:

批量字符串回复:存储在键中的旧字符串值。

空回复:(nil)如果密钥不存在。

2.3 SET 指令加锁的结论

通过官网给出的描述可以得知,当前 SET 指令的使用方式,只要返回的不是“OK",就是锁已存在了,所以将 《分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇》示例中tryLock的逻辑中,加入一个判断锁类型的逻辑即可,即如果锁 key 已存在,并且锁是”一次性“锁,则不循环等待而是立即返回。

2.4 无情的现实

使用 Jedis 客户端来实现分布式锁功能的时候,我们发现并确认了,从客户端用户的视角来看 SET 指令的原子性语义并不一定能得到保障。

三、诊断过程

1) 用户反馈,偶发一次防重入锁的加锁失败了

从日志的结果看,与这个 key 相关的加锁日志中,只有SET返回空,即 key 已存在的信息。

是不是有其他的程序也可以加锁,比如人工在 Redis 里设置了 key 或 还有其他的实例也在运行?

经确认,没有人工设置 key 的现象,整个程序在测试环境中只有1个实例,没有其他实例

2)没有足够的可观测信息,的确是看不出来哪里有问题

用 SkyWalking 中 @Trace的方法 通过 Trace 以及 Tag 来记录几个怀疑点: 1. 从用户请求进入到结束,加锁 SET 指令执行了几次 2. SET 不成功的时候,返回的结果到底是OK 还是 空 3. 如果 SET 返回的是空, 通过 GET 查询一下,记录其 value,可以判断跟加锁时的 value 是否一致

3)用户反馈,又出现了

我:通过 TraceId 信息查看 Trace,越不相信什么越呈现什么:

  • 只有一次有效的SET指令
  • SET 返回的是空
  • GET 返回有结果,并且 value 是 SET 指定的 value
  • SET 的耗时也不算太长,是208ms

4) 难道 SET 指令 并非官网所讲的效果,有什么坑?

通过直观的 Trace 信息,不再怀疑上层加锁逻辑和应用程序的逻辑,而把 Jedis 客户端和定位成最大怀疑对象,但一次现象还是缺少一些研判的依据,再复现一下找一找规律,甚至也怀疑 Reids 服务端

5) 规律出现了,耗时偏长

问题再次出现,通过 Trace信息来对比出问题的 SET 与 无问题的 SET 表现出了哪些差异,很快一个显著的特征被找了出来,出问题的 SET 指令的执行耗时 都在 200ms 以上,而没问题的 SET 的耗时 都在20ms 以下。

6)200ms 是什么?

通过排查发现,Jedis客户端几个超时时间设置的是 200ms ,莫非是哪个环节的超时导致了问题?

7)调试源码

从下边的调用堆栈,你是不是也发现一个单词挺让人生疑?没错runWithRetries,它会重试。

execute:112, JedisCluster$2 (redis.clients.jedis)
execute:109, JedisCluster$2 (redis.clients.jedis)
runWithRetries:120, JedisClusterCommand (redis.clients.jedis)//》这里
run:31, JedisClusterCommand (redis.clients.jedis)
set:109, JedisCluster (redis.clients.jedis)
复制代码

8)再看一看那几个超时时间都是什么意思

public BinaryJedisCluster(Set<HostAndPort> jedisClusterNode, int connectionTimeout, int soTimeout, int maxAttempts, String password, GenericObjectPoolConfig poolConfig) {
  this.connectionHandler = new JedisSlotBasedConnectionHandler(jedisClusterNode, poolConfig,
          connectionTimeout, soTimeout, password);
  this.maxAttempts = maxAttempts;
}
复制代码

构造函数里,能看到 几个关键参数的信息:

  • connectionTimeout = 200
  • soTimeout = 200
  • maxAttempts = 3

9)分析 connectionTimeout

这是建连的耗时,推理一下,如果200ms都没连接上,那么200ms后会有第二次连接,连接成功后,再发指令。

这种情况下应该发一次指令就够了。

10)分析 soTimeout

soTimeout 指定给了 socket。

public void connect() {
  if (!isConnected()) {
    try {
      socket = new Socket();
      ...
      socket.connect(new InetSocketAddress(host, port), connectionTimeout);
      socket.setSoTimeout(soTimeout);//在这里
复制代码

看权威解释:

Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout.

结合JDK注释解释一下本次遇到的情况:

通过socket.setSoTimeout(int timeout)方法设置,socket 关联的InputStreamread()方法会阻塞,直到超过设置的soTimeout,就会抛出SocketTimeoutException。当不设置这个参数时,默认值为无穷大,即InputStreamread()方法会一直阻塞下去,除非连接断开。

但重试逻辑内部把异常吞掉了,并重新发出执行指令的请求。

11)所以是重试 + soTimeout的问题

模拟一个场景方便理解:

  1. 0ms 客户端发出第一个 SET 的指令
  2. 30ms 服务端收到第一个 SET 指令,存储后给客户端响应说第一个SET 成功,但响应返回的有点慢
  3. 200ms 客户端仍未收到 服务端的响应,出现了超时异常,捕获后,发起重试
  4. 201ms 客户端开始重试,发出第二个SET 的指令
  5. 202ms 服务端给第一个SET的响应到了,但客户端不关心了
  6. 204ms 服务端收到第二个 SET 指令,判断发现 key 已存在,给客户端响应说第二个 SET 失败
  7. 208ms 客户端收到 服务端第二个 SET 失败的响应。
  8. 而对于Client端最上层的 SET 使用者来说,效果是SET 失败了,但key 设置成功了。

四、如何避免

既然是重试+超时时间引发的,那么可以从此特性出发,将其配置的值进行调整,比如:

  1. soTimeout设置的足够大
  2. 取消掉Jedis内部重试

但这两个参数既然能暴露给我们使用,那么他们必然有其很重要的价值,这两种方法都只是尝试去避免问题,但并不能根治。

我们既需要这些核心能力,又要避免遇到这类破坏原子性语义的问题。读者朋友,您有没有什么好的办法来解决呢?

五、最后说一句

我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android ASM

文章目录逆波兰表达式与字节码的关系中缀表达式转换为逆波兰表达式&#xff08;后缀表达式&#xff09;的过程逆波兰表达式求值过程ASM 的使用ASM 常用 api 说明ClassWriter构造函数传参 flags 的作用定义类的属性&#xff1a;visit()定义类的方法&#xff1a;visitMethod()定义…

原生小程序canvas生成图片、保存到本地

今天在视频中看到一个跳动的小球的效果&#xff0c;感觉挺好玩的。于是自己也实现了一个&#xff0c;感觉还是好玩&#xff0c;就想来分享一番&#xff1b;小伙伴们可以来看一下。这次主要为大家玩一下radial-gradient和动画阴影的调试。 效果呈上 代码来了 大家可以先仔细阅…

Docker安装(图文教程)

一、Docker简介 1、Docker是什么 &#xff08;1&#xff09;Docker是一种虚拟化容器技术。Docker基于镜像&#xff0c;可以秒级启动各种容器。每一种容器都是一个完整的运行环境&#xff0c;容器之间互相隔离。&#xff08;2&#xff09;在Docker的官方&#xff0c;提供了很多容…

Java培训之Nginx启动

1. Nginx启动 启动问题 进入/usr/local/nginx/sbin目录&#xff0c;运行命令./nginx 即可启动nginx nginx无法启动: libpcre.so.1/libpcre.so.0: cannot open shared object file解决办法 Java培训之Nginx启动 解决方法&#xff1a; ln -s /usr/local/lib/libpcre.so.1 /l…

web前端网页设计期末课程大作业:HTML旅游网页主题网站设计——酒店主题网站设计—酒店阳光温馨网站(5页)HTML+CSS+JavaScript

&#x1f468;‍&#x1f393;学生HTML静态网页基础水平制作&#x1f469;‍&#x1f393;&#xff0c;页面排版干净简洁。使用HTMLCSS页面布局设计,web大学生网页设计作业源码&#xff0c;这是一个不错的旅游网页制作&#xff0c;画面精明&#xff0c;排版整洁&#xff0c;内容…

[附源码]Nodejs计算机毕业设计基于的汉服服装租赁系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

32-Vue之ECharts-雷达图

ECharts-雷达图前言雷达图特点雷达图的基本实现雷达图的常见效果显示数值区域面积绘制类型完整代码前言 本篇来学习写雷达图 雷达图特点 可以用来分析多个维度的数据与标准数据的对比情况 雷达图的基本实现 ECharts 最基本的代码结构定义各个维度的最大值准备具体产品的数…

Python编程|手把手教植物大战僵尸,代码开源

前言 如题&#xff0c;手把手教Python实现植物大战僵尸游戏&#xff0c;代码简单易学&#xff0c;无需额外安装Python包&#xff0c;只要有pygame即可&#xff0c;文末获取全部素材及源代码~ 视频演示效果&#xff1a;https://www.bilibili.com/video/BV1cG411u755/?spm_id_…

并发编程之深入理解ReentrantLock和AQS原理

AQS&#xff08;AbstractQueuedSynchronizer&#xff09;在并发编程中占有很重要的地位&#xff0c;可能很多人在平时的开发中并没有看到过它的身影&#xff0c;但是当我们有看过concurrent包一些JDK并发编程的源码的时候&#xff0c;就会发现很多地方都使用了AQS&#xff0c;今…

(文章复现)6计及源荷不确定性的电力系统优化调度(MATLAB程序)

目录 参考文章&#xff1a; 代码主要内容&#xff1a; 主程序&#xff1a; 结果图&#xff1a; 参考文章&#xff1a; 考虑源荷两侧不确定性的含风电电力系统低碳调度——崔杨&#xff08;2020&#xff09; 代码主要内容&#xff1a; 参照考虑源荷两侧不确定性的含风电的…

JAVA基础讲义06-面向对象

面向对象一、编程思想什么是编程思想面向过程和面向对象面向过程编程思想面向过程思想面向过程实现应用场景面向过程特点面向过程代表语言面向对象介绍面向对象编程思想面向对象的三大特征面向对象思想总结什么是编程面向对象分析方法分析问题的思路和步骤二、类和对象类类的概…

它破解了AI作画的中文语料难题,AIGC模型讲解(以世界杯足球为例)

目录1 扩散模型与AI绘画2 中文语料的挑战3 昆仑天工&#xff1a;AIGC新思路3.1 主要特色3.2 模型蒸馏3.3 编解码与GPT3.4 stable-diffusion3.5 性能指标4 体验中文AI绘画模型5 展望1 扩散模型与AI绘画 AI绘画发展历史始于20世纪60年代&#xff0c;当时人工智能研究者们尝试使用…

springboot启动流程源码分析

一、引入思考的问题 1、springboot未出现之前&#xff0c;我们在在spring项目中如果要使用数据源&#xff08;比如我们使用druid&#xff09;&#xff0c;需要做哪些事情呢&#xff1f; &#xff08;1&#xff09;引入druid的jar包 &#xff08;2&#xff09;配置数据源的参…

微服务调用工具

微服务调用工具目录概述需求&#xff1a;设计思路实现思路分析1.A2.B3.C参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,challenge Survive…

Postman API测试工具 - 初认知 基本使用(一)

Postman - API测试工具 初认知&#xff08;一&#xff09; 文章目录Postman - API测试工具 初认知&#xff08;一&#xff09;一、什么是Postman&#xff1f;二、如何下载Postman&#xff1f;三、Postman的使用四、处理GET请求&#xff1a;五、处理POST请求总结一、什么是Postm…

Python 缩进语法的起源:上世纪 60-70 年代的大胆创意

上个月&#xff0c;Python 之父 Guido van Rossum 在推特上转发了一篇文章《The Origins of Python》&#xff0c;引起了我的强烈兴趣。 众所周知&#xff0c;Guido 在 1989 年圣诞节期间开始创造 Python&#xff0c;当时他就职于荷兰数学和计算机科学研究学会&#xff08;简称…

MySQL之聚合查询和联合查询

一、聚合查询&#xff08;行与行之间的计算&#xff09; 1.常见的聚合函数有&#xff1a; 函数 说明 count 查询到的数据的数量 sum 查询到的数据的总和&#xff08;针对数值&#xff0c;否则无意义&#xff09; avg 查询到的数据的平均值&#xff08;针对数值&#xf…

北京智和信通 | 无人值守的IDC机房动环综合监控运维

随着信息技术的发展和全面应用&#xff0c;数据中心机房已成为各大企事业单位维持业务正常运营的重要组成部分&#xff0c;网络设备、系统、业务应用数量与日俱增&#xff0c;规模逐渐扩大&#xff0c;一旦机房内的设备出现故障&#xff0c;将对数据处理、传输、存储以及整个业…

极光笔记 | 以静制动:行为触发营销助力用户转化

01、营销人&#xff0c;你是否饱受困扰&#xff1f; 作为营销人的你&#xff0c;从996到007&#xff0c;每天从早忙到晚&#xff0c;但还是没办法把访客转化成客户&#xff1f; 作为营销人的你&#xff0c;想通过APP通知、短信、邮件、公众号消息等方式&#xff0c;把所有能想…

牛客题霸sql入门篇之条件查询(二)

牛客题霸sql入门篇之条件查询(二) 2 基础操作符 2.1 查找学生是北大的学生信息 2.1.1 题目内容 2.1.2 示例代码 SELECT device_id,university FROM user_profile WHERE university北京大学2.1.3 运行结果 2.1.4 考察知识点 WHERE子句中可以写查询的条件,用于筛选出符合的…