记一次重大事故 -- 非线程安全框架引发的意外数据共享

2015-09-23 12:18:31   最后更新: 2016-08-05 17:10:41   访问数量:1783




2015-09-21 15:43:34 到 2015-09-21 16:13:45 之间用户通过客户端 APP 查看“我的订单”列表会查看到同时登陆的其他用户订单

2015-09-21 16:13:45 到 2015-09-21 16:36:17 之间用户无法查看客户端 APP 查看“我的订单”列表页面

 

spring 框架的 bean 默认以单例模式(这里并不是指 GOF 设计模式中的单例模式,而是在 spring 的 IOC 容器中只会存在一个该 bean 创建的对象)启动,这个类对象的成员数据共享,因此用户的请求数据被共享,后到的 request 覆盖了已经存在的 request 数据,等待的用户返回了后到的用户查询到的结果

 

测试过程中可以避免这个问题的发生吗?测试没有模拟并发的问题,这个问题是无法出现的,这也是事故发生后一个多小时无法定位到的主要原因 -- 不能在测试环境中复现

然而,小流量上线显然是可以触发的,然而,在小流量上线后仅仅对返回结果进行了简单的核对,没有持续观察输出是否稳定,因此刚好没有触发到问题,如果小流量部署后可以持续观察一段时间是可以看到这个问题的

 

当一个 bean 的作用域设置为 singleton, 那么Spring IOC容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回 bean 的同一实例

换句话说,只要把 bean 设置为 singleton 以后,在一次生命周期中,只会创建一个类对象实例,所有的请求都使用这个类对象,也因此共享了类对象的成员数据,也就造成了这次事故的发生

在配置 bean 时需要加上属性 scope=”prototype” 或 singleton=”false” 来让他们成为非单例的实例

 

在 360 的时候,事业部老大说过一句话:做人做事还是应该有一个“战战兢兢如履薄冰”的态度的

在上线过程中,是有有密切关注接口返回的,并且发现了异常,原本有 288 个订单数据的账号在刷新过程中有时返回十几个订单有时返回三到五个订单,一时麻痹大意认为是上线还没有完成造成的返回不稳定,这时,接到反馈有人刷到了别人的订单,才立即停止发布,开始回滚

事实上处理是比较及时的,但是由于部署系统的串行执行,使整个处理时间长达近一个小时,这是亟待解决的“灾后重建”工作之一

 

事故的主要原因是对 spring 框架不熟悉,毕竟作为一个 php 程序员,接手 java spring 框架的项目不足两个月,对这些问题完全没有任何先见意识

无论是同步多进程模型的 apache 还是异步非阻塞模型的 nginx,他们在处理 php 请求的时候都是让每个请求被单独的进程处理(apache worker,nginx 将请求投递给 php-fpm),进程与进程之间完全隔离,因此不会在高并发时出现问题

而 java spring 框架实现的 IOC 容器是使用线程模型来处理所有的请求的,所有的请求都运行在相同的进程地址空间中,他们相互之间是可以共享进程地址空间中的数据的,这一点是始料未及的,未来还是要多看一下 java,毕竟只在大学的时候学过 java 的语法,对于 java 的特性、spring 框架的特性等等都知之甚少

 

所幸的是,下午三点处于非高峰期时段,实际问题持续 30 分钟,最终看到了十几个微博在吐槽这个事情并截了图,当时有大量的用户打客服电话,后续解决后并没有造成重大的经济损失,还算是一个比较圆满的结果吧

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤

 






技术帖      php      技术分享      work      工作      server      java      事故      spring      ioc      单例模式      prototype      scope      singleton      casestudy     


京ICP备2021035038号