连接池泄露定位案例

前阵子,其他项目组里边有个某项目频繁出现获取不到连接的问题,基本上每一天都出现一次。

打出来的javacore里面,有大量的线程等待获取连接。并且现场反映,从数据库那边监控到大量的空闲连接没有释放。

事实上,这个系统会用到两个数据源,其中有个是正常的。有意思的是,这几个月都没有新版本上载。

走读了java里边的连接获取释放逻辑,虽然获取和关闭的调用到处都是(没封装好),但最后还是调用一些静态方法来完成的,并且都在finally块中进行了处理,感觉不会存在泄露的问题。

尝试看看能不能重现,运气还不错,在开发环境模拟生产的业务场景(业务功能不多,但逻辑处理流程比较长)进行了压力测试,也的确出现了这种情况。

既然能够重现问题,定位就方便许多了。我知道有Btrace这类神器可以帮助定位,但我没有使用过(下次再尝试一下)。我是通过修改代码来定位的,基本原理就是在获取连接时记录堆栈信息,在关闭时清除,这样出问题的时候就可以找到哪些地方有问题。其实用工具也是类似的做法。

大体上是这样的:

// 用来存储堆栈信息
private static final Map<Connection, Exception> conns = new ConcurrentHashMap<Connection, Exception>();

// 当获取链接的时候
public Connection getConnection(){
	Connection conn = dataSource.getConnection();
	conns.put(conn, new Exception());
	return conn;
}

// 当释放链接的时候
public void releaseConnection(Connection conn){
	conns.remove(conn);
	dataSource.close(conn);
}

修改后重新压力测试后,打开记录的堆栈一看,居然是在jsp里边获取的,真是顿时无语。

后来,虽然听说这个问题是其他原因引起的,导致非正常情况下走到这段逻辑,但这个地雷还是以前埋进去的,也怨不了别人。

其实,关于资源释放的逻辑封装,可以参考spring的jdbc封装(回调的方式),又或许用ThreadLocal、拦截器等方式在整个应用上进行处理。

周日笔记本系统安装记事

上周在京东商城买了一个HP的笔记本,没有带系统,本来以为装个系统不麻烦,没想到整了一天才装好。

事情是这样的,网上推荐使用USB安装,可是我手头上只有一个2G的USB,心想装个xp凑合使用着。我使用了老毛桃的装机工具, 发现无论是使用ghost还是原版安装,都没法成功,重启之后到了xp启动界面就蓝屏了。

尝试了好几次之后,并且测试chkdsk /f 后无效,我还是上网查找一下。错误码是0X0000007B,网上说这个是和硬盘有关,是设置问题或者病毒造成的硬盘引导分区错误。 如果是在用原版系统盘安装系统的时候出这个问题,那说明机器配置还是比较新的,作为老的系统盘,不认这么新的硬盘接口,所以得进BIOS把硬盘模式改成IDE兼容模式。

我这机器是新的,硬盘也重新格式化并分区了,应该不是什么病毒的问题。那应该是硬盘接口的问题,不过我发现BIOS里边没有硬盘的设置项目,上网搜索发现的确是没有这个设置的。

看来xp是没希望了,只好换win7系统,win7的iso有2G多,只能分两次拷贝到笔记本硬盘上,再安装系统。果然,这次就很顺利了,整整折腾了一天。看来,装机也是技术活呀。

谈任务分解与评估

平时工作,做需求的时候,总有有时间限制,俗称工作量评估,通常用x天或者x个单位点数来表示。在现实中,评估都是很随意的,实际结果经常有很大出入。 在这里不评论,这种做法有多科学。只是单个任务工作量越大,越需要细分,直到有足够的清晰度,这样才能评估得比较靠谱。这里我们用一个例子来解释一下。

我最近想做一个andriod上的死活题app,至少第一期的目标是这样的。我们先来做一下分解,看看需要那些工具,需要做什么工作。

总体来说,用android,优先考虑使用java开发。这需要java的编程经验和一些android的经验。我认为,这个不是难点,还是比较有信心的。

既然是围棋的死活题,需要了解死活题的存储格式,这是一种基于文本的格式,用树型分支的形式。 这个程序需要能够识别这种格式,这要求我们需要实现一个解释器,不过我考虑使用现成的词法分析器来实现,例如javacc,当然如果不懂这个,还得去学习一下。

