源码面前,了无秘密
著名IT作家、译者侯捷老师以前在其著作中有句话,就是我们今天文章的标题「源码面前,了无秘密」。可以说相当精炼。高度概括了从源码中我们可以收获的内容。在源码中,无论是应用的调用逻辑,关系,各种设计都一目了然。
为什么会突然想到这样一个题目呢?
是因为最近一个项目上线,其中有几个功能模块使用了 Redis 进行数据的缓存,包括权限信息也在其中。所以每个请求都会通过 Redis 进行鉴权。这块功能是我指导的一个小同学负责开发的。上线几天后出现了这样的问题:
问题现象是线上出现了大量的错误
Cannot get Jedis connection; nested exception
is redis.clients.jedis.exceptions.JedisException:
Could not get a resource from the pool
项目约等于小流量,上线时 Redis 的最大连接数开到了1000,用户数远小于最大连接数,Redis 使用的是公司的集群,稳定性也有保障,理论上是不该出现这种取不到资源的情况的。
小同学自行 Google了好久没找到问题具体原因。我提供的几个思路也没本质解决问题。不过好在调整配置可以在线下稳定复现。所以我在小同学的电脑上开始debug,跟源码。
和结对编程类似,我一边Debug,我一边给小同学讲 Redis 这个连接池的实现思路,目前怀疑的问题等等。小同学说自己刚才也在试着跟源码,但跟了两层之后,感觉调用越来越深,有点晕,就放弃了。
然后我继续Debug和讲原理,跟到资源使用完毕,在 finally 进行释放回收的地方,发现有处代码在判断当前的 dataSource
是否为空,从而执行两种不同的操作。 如果不为空,则会将资源放回pool中,便于下次继续使用,为空则真正的进行close
的操作,直接将Socket关闭了。而这两个if/else的逻辑是封装在一个方法中,不跟进来不会发现区别对待。
而且我们当前在用连接池,预期是按连接池的思路,使用完毕的需要释放回池中继续下次的使用,当前这个现象就比较怪异了。
继续向前,发现整体虽然Connection 使用的是JedisConnection
,但在我们出问题的这里,返回的是个其子类JedisWrapper
,这个是部门同学开发的一个SpringBoot 的 starter,重写了一部分逻辑,将连接对象作为原始对象持有,我们并没有用到其中的特性。上面判断dataSource是否为空本来是要判断JedisConnection
里该属性是否为空,在Wrapper之后,是保存了origin的对象,返回的是个new Wrapper,这样dataSource并没有初始化,就出现了前面dataSource为空的问题。
跟到这里,发现是maven 的依赖不同,切换回标准的 SpringBoot starter,问题解决了。
小同学感叹道,「这不是造孽么,如果不是你帮我,不知道从哪下手分析了。」
我则鼓励小同学没有思路的时候,可以从源码里找找,跟代码的过程,也是学习的过程,比如这次就可以在跟的时候了解连接池具体是怎样实现的。任何问题在源码中都无处遁形,况且这些源码也可能有Bug呢。
回过头来,我们不难发现,可能不是所有的问题都需要跟源码,但阅读源码,带着问题 Debug,本身就像你放大招一样,虽然费时费力,但收获也会很多,一招制敌。这也是开源带来的好处,可能通过源码来分析作者的思路,在问题产生时可以逐行分析,了解细枝末节,这些都是C/C++里dll文件所不具备的优势。:-)
对于新同学,可能对于源码阅读有畏难情绪,感觉这一层层的嵌套,调用有点找不着头绪。但这些都是表象,你试着跟几次就轻车熟路了。
老司机为什么称为老司机,一是车技好,二是熟悉车况。这些也都是练出来的。
所以无论是做为解决问题的终极手段,还是学习设计与思想的宝库,源码都是不二之选,有时间的时候,可以多看看,不久之后,你也会是老司机。
最后,总结下一般出现问题时,跟源码的思路
如果有异常,可以利用IDE的
异常断点
,这样异常产生时,一个鲜活的异常栈就获取到了,完整的调用链就在你眼前。逐级深入,先分块找到怀疑的目标方法,结合前一步,跟进
具体方法时,不要根据表现的方法名,参数来猜方法的意图,必要时进去看一眼,万一有隐藏逻辑呢
同样的类,看看包名、类名,是否是加载的类不对导致
之前我也写过一些读源码找Bug的文章,比如这篇:与MyBatis缠斗的几个小时...
你也可以查看其他相关阅读:
关注『 Tomcat那些事儿 』 ,发现更多精彩文章!了解各种常见问题背后的原理与答案。深入源码,分析细节,内容原创,欢迎关注。
转发是最大的支持,谢谢
更多精彩内容:
一台机器上安装多个Tomcat 的原理(回复001)
监控Tomcat中的各种数据 (回复002)
启动Tomcat的安全机制(回复003)
乱码问题的原理及解决方式(回复007)
Tomcat 日志工作原理及配置(回复011)
web.xml 解析实现(回复 012)
线程池的原理( 回复 014)
Tomcat 的集群搭建原理与实现 (回复 015)
类加载器的原理 (回复 016)
类找不到等问题 (回复 017)
代码的热替换实现(回复 018)
Tomcat 进程自动退出问题 (回复 019)
为什么总是返回404? (回复 020)
...
PS: 对于一些 Tomcat常见问题,在公众号的【常见问题】菜单中,有需要的朋友欢迎关注查看。