我们的APP生产上出了一次比较严重的事故,许多用户投诉登录后能看到别人的信息,收到投诉后我们就开始查找问题,一般这样的问题都是线程安全引起的,所以查找原因的思路也是按线程安全的思路去查。

业务场景是这样的,用户登录后,点击一个页面查看信息,这个信息显示了别人的信息。

登录交易大致流程如下:

//一系列验证
session.setAttribute("id",id); //身份证号放入session
//其他操作

查看信息交易的流程如下:

session = request.getSession();
if(session != null && session.getAttribute("LoginStatus") == True) {
String id = session.getAttribute("id");
Information info = queryInfo(id);
return info;
} else {
return "not login";
}

通过加日志等方法,我们确认了是查看信息的时候,从session里拿出来的身份证号是其他人的,但是到底是在什么时候变化的,没找到,因为我们一直顺着线程安全的思路,找全局变量这样的地方。

另外还发现有个地方可疑,就是有一个异步的线程,会验证用户身份证,并且重新在session里放一次。

public void updateId(HttpServletRequest request) {
HttpSession session = request.getSession();
String id = validate();
session.setAttribute("id",id);
}

这个函数是在登录主交易调起的线程池处理的,看上去其实没有多大毛病,而且也是老的代码,很久了。而且我发现了一个规律,被别人看到信息的用户,登录交易都触发了使用新设备登录,因为我们加了一个逻辑,对换设备登录做了验证,这样的话需要验证短信,登录分两步了,第一步比较快的返回了,但是异步的更新信息流程还在。

于是我怀疑是不是因为Servlet的交易已经返回了,异步的线程虽然拿到了HttpServletRequest,但是这个request已经无效了或者被复用了。

我写了下面的代码进行验证,不停的用curl调用。Http请求很快就返回了,但是我会把HttpServletRequest传给一个线程池,等5s后才会去处理这个Request,结果果然是有问题的

结果果然有问题,大部分情况new_session都是null,但也出现了不是null的情况,这时候发现session不是自己的。



HttpServletRequest是有生命周期的,当一个http请求过来后,应用服务器解析报文,把各种参数放到一个HttpServletRequest对象中,然后传递给Servlet的service函数,service函数根据里面的方法调用对应的doGet/doPost等方法,而一旦service函数调用结束,HttpServletRequest的生命周期就结束了,再这之后你继续使用这个对象,产生的结果是不确定的。



网上遇到这类问题的人不多,我专门找了servlet specification,其中有一章讲HttpServletRequest生命周期的。



从中可以得到如下信息:

(1)三种情况下request有效:service函数内,doFilter函数内,startAsync起的异步线程

(2)在三种情况之外,使用request会产生不确定的结果(indeterminate results)

(3)大部分容器在实现servlet的时候,为了提高性能,会复用request对象,但这不是规范里必须的

其中提到的startAsync是servlet 3.0开始有的,它是为了让一个工作线程可以在做IO或类似阻塞线程的操作的时候能干其它的事情,但是它要求异步线程都结束了,才会将请求返回给客户端,本质上还是同步的,只是并行了。所以要想异步的处理Request,必须使用servlet自己的异步机制,但是这样并不能满足我们的需求,因为我们就是为了不让主线程等待。

用法示例:

如果使用了这个,那么客户端需要等待5s才能拿到结果。

我又看了tomcat的源码,发现它确实对Request做了复用:

虽然问题的原因很简单,但是产生的后果十分严重。需要异步处理数据的时候一定要特别小心,此处如果传Session就没问题了,但是还是要尽量避免。