另外,这是一个围棋游戏,肯定需要实现游戏规则,最重要的就是落子,吃子的判断。这个是最重要的算法部分。

还有就是,由于涉及棋盘显示,可能需要实现自定义控件或者绘图功能。如果不熟悉,也是要多花些时间的。

还有一些不那么明显的细节部分,举例如下:

死活题通常只用了棋盘的一小部分,手机屏幕又小,如何只是集中显示部分?
另外,在显示所有死活题列表的时候,能不能显示预览图?

举例子,是想说明,一些看似简单的任务,其实并没有那么简单。除了仔细分解任务,还有个常用的手法就是原型,把认为最难的部分先做一下,看看心里有没有底。

当然,所有的任务都跟你的熟悉程度有关,经常做一件事情就是这样(日常的工作就是这样)。但这些工作经验,在切换到非舒适区的时候,并不能减少心里的担忧。对于做技术的人来说,需要做的事,往往是要敢于打破技术舒适区。

小心jsch的sftp连接泄露

今天早上和一个同事处理一个现网问题,从javacore里边可以看到大量的Connect Thread,如下所示:

Connect thread 192.168.1.100 session" prio=6 tid=0x042d3400 nid=0x1458 runnable [0x04e4f000]

堆栈信息如下:

...
com.jcraft.jsch.Session.run(Session.java:1193)
java.lang.Thread.run(Thread.java:619)

怀疑是资源泄露了,jsch是一个sftp的工具库。检查jsch的使用代码,可以看到代码是有进行关闭的,如下所示:

    JSch jsch = new JSch();
    Session session = jsch.getSession("caixiaojian", "192.168.1.100", 22);
    session.setPassword("******");
    session.setConfig("StrictHostKeyChecking", "no");
    session.connect();
    Channel channel = session.openChannel("sftp");
    channel.connect();
    ChannelSftp c = (ChannelSftp) channel;

    channel.connect();
    //...
    channel.disconnect();

不过从官方的例子上看到,最需要关闭的是session对象而不是channel对象。 于是写了一个简单的测试Demo,把上面的代码跑5次,看看能不能重现:

#jstack -l 3621 | grep Connect
Connect thread 192.168.1.100 session" prio=6 tid=0x042d3400 nid=0x1458 runnable [0x04e4f000]
Connect thread 192.168.1.100 session" prio=6 tid=0x042d0400 nid=0x16c8 runnable [0x04def000]
Connect thread 192.168.1.100 session" prio=6 tid=0x041b4000 nid=0xd38 runnable [0x04d8f000]
Connect thread 192.168.1.100 session" prio=6 tid=0x041b2000 nid=0x166c runnable [0x04bcf000]
Connect thread 192.168.1.100 session" prio=6 tid=0x041b1000 nid=0x450 runnable [0x04b2f000]

果然出现了,试着在最后调用一下session.disconnect(),重试一下果然不存在了上述线程了。

单词接龙问题

昨天看到其他部门的技能鉴定题目,相对我们部门偏应用的题目,他们的相对更加偏重算法。题目描述如下:

有一种单词接龙的游戏,就是连续的两个单词,第一个单词的尾要和第二个单词的头是一样的。
例如: cat tell log google就是一个单词接龙。

定义最少单词接龙是单词个数最少的接龙,定义最短单词接龙是单词字母个数总和最少的接龙。 假设所有单词都是由小写字母组成,现在给定一堆单词,给定龙头和龙尾,求出最少单词接龙和最短单词接龙。

当时在招聘,一时也没反应过来。下午回来的时候,仔细一想,这不就是最短路径问题么?

把26个小写字母看成是结点,那么每个单词就是从某个结点到另外一个结点的单向路径。
上面的接龙如果在加上单词gc, cookie,就是下面的图表示:
单词接龙

最少单词接龙,就是每条路径的权值为1,求两个结点之间的最短路径。
最短单词接龙,就是每条路径的权值为单词的长度,求两个结点之间的最短路径。

另外,关于一些特殊情况和注意点:

对于存在多个单词,他们的首尾字母相同的话,只记录长度最短的为路径的权值即可。

结点的存储可以不用链接表,因为只有26个字母,用二维数组即可,实时增加单词也容易处理。

求最短路径的算法,参考经典的Dijkstra最短路径算法,这里就不详述了。 关于Dijkstra,可以参考百度百科中的条目