Weblogic92中使用JDBC store存储session时,unique constraint violated(唯一约束冲突)相关问题分析

Weblogic92中使用JDBC store存储session时,unique constraint violated(唯一约束冲突)相关问题分析
        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应该是基本类似的,原因当然都是唯一约束冲突。
       

你可能感兴趣的:(Weblogic92中使用JDBC store存储session时,unique constraint violated(唯一约束冲突)相关问题分析)