当前位置: 首页 > 后端技术 > Java

记一次ThreadLocal造成的线上失败,年终奖没了,还可能面临解雇

时间:2023-04-01 21:50:53 Java

写下惨痛的线上ThreadLocal失败,3个月的年终奖没了,还可能面临解雇.事情的起因是老鼠逗猫——我没什么好找的前几天,在工作还不算太忙的时候,为了表现自己工作积极主动,有很强的技术过硬,给领导留下了好印象,我看了下项目代码有没有优化的空间。没想到真的让我找到了。诅咒就是这么种下的!有用户反映查询订单列表的界面有点慢,我就把每一步的耗时信息打印出来。在查找订单查询之前,需要根据用户ID查询用户信息,查询用户信息的接口需要调用用户团队提供的服务。有时网络慢的时候,最多需要200毫秒。逐层调用查询订单接口时,多次调用用户信息查询接口。当然也可以改成在顶层查询一次,然后一层层往下传。这样改的地方很多,很麻烦。我在想能不能加一个本地缓存来缓存用户信息,这样就不用每次都去调用用户服务查询了。我只是想到了使用ThreadLocal。听说高级程序员都用ThreadLocal。我也想试试。ThreadLocal是线程私有的。调用结束后线程被销毁,ThreadLocal中的数据也没有了。监听ThreadLocal是线程安全的,应该没有问题。为了动手实践,我先写了一个ThreadLocal工具类来存储和获取用户信息:staticfinalThreadLocalthreadLocal=newThreadLocal<>();/***获取用户信息*/publicstaticUsergetUser(){//如果ThreadLocal中没有用户信息,则从request中解析出来放到里面if(threadLocal.get()==null){threadLocal.设置(UserUtil.parseUserFromRequest());}返回threadLocal.get();}}然后在订单查询界面,调用这个工具类的方法获取用户信息,最后根据用户信息查询订单信息,完美。/***获取订单列表方法*/publicListgetOrderList(){//1.从ThreadLocal缓存中获取用户信息Useruser=ThreadLocalUtil.getUser();//2.根据用户信息调用用户服务获取订单列表returnorderService.getOrderList(user);}自测,测试,验收,上线,界面访问速度立马上去,一切看起来都那么完美。我已经开始幻想升职加薪,嫁给白富美,走上人生巅峰。上线一小时后,值班群炸了。陆续有用户反映,刚刚下的订单不见了,也有用户反映,他们的订单列表莫名多了一些订单。我惊呆了,我从来没有遇到过这样的情况,渐渐地反馈的用户越来越多,我也不知所措了。领导当机立断,小邓,你搞什么飞机,赶紧回滚服务。半小时后,回滚完成,用户的情绪也逐渐平复。在故障恢复线上解决故障后,会立即排查问题原因。经过无数次的logging和debug,终于定位到问题所在。ThreadLocal确实是线程私有的,线程销毁后,ThreadLocal中的数据也会被清除。但是问题是不管我们的服务器是使用Tomcat、Jetty、SpringBoot、Dubbo等,我们都不会为一个请求创建一个线程,而是创建一个线程池,所有的请求共享这个线程池中的线程。.线程在处理请求后不会被销毁。可能会导致多个用户请求共享一个线程,最后出现数据违规,看到其他用户的订单。解决方案解决方案是使用ThreadLocal后调用remove方法清除ThreadLocal数据。/***@author一灯*@apiNote在本地缓存用户信息**/publicclassThreadLocalUtil{//使用ThreadLocal存储用户信息privatestaticfinalThreadLocalthreadLocal=newThreadLocal<>();/***获取用户信息*/publicstaticUsergetUser(){//如果ThreadLocal中没有用户信息,则从请求中解析,放入if(threadLocal.get()==null){threadLocal.设置(UserUtil.parseUserFromRequest());}返回threadLocal.get();}/***删除用户信息*/publicstaticvoidremoveUser(){threadLocal.remove();}}使用try/catch包裹业务代码,最后在finally中清除ThreadLocal数据。/***获取订单列表*/publicListgetOrderList(){//1.从ThreadLocal缓存中获取用户信息Useruser=ThreadLocalUtil.getUser();//2.根据用户信息调用用户服务获取订单列表try{returnorderService.getOrderList(user);}catch(Exceptione){thrownewRuntimeException(e.getMessage());}finally{//3.使用ThreadLocal后,删除用户信息ThreadLocalUtil.removeUser();}returnnull;}如果故障评分影响超过10万用户,或者错误数据超过10万,或者资金损失大于10万,则故障评分为P1,年度业绩为C。本来想优化提高程序性能,提高访问速度,给领导留下好印象,使自己显得技术能力强,工作积极主动。现在,不仅年终奖没了,工作也可能丢了。我睡觉的时候没有捂屁股——我露出了脸!事故总结经过这次事故,我总结出以下教训:闲着不要盲目行动。没有钻石就不要做瓷器。不求功,不求过错。登子,重构优化的水太深了,你把握不住。文章持续更新中,大家可以在微信搜索“一光架构”第一时间阅读更多技术干货。