程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Weblogic92中使用JDBC store存儲session時問題分析

Weblogic92中使用JDBC store存儲session時問題分析

編輯:關於JAVA

Weblogic92中,不少系統為了降低系統的內存開銷,抑或防止session丟失,管理人員會是用JDBC store來存放session信息。不過在使用這種配置的時候,不少客戶反映會碰到約束沖突的異常信息,如下,

<Jan 23, 2009 10:07:33 AM CST> <Error> <HTTP Session> <BEA-100087> <The jdbc session data for session id: DcwFJ5mH1HbFrVR2L6z5xpyGXcWLbJFxHrxP2ZF6jQ1hVJ32Gmfl ctx:testWeb dblat:1232676391562 triggerLAT:0 has been modified by another server in the cluster.

java.sql.SQLException: ORA-00001: unique constraint (SYSTEM.SYS_C003007) violated

at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)

at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:331)

at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:288)

at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:743)

at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:216)

Truncated. see log file for complete stacktrace

本文就對這個問題作一下分析,看看什麼樣的原因會引起上述問題。

首先,我們想一下,為什麼會出現諸如ORA-00001,這樣的錯誤。下面是ORA-00001的問題官方描述,

This error means that an attempt has been made to insert a record with a duplicate (unique) key. This error will also be generated if an existing record is updated to generate a duplicate (unique) key. Typically this is a duplicate primary key, but it need not be the primary key.

如上所述,這類問題多是由於我們插入或更新紀錄時,出現duplicate (unique) key導致的。Weblogic92中,使用JDBC store來存儲session的時候,所有的session會被放入一張叫做wl_servlet_sessions的表中,我們現在看看wl_servlet_sessions,哪些列可能導致duplicate key呢?wl_servlet_sessions的結構如下:

 create table wl_servlet_sessions
( wl_id VARCHAR2(100) NOT NULL,
    wl_context_path VARCHAR2(100) NOT NULL,
    wl_is_new CHAR(1),
    wl_create_time NUMBER(20),
    wl_is_valid CHAR(1),
    wl_session_values LONG RAW,
    wl_access_time NUMBER(20),
    wl_max_inactive_interval INTEGER,
   PRIMARY KEY (wl_id, wl_context_path) );

對於不同的database,具體表結構請參考 http://e-docs.bea.com/wls/docs92/webapp/sessions.html。從表結構中我們可以看到,weblogic使用wl_id,wl_context_path作為聯合主鍵,由於對於一個session application而言,他的wl_context_path是固定,所以引發ORA-00001的只有wl_id。那麼到底是insert,還是update引起這個問題的呢?Weblogic中,更新sessio的時候,只更新session data,不會更新其primary key,也就是說session update不會引起ORA-00001,原因只能是insert,即嘗試插入相同wl_id數據的時候,會引發該問題。即使同樣是插入操作,其直接原因也可能分為如下幾種情況,

1: weblogic的bug

2: 應用場景問題(比如load balancer不能保證session stick)

下面我會分別介紹一下這兩種情況,

1:Weblogic的bug

也許你會問,同一時刻,一個session id不是只能有一個與其對應的session object在內存中嗎?而且這應該由weblogic來保證。是的,正常情況下,客戶端請求進到Weblogic的時候,weblogic會檢查cache中是否存在與其session id對應的session object,有的話,從cache中取出,沒有的話,它會通過getFromDB()從database中load,如果還是沒有,這時候才會通過dbCreate去插入一條記錄。 如果中間某一處weblogic沒有控制好的話,問題就來了。我將以如下的一個test作為案例,分析一下具體過程,

這裡將不考慮應用場景問題,即session stick可以被保證。假如一個客戶在訪問應用系統時,順序訪問了三個應用頁面(page 1/2/3),而這三個頁面中, page 1/3會涉及session更新(set attribute),而page 2只讀取session(get attribute). 由於這個問題和session的dbLAT、triggerLAT相關,所以我們主要這裡主要關注 dbLAT、triggerLAT的變化及問題點。

1.1: client訪問page1, 假如page1中第一次訪問session對象,由於cache和db中均沒有該對象,那麼我們會創建一個session, 並以wl_id和wl_context_path作為主鍵,插入紀錄。page1中作寫setAttribute的動作,請求結束後,這個對象會被同步到數據庫,同時session數據被放入內存的cache中,如下:

1   public void sync(HttpSession data) {
2     // This should be invoked only at the end of the request
3       
4       session.syncSession();
5       cache.put(session.id, data);
6       
7 
8   }

注意:cache用於限制內存中當前active的session數,當cache滿了的時候,最早進入cache的session將被從cache中挪走。默認的cache size為1024,這個至可以通過session-descriptor的cache-size配置。

在來看看session.syncSession()邏輯, 它通過dbUpdate()實現,dbUpdate()如下:

 1 private void dbUpdate() throws SQLException {
 2         
 3     if (isModified()) {
 4     conn = getConnection(jdbcProps);
 5     stmt = conn.prepareStatement(jdbcCtx.getUpdateQuery());
 6     int i = 0;
 7     
 8     // the sql update is performed only if the lat in the DB matches the value of dbLAT or triggerLAT.
 9     i = stmt.executeUpdate();
10     if (i > 0) {
11           dbLAT = accessTime;
12           triggerLAT = 0;
13         }
14     if (i == 0) {
15           dbCreate();
16     }
17       } else {
18         // Session Data has not changed so just update last access time
19         jdbcCtx.updateLAT(this, contextName);
20       }
21   }

這裡可以看到,syncSession的時候,首先檢查對象是否作過修改,如果沒有,通過jdbcCtx.updateLAT()去更新triggerLAT,如果做過修改,我們到數據庫中檢查對象是否存在,存在的話,更新dbLAT,如果不存在,通過dbCreate()插入紀錄。我們這一步中,因為是第一個請求,所以db中沒有記錄,要通過dbCreate()插入紀錄,假如同步進數據庫的dbLAT為1,由於這是沒有timerTrigger被觸發,它的triggerLAT為0。

1.2: client訪問完page1後,繼續訪問page2,由於cache中能夠找到session-id對應的對象,我們直接利用cache中的對象,該對象屬性如下:

Session_A.dbLAT = 1(這裡1標示某個時間點)

Session_A.triggerLAT = 0

請求結束時,由於我們沒有修改這個session對象(只做了read attribute), 在syncSession的時候,我們會把這個update操作交給trigger去做,即jdbcCtx.updateLAT()。這個trigger就是LastAccessTimeTrigger,它用於批量更新類似未作修改的session的triggerLAT修改,每10秒被觸發一次。我們把這個Session_A交給trigger,這時候triggerLAT被賦一個值(這個值在trigger被觸發的時候被寫入數據庫),假如是2,如下:

Session_A.triggerLAT = 2

1.3: client訪問完page2後,繼續訪問page3, 假如這個時間間隔有點長(但不超過上述trigger的默認間隔10秒),如果系統的訪問量很大,或者cache-size設定很小,Session_A在cache中的位置被其他後入的session占據,即session中不再有這個session。請求進入訪問page3,由於cache中沒有這個對象,我們需要通過getFromDB()從db中load這個對象(注意:load數據後,我們將重建一個session對象,wl_id同於1.1, 1.2的session, 這裡用Session_B表示)。如果此刻1.2中的trigger仍沒有被觸發,即triggerLAT=2沒有被寫入數據庫,那麼getFromDB()從db中獲取的數據將是1.1的數據,即

Session_B.dbLAT = 1

Session_B.triggerLAT = 0

1.4: 在page3請求處理結束前,即數據被同步到數據庫前,1.2的trigger被觸發,即它將triggerLAT =2寫入數據庫。

1.5: page3的請求處理結束,Session_B的dbLAT會變成一個其他值,比如2,如下:

Session_B.dbLAT = 3

Session_B.triggerLAT = 0

此時他同樣會通過syncSession()去同步數據,由於page3中涉及session的修改,所以它會通過dbUpdate()去檢查數據庫中是否存在session_id對應的紀錄(數據的確是存在的, wl_access_time=triggerLAT=2),它加查的條件是數據庫中的只有wl_access_time等於當前的dbLAT或者triggerLAT,顯然數據庫中的紀錄是不符合條件的,即返回紀錄數為0,我們將通過dbCreate()創建一條記錄,這樣問題就來了,我們嘗試創建相同wl_id,相同wl_context_path的紀錄,ORA-00001自然就出現了。

這個問題的根源是weblogic沒有處理好cache和trigger間對象應用的關系,即trigger拿著對象要求作batch update的時候,如果cache滿了,cache中的對象將被刪除,新入請求將會於server中創建同一session id對應的不同對象。這個weblogic的一個bug(CR331498),修正bug的方法是通過WeakReference解決trigger和cache間的對象應用關系,即如果某個對象被trigger refer的話,這個對象就不會被從cache中刪除。當然,既然是cache的問題,我們其實也可以通過加大cache-size來解決。   

2: 應用場景問題

從1中我們可以看到,引發ORA-00001的根本原因是一個session id的多個sess實例同時存在於一個server上。同樣,如果這多個實例分布在不同的server上,這樣的問題還是會出現。很簡單,加入server_1上有個session,session創建的時候,會往數據庫中插入該session對應的記錄。如果插入數據後,server_2接收到帶著同一session id的request, 那麼server_2會從數據庫中load數據。如果在server_1、server_2的請求都涉及到session更新,那麼後syncSession的那個request必然碰到ORA-00001的錯誤。比如server_1後結束,因為server_2結束請求時,把db中的數據更新,造成server_1和db中的數據完全不一致,server_1最終會嘗試通過dbCreate()創建記錄,結果當然是ORA-00001。

現在我們看看什麼原因會導致server_1, server_2同時存在同一session id對應的session對象呢? 基本上都是因為前端的代理無法保證session stick,無論是軟代理,還是hardware loadbalancer。通常情況下,這些代理都能保證session stick,至於為什麼不能保證session stick,我們可以通過相應的日志文件找出原因。比如apache,我們可以打開weblogic的wl proxy debug,log中基本可以幫助我們定位這些問題,可能是某個時刻,server_1無法相應,proxy錯誤的以為server_1不可用了,所以會failover請求到server_2上。

這裡我之所以以ORA-00001舉例,因為用Oracle的太多了,對於其他數據庫,雖然不會ORA-00001錯誤,但statck trace應該是基本類似的,原因當然都是唯一約束沖突。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved