?
诡异的时区问题+使用eclipse的远程调试(remote debug)功能(2011-3-2)
?
一.分析
?
昨晚和一般兄弟加班到凌晨两点,我花了很长时间诊断了一个错误. QA晚上Log了一个bug,关于在系统的一些search页面不能够正确的按照时间来进行搜索记录.比如需要搜索3月2号的数据,结果里面只能出来3月1号的数据.
拿到这个问题,我首先起了一个本机的服务器,结果可以正确搜索,由于QA使用的是另外一个HK的服务器,所以我又连接到HK server的测试URL上面测试,确实如QA所说存在问题,通过在Firebug我查看了post到后台的数值,时间值没有错误(3月2号),从后台返回的JSON来看,出来的数据是提前了一天的,也就是3月1号的。
好,那我只能通过FileZilla连上HK那边的server去取log文件,晕...140多M..下载了快20分钟. 趁这个空出去吃了个饭... 回来log文件已经拿到,Yeah.
打开log文件,找到相应时间点的记录,拿出log中用来search的sql.如下:
/* Formatted on 2011/3/2 23:59:41 (QP5 v5.114.809.3010) */SELECT * FROM v_market_po_advance_search mmp WHERE dept_id IN ('DEPT_GAP_2117_000000000000000000', 'DEPT_GAP_2173_000000000000000000', 'DEPT_GAP_2142_000000000000000000', AND plan_Stock_Date >= TO_DATE ('01/03/2011', 'dd/MM/yyyy') AND plan_Stock_Date <= TO_DATE ('01/03/2011', 'dd/MM/yyyy')
看来还真的在前台到后台的过程中发生了错误的转换,我们的系统中对于这种页面的search条件,会有一个构造的过程,也就是说会自动在sql的where条件后去append搜寻条件.而是通过struts2的拦截器(intercepter)来实现的,即在调用action(执行sql去真正的查询)之前,会通过intercepter来对前台传入的数据进行转换,拼成一条最终的sql. 至于这个intercepter的实现也很简单,继承了AbstractIntercepter抽象类而已.这里就不贴代码了,无非是对所有考虑的到的类型进行一些必要的转换.
对代码进行了一些分析.其中对一些特殊日期进行转换的代码是这样的(因为从页面传过来的日期值是EEE MMM d yyyy HH:mm:ss 'GMT'Z这样的format,会在如下的code里面处理) ??
private Date parseDateUseSysDateFormat(String str) { String[] parsePatterns = new String[] { "EEE MMM d yyyy HH:mm:ss 'GMT'Z", "EEE MMM d yyyy HH:mm:ss", "EEE MMM d HH:mm:ss 'UTC'Z yyyy" }; SimpleDateFormat parser = null; ParsePosition pos = new ParsePosition(0); for (int i = 0; i < parsePatterns.length; i++) { if (i == 0) { parser = new SimpleDateFormat(parsePatterns[0], Locale.US); } else { parser.applyPattern(parsePatterns[i]); } pos.setIndex(0); Date date = parser.parse(str, pos); if (date != null && pos.getIndex() == str.length()) { return date; } } return null;}
这段代码没有什么特别之处,通过特定的Pattern和Locale.US构造了一个SimpleDateFormate的paser,然后对日期进行转换,返回值.于是我想会不会是时区的问题,因为我在本地测试,客户端和服务器都是在同一时区,而QA通过本地的浏览器测试远程HK的server,这中间应该是存在时区的差别的,关键取决于server所在机器的时区设置.
?
二.使用Remote Debug实验
?
有了这样的设想,我决定在我本地起一个测试server,然后调整本机所在的时区为UTC+7(北京为UTC+8),然后再通过局域网中的另外一台机器作为客户端,来访问我本地server,并在相应页面通过时间对记录进行查找. Good Idea.. Haha..?
为了确定数据实际转换过程中的流程,也为了避免繁琐的log信息打印,我决定直接使用eclipse的remote debug功能(这是个很powerful的功能,我用了一次就爱上它了)
我们的项目使用的maven管理,而server就使用小巧的jetty.以下是jetty-debug.bat,直接运行就能就能让jetty去监控一个地址的特定端口.
?
set MAVEN_OPTS=-Xms512m -Xmx512m -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+CMSPermGenSweepingEnabled -XX:+UseConcMarkSweepGC -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9999,server=y,suspend=ymvn jetty:run 2> ..\error.log
然后需要在eclipse中进行必要的设置(见图片remote debug setting.png).
?
设置完成后,首先运行jetty-debug.bat脚本.从输出的信息可以看到,jetty已经在监听9999端口了(见图片jetty-debug.png).
?
然后进入eclipse,在程序里面设置断点,这样就可以开始debug了,按debug configuration中的debug按钮就行,这是可以看到命令行中的输出信息(见图片jetty-debug-done.png).
?
启动之后,进入search页面,通过时间进行search,通过firebug可以看到传入后台的参数如下.
?
Wed Mar 02 2011 00:00:00 GMT+0800 (China Standard Time)
?
同时eclipse也会自动进入debug模式窗口,因为我在上面日期转换那段代码里面设置了断点,这样可以省略前面的一些不重要的步骤,直接奔入主题啦,哈哈.按F6是单步,按F5可以进入相应的方法进行查看,按F7是回退(注:貌似是这样,没有深究)
?
可以看到时间在经过转换之后已经往前推了一天(见图片debug info.png).
?
然后我又将我本地server的时区调整正确,与客户端保持一致,再次进行debug,发现转换前后时间就是一致的了,这就表示确实是server端时区的设置问题了.相关部分就不贴图啦.
?
三.总结
?
通过这个Bug,让我记住了两点
1. 在我们进行项目部署测试的时候,有些问题可能是server端和client端时区差异造成的,其实后来询问过我们经理,这个问题我们在一开始的时候就和客户谈过,明确的告诉了他们会有这样的问题.呵呵.其实我觉得这种问题在前台就进行处理,例如转换成一个字符串,在构成sql之前再把这个字符串转换成日期,而不是在后台进行日期格式的复杂转换可能更好一些,这样也不会造成由于时区的不一致引起的各种问题.
2. Eclipse的Remote debug确实很方便很强大,大家可以尝试尝试,应该能够很方便的和一些主流的中间件集成并进行测试,这里有两篇关于eclipse remote debug的帖子,大家也可以看看:
http://www.itpub.net/thread-854391-1-53.html
http://www.ibm.com/developerworks/cn/opensource/os-eclipse-javadebug/
?
?
?
?
?
?
?