引言
最近LZ的工作發生了重大變化,以后博文的更新速度可能會再度回溫,希望猿友們可以繼續關注。
近期LZ辭掉了項目經理的工作,不過並未離開公司,是轉到了基礎研發部做更基礎的研發,為廣大技術人員服務。這會讓LZ有更多的時間去研究一些技術方面的東西,LZ打算折騰一下spring的源碼,期待有一天可以成為spring代碼的貢獻者。
好了,廢話說到這里吧,今天先分享兩個小問題的解決辦法,可能你以后也會遇到的。
DBCP數據源坑爹的地方
前幾天系統出現了一個錯誤,比較奇葩。中文解釋是“無法從套接字讀取更多的數據”,原因是connection reset。首先很明顯的是,這是數據庫的連接出了問題,因為這個錯誤是在sql執行時報的錯。
從tcp原理上分析,這個錯誤的原因是因為連接被無緣無故的關閉了,導致連接被重置。於是簡單分析過后,懷疑是因為連接長時間沒有使用已經失效,但是連接池依然把連接給了應用程序去使用,結果導致使用了已經失效的連接。
於是LZ簡單搜索了一下,發現很多人都說有一個屬性可以控制在把連接交給數據源之前,先進行一下可用性的檢測。於是LZ打開源碼,看了一下這個屬性的初始值,結果一看,發現是true。
1 /**
2 * The indication of whether objects will be validated before being 3 * borrowed from the pool. If the object fails to validate, it will be 4 * dropped from the pool, and we will attempt to borrow another. 5 */
6 protected boolean testOnBorrow = true;
這尼瑪就奇怪了,已經是true了,那說明借用之前應該已經驗證了,為毛還會出現上面的錯誤?
LZ不服氣,於是在本地啟動了一下應用,跟蹤了連接獲取的過程,發現壓根就沒驗證。於是LZ再次把整個數據源初始化的代碼都看了一遍,才發現DBCP最坑爹的地方。看下面的代碼。
1 // Can't test without a validationQuery
2 if (validationQuery == null) { 3 setTestOnBorrow(false); 4 setTestOnReturn(false); 5 setTestWhileIdle(false); 6 }
LZ看見的時候當時就TM想爆粗口了,這尼瑪不是坑爹是什么?validationQuery默認就是空的,這不是相當於testOnBorrow默認是false嗎,還在屬性上寫個true誤導我等菜鳥程序猿。如果LZ不是閑着沒事看了看初始化的源碼,估計還在一直蛋疼這個問題,而且還要面臨業務同事的鄙視。
最終,設置了validationQuery屬性以后,解決了這個小小的疑難雜症。各位猿友也要注意下,在使用DBCP時,最好設置一下validationQuery。
高端springmvc的濫用
接下來的故事,是LZ濫用springmvc的故事,幸好LZ在上線之前就發現了這個問題,沒有在合作伙伴面前丟人。
隨着公司的發展,需要與合作伙伴進行系統對接,於是LZ需要編寫一個處於互聯網上的服務端。上一篇博文里LZ簡單介紹了加密的過程,本次則是后續LZ在做單元測試過程中發現的問題。
由於服務端的調用會比較頻繁,因此LZ在做單元測試的時候,專門寫了並發訪問的測試,期待能夠簡單的得到一個並發量的極限。結果卻出乎意料,並發量極限沒得到,卻發現在跑的過程中,服務端爆了一些空指針錯誤。
其實空指針錯誤也算是java當中最好解決的異常之一了,只要找到堆棧提示的位置,分析一下哪個表達式可能為空就基本上能解決問題。不過這次不同的是,LZ分析完以后,發現得到的結果是“不可能出現空指針異常”。
怎么會不可能出現呢?看看下面這段簡單的代碼,這段代碼不是真實的代碼,但道理一樣。
user.setName("xiaolongzuo"); //某一大堆與user無關的代碼以后
user.setSex("1");
錯誤提示的是setSex那一行空指針,那么從代碼上看,只有user為空時才會報空指針,但是假設user為空,那么在第一行就應該已經報了空指針錯誤,怎么可能到第三行才報出來呢?所以結論就是“不可能出現空指針異常”。
后來LZ仔細分析之后才發現,LZ的結論是沒錯的,但那個結論的前提是程序按照代碼編寫的順序執行。很明顯,這是由於並發造成的,歸根結底,是因為LZ以前從未用過springmvc,本次寫服務端,由於希望發布restful風格的服務,因此選擇了springmvc,拋棄了struts。
springmvc的請求上下文是方法,struts的請求上下文是Action。LZ在代碼當中錯誤的將請求作為了Action的屬性出現,於是當並發訪問時,請求中的參數就可能出現混亂,導致在第一行的時候user還不為空,到第三行的時候,由於請求被另外一個線程更新了,於是導致user為空,出現了奇葩的空指針,更加形象的代碼如下。
((User)request.getAttribute("user")).setName("xiaolongzuo"); //某一大堆與user無關的代碼以后
((User)request.getAttribute("user")).setSex("1");
解決辦法有兩種,第一種是去除Action中的request屬性,保證線程安全,不過這樣的話不少代碼會出現編譯錯誤。第二種是使用ThreadLocal,這種辦法相對來說比較簡單,而且不會出現編譯錯誤,只需要簡單的更改幾行代碼即可。
最終,LZ采取了第二種辦法,再次進行測試時,問題再也沒有出現。為了保證錯誤真正解決了,LZ還特意加大了並發量,多測試了幾次,依舊沒有出現該問題。
小結
本文沒有高大上的技術,更多的算是LZ自己的一個問題記錄,猿友們下次見!