Weblogic92中使用JDBCstore存储session时问题分析

Weblogic92中,不少系统为了降低系统的内存开销,抑或防止session丢失,管理人员会是用JDBC sTore来存放session信息。不过在使用这种配置的时候,不少客户反映会碰到约束冲突的异常信息,如下,

<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 request3       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 time19         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中使用JDBCstore存储session时问题分析

相关文章:

你感兴趣的文章:

标签云: