信息化 频道

CRM,一个亿到四个亿

    乐观并发

    乐观并发策略通过消除读取和更新操作之间数据库中的所有锁定解决了性能问题,同时还提供了检测更新丢失的机制。和数据库并发一样,乐观并发给每个事务一个自己的bean实例,但是它没有在事务进行过程中在EJB容器或数据库中包含任何锁。对于进行读锁定的数据库(非Oracle数据库),容器在一个单独的事务(该事务在数据读取操作一完成就立即进行)中读取bean数据,从而避免了读取锁,并且提供了更高的可伸缩性。

    这种更新丢失检测机制并不新鲜,在Java创建之前就已经存在好长时间了。该模式的基本思想简单而强大:在更新时,首先进行检查,以确定表中的数据在被第一个进程读取之后是否被其他进程修改。实际中,这通常是通过在update语句的where子句中包含一个或多个额外的“版本”列来实现的。下面给出了一个简单的SQL的例子,其中version列是一个数值,在每次更新操作之后会递增:

-- at Entity load time"container executes select of bean fields,
-- plus 'version' field
select name, address, version from person where person_id = 1;
-- container remember value read from version column,
-- say it was 123

-- at update time, value of version which was read previously
-- added to the where clause and version incremented
update person set name='Joe', version = version + 1 where person_id = 1 and version = 123;
    在进行更新时,容器通过执行以下代码能检测出数据库中实际上有多少行被更新:

...
PreparedStatement ps =
connection.prepareStatement('update person...');
int rowsUpdated = ps.executeUpdate();
if (rowsUpdatated != 1) {
throw new OptimisticConcurrencyException('Data was modified by other process');
}
    如您所见,该方法可使服务器避免更新丢失问题,并且不需要在容器或数据库中进行长期的锁定。该策略尤其适用于数据库中读取次数远多于更新次数,因此更新碰撞的几率很小时。通常,对于大部分应用程序来说是这样的。

    一个用数字表示的版本列不是在WebLogic Server中实现乐观并发策略的唯一途径。相反可以使用一个时间戳列。(注意,数据库应该允许您存放足够精确的时间戳值)。时间戳的使用给您带来了额外好处,那就是知道记录的最后一次更新是什么时候。有时候在使用遗留数据库模式时,会不愿意或不可能更改数据表以添加一个版本列。这种情况下,WebLogic Server可以检查表中事务期间被读取的所有列(实体bean中的所有字段),或者只检查被当前事务更新的列。乐观并发可用于遗留模式,无需对数据表进行任何修改,但是开销略微增加(意味着更复杂的SQL更新)。

    只读并发

    最后一个并发策略是read-only并发。如果数据库中的某些表包含很少或从未被更改过的数据,那么就很有必要将CMP bean声明为只读。服务器仍然为每个事务激活一个新的bean实例,所以请求是并行处理的;它不会每次都调用ejbLoad(),但是根据read-timeout-seconds参数的值周期性地调用。这为您的应用程序带来了显著的性能提升,因为SQL选择只进行一次(第一次访问实体bean时),然后就被缓存起来,在后面的事务中重用。

    WebLogic Server 8.1 SP2之前版本中的一个特殊功能是,即使bean被部署为只读,开发人员仍然可以对该bean的实例进行create和remove操作。从8.1 SP2开始,这个功能在默认情况下已被禁用,但是如果您需要,可以通过在weblogic-cmp.jsr.xml中将allow-readonly-create-and-remove元素设置为true来打开它。

    也有一种明确禁用只读实体bean的方法。该禁用操作强制在下一次事务开始时从数据库中刷新bean实例,即使读取时限还没过去。您可以将其看作冲洗实例缓存。可以使特定bean的一个特定实例,任何实例子集,或者给定bean的所有实例无效。要想调用invalidate()方法,您可能需要将bean的home或local home分别转换为CachingHome或CachingLocalHome。下面的代码说明了如何实现这一点:

Object o = context.lookup("MyBeanCMP");
// cast to CachinHome for remote interface or CachingLocalHome
// for local
CachingHome cachingHome = (CachingHome)o;

