客戶端一段時間不訪問,第一次訪問資料庫自動斷開連線,重新整理可以正常連線
資料庫應用開發過程中,我們可能會遇到一個問題:應用使用了資料庫連線池,每經過指定時間後,發出到資料庫伺服器的任何請求都會失敗,而且有且僅有一次失敗,之後的正常訪問都沒有問題。尤其是在Web應用中,如果晚上時段沒有訪問,而第二天第一個訪客的經歷就是碰到一個數據庫訪問錯誤,如果開發系統的程式設計師沒有注意這個問題的話,可能終端使用者訪問會看到丟擲的一堆資料庫異常資訊。
其實,這個問題的主要原因是,應用中資料庫連線池中會儲存指定數量的資料庫連線例項,而這些連線例項並沒有定時地檢測其到資料庫伺服器連線是否正常;資料庫伺服器可以配置一個數據庫連線例項的超時時間,超過時間後它會自動斷開連線。也就是,被斷開的那個連線此時仍然儲存在應用的資料庫連線池內,下次被使用的時候就會發生資料庫連線斷開而導致一次訪問失敗。
解決上述連線關閉的方案有兩種值得推薦:
- 如果能夠提供這樣一種檢測機制,在應用的連線池管理中定時地檢測連線池中連線的有效性,就完全可以避免上面描述的問題。
- 在應用程式碼中通過異常處理機制,來實現該次業務的重新處理,也可以很好地避免。
我們通過上面給出的第二種方案來解決,對應異常中實現的程式碼,進行異常處理的邏輯如下所示:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { boolean retry = false; String type = request.getParameter(RequestParams.ITEM_TYPE); String top = request.getParameter(RequestParams.TOP_N); Byte itemType = Byte.parseByte(type); Integer topN = super.topN; if(top!=null) { try { topN = Integer.parseInt(top); } catch (Exception e) {} } Target target = itemNames.get(itemType); try { makeStat(request, response, itemType, topN, target); } catch (Exception e) { LOG.warn("", e); // com.ibatis.dao.client.DaoException: Error ending SQL Map transaction. Cause: java.sql.SQLException: Already closed. if(!retry && e instanceof DaoException) { LOG.warn("Try to obtain database connection again."); retry = true; this.makeStat(request, response, itemType, topN, target); } else { response.sendError(500, e.toString()); return; } } request.getRequestDispatcher(target.url).forward(request, response); } private void makeStat(HttpServletRequest request, HttpServletResponse response, Byte itemType, Integer topN, Target target) throws IOException, ServletException { List<StatItems> items = statItemsService.countItems(itemType, new Date(), topN); for (StatItems statK : items) { if(statK.getItemName()!=null && !"null".equalsIgnoreCase(statK.getItemName())) { pieDataset.setValue(statK.getItemName().trim() + " (" + statK.getPercentage() + ")", statK.getItemValue()); } } String imageUrl = super.generateImage(pieDataset, target.title, request); request.setAttribute("items", items); request.setAttribute("imageUrl", imageUrl); if(items!=null && !items.isEmpty() && items.size()<topN) { topN = items.size(); } request.setAttribute("topN", topN); }
上面程式碼,判斷如果是發生連線失敗,則儲存請求引數,再重新處理該請求。
另一種不推薦的方案,就是修改資料庫伺服器的連線超時配置。因為在實際專案中,通常應用上線的相關人員未必是DBA,對於修改資料庫伺服器的配置可能會給其它上線業務帶來風險。解決方法如下:
以MySQL為例,檢視檔案/etc/my.cnf,查詢有關超時配置的引數:
mysql> show variables like '%timeout'; +----------------------------+----------+ | Variable_name | Value | +----------------------------+----------+ | connect_timeout | 10 | | delayed_insert_timeout | 300 | | innodb_lock_wait_timeout | 50 | | innodb_rollback_on_timeout | OFF | | interactive_timeout | 28800 | | lock_wait_timeout | 31536000 | | net_read_timeout | 30 | | net_write_timeout | 60 | | slave_net_timeout | 3600 | | wait_timeout | 28800 | +----------------------------+----------+
我們可以在屬性組mysqld下面修改如下兩個引數:
- interactive_timeout
- wait_timeout
MySQL資料庫伺服器配置的連線超時時間預設是8小時,如果修改的超時時間足夠長的話,就不會出現前面發生的連線斷開的問題。但是,如果有很多應用都在使用資料庫連線池,大量的資料庫連線資源一直被佔用,嚴重的話可能使資料庫伺服器宕機,而且,也會使一些攻擊者偽造大量請求,使資料庫伺服器負荷過載而宕機,從而影響應用處理業務。