异步处理ServletRequest引发的血案的更多相关文章

  1. [WCF]缺少一行代码引发的血案

    这是今天作项目支持的发现的一个关于WCF的问题,虽然最终我只是添加了一行代码就解决了这个问题,但是整个纠错过程是痛苦的,甚至最终发现这个问题都具有偶然性.具体来说,这是一个关于如何自动为服务接口(契约 ...

  2. dubbox微服务实例及引发的“血案”

    Dubbo 是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成. 主要核心部件: Remoting: 网络通信框架 ...

  3. Integer.parseInt 引发的血案

    Integer.parseInt 处理一个空字符串, 结果出错了, 程序没有注意到,搞了很久, 引发了血案啊!! 最后,终于 观察到了, 最后的部分: Caused by: java.lang.NoC ...

  4. Replication的犄角旮旯(六)-- 一个DDL引发的血案(上)(如何近似估算DDL操作进度)

    <Replication的犄角旮旯>系列导读 Replication的犄角旮旯(一)--变更订阅端表名的应用场景 Replication的犄角旮旯(二)--寻找订阅端丢失的记录 Repli ...

  5. Replication的犄角旮旯(七)-- 一个DDL引发的血案(下)(聊聊logreader的延迟)

    <Replication的犄角旮旯>系列导读 Replication的犄角旮旯(一)--变更订阅端表名的应用场景 Replication的犄角旮旯(二)--寻找订阅端丢失的记录 Repli ...

  6. 转:一个Sqrt函数引发的血案

    转自:http://www.cnblogs.com/pkuoliver/archive/2010/10/06/1844725.html 源码下载地址:http://diducoder.com/sotr ...

  7. 一个Sqrt函数引发的血案(转)

    作者: 码农1946  来源: 博客园  发布时间: 2013-10-09 11:37  阅读: 4556 次  推荐: 41   原文链接   [收藏]   好吧,我承认我标题党了,不过既然你来了, ...

  8. 一个字母引发的血案 java.io.File中mkdir()和mkdirs()

    一个字母引发的血案 明天开始放年假了,临放假前有个爬虫的任务,其中需要把网络图片保存到本地,很简单,马上写完了代码: //省略部分代码... Long fileId= (Long) data.get( ...

  9. form表单提交引发的血案

    最近,公司某条产品线上的一个功能出了问题:点击查询的时候,该页面在IE上直接卡死,chrome上会卡顿一段时间候提交表单进行查询.拿到这个bug单子以后,简单重现了下,基本上定位到是查询操作中的问题, ...

随机推荐

  1. lightoj 1049 - One Way Roads(dfs)

    Time Limit: 0.5 second(s) Memory Limit: 32 MB Nowadays the one-way traffic is introduced all over th ...

  2. Atcoder D - Black and White Tree(树dp+博弈)

    题目链接:http://agc014.contest.atcoder.jp/tasks/agc014_d 题意:有一棵树先手涂白色,后手涂黑色,直到不能再涂为止.涂完后再把所有黑色直接相邻的白色都变成 ...

  3. 天梯杯 PAT L2-001. 紧急救援 最短路变形

    作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图.在地图上显示有多个分散的城市和一些连接城市的快速道路.每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上.当其他城市有紧急求 ...

  4. @PathVariable性能损耗分析

    前端时间参与了一次业务线排障,是接口服务并发性能比较差,性能损耗大的问题,我经过几次研究分析和压测,确定了故障源是@PathVariable耗时过长引起的. @PathVariable使用形式: @R ...

  5. CentOS配置服务开机自启

    本例子以nginx作为举例: CentOS7中 以systemctl 替代了chkconfig 进行服务的管理, 默认在CentOS6 中控制服务开机自启,是在vim /etc/rc.d/rc.loc ...

  6. 讲解开源项目:让你成为灵魂画手的 JS 引擎:Zdog

    本文作者:HelloGitHub-kalifun HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...

  7. 2、顺序表的实现(java代码)

    1.这里实现了简单的顺序表的,为空判断.是否已满判断,插入.删除,查询元素下标等功能 public class Linear_List { private int[] arr; //用来保存数据 pr ...

  8. mybatis源码专题(2)--------一起来看下使用mybatis框架的insert语句的源码执行流程吧

    本文是作者原创,版权归作者所有.若要转载,请注明出处.本文以简单的insert语句为例 1.mybatis的底层是jdbc操作,我们先来回顾一下insert语句的执行流程,如下 执行完后,我们看下数据 ...

  9. 了解Java线程优先级,更要知道对应操作系统的优先级,不然会踩坑

    Java 多线程系列第 6 篇. 这篇我们来看看 Java 线程的优先级. Java 线程优先级 Thread 类中,使用如下属性来代表优先级. private int priority; 我们可以通 ...

  10. [DE] How to learn Big Data

    打开一瞧:50G的文件! emptystacks jobstacks jobtickets stackrequests worker 大数据加数据分析,需要以python+scikit,sql作为基础 ...