当前位置: 首页 > news >正文

记一次ThreadLocal导致的生产事故

1.背景

前段时间发生了一起偶现生产事故,客服查询用户的订单列表时发现多出来了很多不应对其展示的订单。后经排查是由于ThreadLocal使用不当造成的。

2.过程

改动前

大致伪代码如下:

ThreadLocal<Boolean> IS_HIGHVENDER = ThreadLocal.withInitial(() -> false);Set<String> v_set = new HashSet<>();// set里面有一些指定的 vString v = "xx";//客服自身的某属性if(v_set.contains(v)){IS_HIGHVENDER.set(true);}try {List orderList = new ArrayList(); // 查出来的订单列表boolean other = false;// 其他过滤条件if(!IS_HIGHVENDER.get() || other ){orderList = new ArrayList(); // 对订单列表有一些过滤逻辑}}finally {IS_HIGHVENDER.remove();}return orderList;

改动后

大致伪代码:

ThreadLocal<Boolean> IS_HIGHVENDER = ThreadLocal.withInitial(() -> false);Set<String> v_set = new HashSet<>();// set里面有一些指定的 vString v = "xx";//客服自身的某属性if(v_set.contains(v)){IS_HIGHVENDER.set(true);}try {Callable callable1 = new Callable() {@Overridepublic Object call() throws Exception {try {List orderList = new ArrayList(); // 查出来的订单列表boolean other = false;// 其他过滤条件if (!IS_HIGHVENDER.get() || other) {orderList = new ArrayList(); // 对订单列表有一些过滤逻辑}return orderList;}finally {IS_HIGHVENDER.remove();}}};Callable callable2 = new Callable() {@Overridepublic Object call() throws Exception {// 一些其他业务return null;}};// 把两个任务丢线程池去执行了// 得到两个任务的结果后进行一些业务处理,return orderList; // 返回任务1中的订单列表}

分析

改动前是直接在一个线程中执行该业务逻辑,完事儿后在finally 中调用ThreadLocal的remove方法。
改动后因为增加了其他的业务逻辑,将原查询订单列表的逻辑与新增的其他逻辑分成两个异步任务进行处理,在子任务1中任务完成后有ThreadLocal的remove方法调用,但是把原来在外层的finally 中的remove调用给干掉了。

  1. 使用的ThreadLocal,子线程不会自动传递该变量;所以改动后的子任务1中,IS_HIGHVENDER.get()始终为初始值false;
  2. 子任务内的remove只会影响到子线程内的变量,不会影响到外层的ThreadLocal;
  3. 外层的remove去掉后,外层的ThreadLocal中的值只能通过判断符合v_set.contains(v)条件才会更改,不然就保持是之前的值。

3.回顾

  1. 记录的时候离事发已经过去一个多月了,上面的伪代码是真的伪,只能按大概记忆编一下了,不保真。
  2. 这个业务改动前不是我写的,不清楚之前的逻辑;改动后也不是我写的,也不清楚改动后的逻辑,所以有点儿麻烦。
  3. 收到事故反馈时,前两天才有过上线,就是上线的这个改动,所以排查范围一下就缩小了。
  4. 事故现象是查到了不该查到的订单,近一步缩小范围到过滤逻辑上。
  5. 其他过滤条件都是针对订单本身某些属性做的判断,可能性不大。
  6. 一番排除下来这个ThreadLocal变量嫌疑就比较大了。
  7. 使用的是ThreadLocal,不是如InheritableThreadLocal这种可在线程间复制传递的,这好像并不是主因。
  8. 又发现IS_HIGHVENDER这个变量,只有一处调了set方法,一处调了remove方法,多处调了get方法。这个只有一处remove就感觉有点怪了,毕竟现在两个线程中都用到了。
  9. 外层的remove被干掉了,没有清除它的值,这可能性是极大的。

4.终

最初为啥要使用 ThreadLocal来存储这个变量已经不可考了,看起来貌似没太大必要使用ThreadLocal。
最终找人修复后,看了下修复结果:去除了该ThreadLocal变量,直接 boolean isHighvender = v_set.contains(v); 将isHighvender 变量传递下去进行过滤逻辑判断。

http://www.dtcms.com/a/544701.html

相关文章:

  • Rust 入门基础:安全、并发与高性能的系统编程语言
  • PyCharm + 远程调试路径映射总结(以 diffusers 为例)
  • HTML常用特殊字符
  • 手机网站设计公司哪家好保定网站设计
  • 网站建设焦作合肥做网站的的公司有哪些
  • Rust HashSet 与 BTreeSet深度剖析
  • Java二分算法题目练习
  • AI工具赋能需求管理 Jira
  • PostgreSQL 六大索引
  • 2025年--Lc224--100. 相同的树(递归,dfs,带测试用例)-Java版
  • Python打造美观的桌面温馨提醒弹窗
  • 北京网站制作建设太原it培训机构
  • certbot+shell+阿里云api+k8s实现自动化更新SSL证书
  • Linux小课堂: 系统核心技能与应用总结与进阶指南
  • 前端vue项目在vscode使用插件部署到服服务器的方法
  • 使用Labelimg进行图像标注
  • 【计算机软件资格考试】软考案例分析题及解析模拟题10
  • IoTDA应用侧app开发403报错解决方案
  • 3.1 Lua代码中的元表与元方法
  • Rust——多重借用的冲突解决方案:驾驭Rust借用检查器的艺术
  • kaggle比赛与常用的dash board 3lc
  • 适配器模式:让不兼容的接口协同工作
  • Neo4j中导入.owl数据
  • 应急救援 “眼观六路”:SA/NSA 双模覆盖,偏远灾区也能实时传视频
  • 站长工具短链接生成网站中队人物介绍怎么做
  • 【Spring Boot + Spring Security】从入门到源码精通:藏经阁权限设计与过滤器链深度解析
  • 《嵌入式硬件(十七):基于IMX6ULL的温度传感器LM75a操作》
  • 用 Go 手搓一个内网 DNS 服务器:从此告别 IP 地址,用域名畅游家庭网络!
  • Rust async/await 语法糖的展开原理:从表象到本质
  • Rust 零拷贝技术:从所有权到系统调用的性能优化之道