// invalidate particular instance by passing primary key value
cachingHome.invalidate(pk);
// invalidate any subset of bean instances by passing collections
// of primary keys
Collection pks = Array.asList(new Object[]{pk1, pk2, ...., pkN});
cachingHome.invalidate(pks);
// or invalidate all instances currently cached
cachingHome.invalidateAll();
    当表中的数据准静态时(比如,如果它按照批处理过程每天更改一次)进行显式的无效操作很有用。这种情况下,您可以将相应的bean部署为只读,并且设置一个较大的读取超时值,然后,当批处理过程结束时,为这些实体bean调用invalidateAll()。

    您可以通过在weblogic-ejb-jar.xml部署描述符中entity-cache一节中设置concurrency-strategy元素,为每个CMP bean指定一种并发策略。如果没有指定并发策略,WebLogic Server默认使用数据库并发。

    性能改善策略

    既然大家已经熟悉WebLogic Server中的各种并发策略,现在就让我演示一下如何应用这些知识应用来提高CMP bean的性能。CMP bean通常由于性能不出众而遭到批评。从某种程度上说确实如此,因为如上所述,对于按照默认并发策略设置部署的bean,WebLogic Server会在每次新事务的一开始从数据库中读取数据。换言之,bean实例状态并没有在各个事务之间缓存,并且数据库收到大量的选择请求。有人争辩说,这没问题,因为现代的数据库本身就有有效的缓存机制。因此,一旦第一次被选择,数据就可能留在数据库缓存中,等待后面被调用,需要很少或者不需要磁盘活动来进行数据库选择。虽然这样,但是我们不应该忘记,大多数情况下应用服务器和数据库之间的调用要在网络间传输,调用的延迟要比JVM内部的本地调用高几个数量级。另一个要关心的是,多个应用服务器实例可以共同访问同一个数据库(通常在集群配置中)。这种情况下,数据可能会充满应用服务器和数据库之间的甚至是最快速的网络链接。

    简而言之,如果我们需要好的性能和伸缩性,那么我们可用的最有效的策略就是如何在可能的情况下在本地缓存数据,以避免代价高昂的对数据库的远程调用。从CMP bean的角度来说,这意味着我们需要一种机制在来自各个不同事务的调用之间保存(或缓存)bean的状态数据。当然,这会给我们带来性能收益,只要同一个bean实例在它生命周期中被调用一次以上的几率大于零。换言之,您的应用程序读数据的机会要多于写数据,并且存在在每次更新之间多次访问同一数据的可能。比如,如果您的应用程序只向数据库中写数据(OLTP),缓存那些数据根本不能提高性能。同样,如果您有特别大的表,并且要随机选择某些行,那么就存在这样的可能:缓存数据要存在足够长的时间,以便在下次需要时可用。幸运的是,仍然有很多类型的应用程序满足我们的缓存标准。您需要针对具体任务评估缓存的有效性。

    观察一下CMP bean的可用并发策略,您会注意到,至少有两种方法可以实现长期缓存。第一种方法是尽可能使用只读bean。不幸的是,通常数据库中的数据不是完全静态的,并且/或者按照不可预知的时间间隔更新。如前所述,有一种机制可以显式地使任何只读CMP bean的缓存无效。这种方法虽然可行,但是却很不理想并且易发生错误,因为开发人员必须记得在每次更新操作之后调用invalidate()。幸运的是,WebLogic Server提供了方便的read-mostly模式实现,这在稍后将会详细讨论。更强大的方法是充分利用使用乐观并发策略部署的bean可用的事务间缓存机制。

    使用read-mostly模式

    WebLogic Server通过将一个只读CMP bean和一个读写CMP bean映射到同一数据中为您提供了一个实现read-mostly模式的机制。您的应用程序应该用只读bean来读取数据,用读写bean来修改数据。只读bean按照上述部署描述符中read-timeout-seconds元素所指定的间隔从数据库中加载数据。为了保证只读bean总是返回当前数据,应该在读写bean更改数据时使只读bean无效。您可以通过在weblogic-ejb-jar.xml中的entity-descriptor小节的invalidation-target元素中指定该bean,来配置服务器,使其自动让相应的只读bean无效。这只能用于CMP 2.0实体bean。虽然该模式提供了缓存方面的好处,但是也有严重的不足。当使用该模式时,您应用程序中的大量实体bean将被有效地加倍,对应用程序的启动时间和内存造成影响。同样,开发人员需要记住,只读和读写操作应该使用不同的bean,这经常会令人混淆。

    值得一提的是,在旧版本的WebLogic Server中(没有对只读模式的通过invalidation-target的内在支持),仍可以使用它。回忆一下前面讲过的,根据EJB规范,如果EJB抛出一个RuntimeException或者它的子类物,容器就应该销毁bean实例。因此,可以在实体bean的只读版本上暴露这样的destroyMe()方法,并从读写bean的ejbStore()方法中调用它。这就是著名的sepukku模式。

0
相关文章