首页 > 代码库 > 一次由Tomcat重新加载引发的血案
一次由Tomcat重新加载引发的血案
引言: Tomcat是目前开发中使用非常广泛的Web服务器,其提供了很多优秀和简便易用的功能。我们这里的血案就是由于在部署过程中,不慎而引发的....
1. 问题的出现
在我们的系统后台每日定义会触发一些任务操作,进行一些数据的处理和更新,从上线到现在已经正常运行了将近3个多月了。忽然某一天,发现其数据不对,大量的数据都是被执行了2次某个定时任务A造成的。 查询日志,的确是执行了2次。但是之前为什么是正常的呢?诡异诡异....
环境: Tomcat 6.0.x
2. 血案的发生
由于在生产系统中,数据出现了问题,这个是一个非常严重的事情。于是首先把这个定时任务A先停掉,清理数据。由于数据量比较大,所以就有了焦头烂额的几天修正系统数据的过程。
3. 问题分析
3.1 任务A本身有问题吗?
之前都是正常运行的, 只在最近几天才发生问题。而且基于日志分析,其数据操作是正确的,问题是操作了2遍。
3.2 任务A实现分析
首先怀疑其实现的代码上是否有问题? 经过分析其是基于Quartz来实现定时任务的,Quartz是老牌的定时框架,不会出现问题。 是否是我们自己使用的方式问题,并只在特定情况下才会出现问题呢? 经过代码的排查分析,没有问题。
3.3 代码变更记录分析
是否是某个同事无意之前做的什么修改造成的呢? 经过针对代码变更记录和上线记录的分析,排除这个可能。
4. 系统日志分析
在排除了这个任务A自身问题之后,将目光转移到了环境本身。 部署环境是Tomcat6. 下载了Tomcat日志文件进行分析,逐渐找到了某些蛛丝马迹,从而剥丝抽茧般的发现了问题的原因。
在系统日志中,发现了若干在应用部署过程中的错误信息,这个引起了我的怀疑。是否是由于在部署中的错误造成的呢?
经过排查,确定是部署不慎造成的,而且是由于Tomcat本身的Reloadable特性造成的问题。
5. 问题场景的还原
1. 某位同事由于在修改某个问题,只修改了某几个Class文件,故其将编译后的class文件在服务器上直接进行了替换。
2. 由于Tomcat本身设置了Reloadable的属性,故Tomcat在检测到class文件发生变化之后,对WebApp的应用进行了重新加载:先卸载,再重新加载。
3. 在卸载的过程中,程序出错,卸载失败,原来的应用没有卸载成功,保留在Tomcat的缓存之中。
4. 虽然卸载失败,Tomcat继续加载新的WebApp应用。这里请注意: 新加载的WebApp应用在项目名称之后加了一个2,比如原来的项目名称是AAA, 新的项目名称就是AAA2。 重新定义了一个项目名称哦,我当时就知道,这里就是问题所在。
此时,二次加载的Web应用,也由于某些原因失败了。
5. 运维的同事,注意到了这些错误信息,由于重新启动了Tomcat,这次Tomcat正常启动了
第一个Web应用正常启动了
第二个Web应用也正常启动了,参看日志。
6. 于是在我们的Tomcat中就出现了两个一模一样的Web应用,换句话说,就是出现了两个一模一样的定时任务。 这个就是为什么定时任务会被执行2次的原因。
7. 解决以及应对
7.1 不允许动态的在生产系统中,替换class文件
7.2 在系统部署和上线过程中,需要进行日志分析,日志可以告诉我们一切的技术细节和内容。
7.3 将Tomcat的Reloadable设置置为false
<Context path="/helloApp" docBase="helloApp" debug="0" reloadable="false"/>
8. reloadable参数说明
Context>代表了运行在<Host>上的单个Web应用,一个<Host>可以有多个<Context>元素,每个Web应用必须有唯一的URL路径,这个URL路径在<Context>中的属性path中设定。
<Context path="/helloApp" docBase="helloApp" debug="0" reloadable="true"/><Context>元素的属性:
path:指定访问该Web应用的URL入口。
docBase:指定Web应用的文件路径,可以给定绝对路径,也可以给定相对于<Host>的appBase属性的相对路径,如果Web应用采用开放目录结构,则指定Web应用的根目录,如果Web应用是个war文件,则指定war文件的路径。
reloadable:如果这个属性设为true,tomcat服务器在运行状态下会监视在WEB-INF/classes和WEB-INF/lib目录下class文件的改动,如果监测到有class文件被更新的,服务器会自动重新加载Web应用。
在开发阶段将reloadable属性设为true,有助于调试servlet和其它的class文件,但这样用加重服务器运行负荷,建议在Web应用的发存阶段将reloadable设为false。
9. 总结
由于一个微小的变化引发的问题,在这之前一直正常。由于动态替代class,引发自动部署;部署失败,导致出现了2个相同的部署包。再次重启,然后2个缓存包都正常启动。 其本质原因是Tomcat在卸载应用的过程中,如果出现异常,则无法继续删除缓存;在重新加载的时候,无法清空已有的缓存,这个才是问题的根源。
我们姑且把这个问题,作为Tomcat的一个bug吧,只在特定情况下才会出现的问题。
一次由Tomcat重新加载引发的血案