信息化 频道

CRM,一个亿到四个亿

    为实体Bean选择非常好的缓存大小

    既然您理解了事务间的缓存是如何工作的,下面就让我们讨论一下选择非常好的缓存大小方面的重要话题。默认情况下,每个实体bean都定义有一个大小为1000的缓存。缓存大小由weblogic-ejb-jar.xml部署描述符中的max-beans-in-cache元素控制。我发现该名称有些令人误解,因为根据并发策略的不同,WebLogic Server保存无状态的实体bean实例池(采用数据库并发策略和排他性并发策略,且事务间缓存被禁止的情况下)或者(只读、乐观或排他性并发策略,且事务间缓存启用的情况下)保存具有保留字段值的bean的真正缓存,从而无需从数据库中重新加载bean的状态就可使用。后一种情况更有趣。有人可能会想,更改缓存大小只影响具有实体bean的操作的性能;缓存越大,在缓存中发现需要的特定实体bean实例的机会就越大。基本上是这样的,但是如我下面会讲到的那样,另一个重要因素会影响缓存大小的选择。

    多版本化和事务的因素

    确定实体bean缓存大小的推动因素之一(可能不太明显)是当事务使用实体bean时,它们在事务的执行期间被实际加载和“固定”在实体缓存中,即使调用者不在修改实体bean实例而仅是从中读取值。比如,想象一下,一个会话或MDB bean中的代码在一个实体bean “Person”上执行一个finder方法,然后在返回的集合上迭代。

...
Collection persons = personLocalHome.findSomething();

for (Iterator iter = persons.iterator(); iter.hasNext();) {
PersonLocal person = (PersonLocal)iter.next();
Long id = person.getId();
// do something: log, send email, etc
...
}
...
    如果findSomething()方法返回比max-beans-in-cache中规定的值更多的对象,您的应用程序将在迭代器得到N+1个对象时(N为当前实体缓存大小)获得一个令人不愉快的(并且很可能是不想要的)CacheFullException。这可能看上去很重要,因为一般大家都认为finder不应返回很大的集合。但是不要忘记默认情况下WebLogic实体缓存是多版本的,这意味着如果多个事务请求同一个实体bean实例,那么会在缓存中创建多个版本(每个事务一个);从唯一对象的角度来看这可能极大地降低了缓存容量。

    由于有多个事务同时运行在一个容器上很正常,所以可以想象如果上面的代码被从一个会话或MDB bean中调用,其中该bean是利用一个较高的max-beans-in-free-pool参数值(默认1000)部署的,并且同时有50个客户端请求。这使得每个事务在实体缓存中只有1000/50 = 20个可用的槽,如果一个finder返回的对象超过20个,有的事务就会失败。

    在设计具有大量实体bean的操作时要时刻牢记这一点。开发人员通常使用小型数据库这一事实使情况变得更糟,并且该问题可能并不表现出来,直到代码部署到生产规模的数据库中时。作为保护措施,我建议在开发过程中不要使用默认的缓存大小,而是将其较低值(10-100),这样缓存相关的问题就能在开发早期发现并解决。

    如您所见,为实体缓存选择正确的大小非常重要,并且不只是从性能的角度来看。如果缓存过大,您的应用程序将消耗很多不必要的内存,但是如果您走到另一个极端,配置过小的缓存空间,会有收到CacheFullException的风险。那么该如何为所有的实体bean选择非常好的的缓存大小呢?

    如果您没有明确为实体bean指定缓存大小,WebLogic Server将使用默认大小1000。这对于预先知道实例数不会太多的某些bean来说足够了——比如,如果一个bean表示数据库中的一个查询表,比如“country”或“state”,其中bean实例的上限是已知的。这种情况下,不指定缓存大小而让服务器使用默认值是完全可接受的,因为如果缓存没有被充分利用不会对内存造成影响。顺便指出,为不变化或不频繁变化的bean使用只读并发策略是一个不错的注意;这不但消除了不必要的数据库调用,还限制了与该bean的实体缓存中的实例具有同样PK的实例数(多版本是不是必要的),从而节省了内存,提高了性能。

    对于其中可同时访问的最大实例数未知或不能可靠地估计出来的bean,情况略微复杂些。您需要分析和估计从finder方法返回以及从一个事务内访问的最大bean数,然后乘以可同时发生的最大事务数(这通常受您应用程序入口点的最大实例数限制——会话bean和/或MDB)。这能粗略地估计出特定实体bean所需的最小缓存容量。

    应用程序级缓存

    如果您的应用程序使用了很多实体bean,那么分析和配置各个bean的缓存会很麻烦。估计从“master-detail”关系的“detail”端返回的bean实例数尤为困难——比如,如果您的应用程序在“订单”表上执行一个finder操作,每个订单都有一个“项目”数可变的集合。另一个问题是由于每个实体bean都有一个单独的缓存,内存没有得到最有效的利用。认识到“每个bean一个缓存”模型的限制,WebLogic Server(从版本7开始)开始支持实体bean的应用程序级缓存。这使得同一个J2EE应用程序中的多个实体bean共享一个运行时缓存。

    应用程序级缓存提供了下列好处:

    它减少了实体bean缓存的数目,从而减少了配置缓存的工作量。
    它充分利用了内存和堆空间,因为减少了分段。比如,如果某一特定EJB经历了一次突发活动,它可以使用联合缓存中可用的所有内存,而使用该缓存的其他EJB都被换出。如果两个EJB使用不同的缓存,当一个bean的缓存满时,容器就不能将EJB换出到另一个bean的缓存中,导致了内存浪费。
    它简化了管理;联合缓存使系统管理员只需调整单个缓存,而不是很多个缓存。
    它提供了更好的可伸缩性。

    为了定义应用程序级缓存,首先配置weblogic-application.xml中的entity-cache元素。然后在weblogic-ejb-jar.xml中引用entity-descriptor元素中entity-cache-ref元素中的应用程序级缓存。您可以定义一个缓存,然会将其用于应用程序中的所有实体bean,或者为bean组定义不同的缓存。也可以将应用程序级缓存与每个bean一个缓存混合使用,这样您就有很大的试验空间。我建议首先从被所有实体bean共享的应用程序级缓存开始,除非您有某些特殊需求。

    使用应用程序级缓存是为每个bean指定一个缓存的可行的替代方法。可引用单个缓存的不同实体bean的数目或者已定义缓存的数目没有限制。可根据bean实例数(与每个bean一个缓存类似的方法)或者最大内存空间来指定缓存大小。从管理角度来看,使用内存的大小很有吸引力,但是要知道WebLogic Server不计算缓存中bean消耗的实际内存数(这可能是一项代价很高的操作);它只是根据weblogic-ejb-jar.xml部署描述符中规定的bean平均大小来分割内存。如果没有指定大小,则假定每个bean平均有100字节。我认为根据bean实例数指定缓存大小会更透明。

    选择哪项策略?

    本文已经讨论了很多内容,但是还未提及可应用于CMP bean的优化技术。比如,CMR缓存和字段组在某些环境下也很有用。选择非常好的并发策略并充分利用长期缓存会给您的应用程序带来直接的性能提升。由于现在各个WebLogic Server版本中可用的选项有诸多不同(对于其他J2EE服务器也是如此),所以有时候很难在某一具体情况下做出选择,尤其是如果开发人员没有调整这些参数的经验的话。如果根本没有指定并发策略和缓存参数,WebLogic Server使用的默认设置从数据一致性方面考虑当然是不错的选择,但是从CMP bean的性能方面来看却不是非常好的选择。没有放之四海皆准的东西,所以如果您不确定的话,应该分析您的用例,然后利用不同的并发设置进行测试,然后再作出非常好的选择。接着,我会讨论一些基本用例以及它们的推荐设置。

    静态只读数据

    最可能的场景是,当数据库是静态(不随时间变化)、准静态(变化不频繁),或者从应用程序的角度来看可当作静态或准静态,并且您的应用程序不修改这些数据时。比如,数据可能被外部进程频繁更新,但是如果您的应用程序只是每分钟/小时/天看到这些更新,那么就没问题。这种情况下,使用带有适当read-timeout-seconds值的只读并发策略是合乎逻辑的。如果您的应用程序需要按照某种预定时间间隔看到更新,或者有一个批处理过程来加载数据,而你需要马上看到新数据,那么就可以像前面描述的那样,显式地禁用缓存。比如,您可以在应用程序的正面暴露一个“CMP缓存失效服务”,然后在批处理的最后或者从调度程序中调用。这种情况下缓存大小很容易计算,因为需要该缓存的所有事务共享同一个CMP实例,所以不需要考虑多版本及其对缓存大小的影响。要根据表大小、单个对象大小以及可用内存选择合理的缓存大小。

    Read-mostly数据

    也许最常见的情形是,数据被读取的频率大于其被更改的频率。这正是缓存可成功应用的场合。我建议使用启用了事务间缓存的乐观锁定。如果数据库模式可更改,我通常指定一个verify-columns的整型值,如果数据库模式不可更改,就指定一个Modified值。如果您决定使用一个版本列,那么要保证外部进程(如果有的话)在数据发生变更时遵守版本列更新的协定;否则,面临更新丢失的风险。

    从选择适当的缓存大小方面来看,应该考虑多版本化,以及从finder方法返回的最大bean数目。一个不错的上限估计方法是将应用程序需要同时处理的最大事务数乘以单个事务可以处理的最大bean数。我通常建议使用更加灵活的应用程序级缓存,因为通常不太可能所有的CMP都同时被使用。应用程序缓存对于所有CMP来说是全局的,会自适应不同bean的活动。如果您定义了一个过大的应用程序级缓存,可能会损害性能,因为所有事务都会串行访问这个唯一的缓存。对于大小适当的缓存来说极少出现这个问题,但是同样,在您不确定缓存大小如何影响性能时应该进行性能测试。顺便说一下,良好的设计实践是,避免创建返回任意多实体bean的finder方法(比如,大型表上的findAll()),因为这使得估计出适当的缓存大小变得几乎不可能。

    带有事务间缓存的乐观并发最适合有缓存碰撞“保护”的用例。比如,在一个项目用例中,应用程序需要处理传入消息(来自JMS)。每个消息记录需要在数据库表中创建,然后必须发送另一个消息作为响应;对于第二个消息来说,应用程序希望在一分钟内收到响应,并且在收到该响应时,同一条数据库记录得到更新。这时在该场景中应用缓存会带来巨大和直接的性能收益。我们“保证”每个被缓存的项目至少有一个碰撞,如果缓存对于保存CMP实例来说过大的话。

    另一个极端是目标表太大,以至于不可能对同一数据作出重复请求。从实践角度来看,这是不可行的,并且缓存这样的数据不能提高性能。

    在上述的read-mostly模式中,乐观并发模式应该是更好的选择。read-mostly模式不能用于集群中,不能防止出现更新丢失,并且一般来说不便于使用。本文讲述它是为了提供关于所有可用策略的整体情况,但是我不鼓励在现代应用程序中使用它。

    Read-mostly数据

    如果您的应用程序主要是插入或更新记录,那么缓存数据意义不大,因为几乎不会再次访问它们。在只进行插入操作(OLTP)的极端情况下,缓存反而会减慢处理速度。非重复性更新(对表中远超过缓存大小的随机行的更新)也很少从CMP缓存中受益。此外,随着更新数目相对于读取次数的增加,乐观并发策略的表现越来越差,因为会出现大量的乐观并发异常。实际上,如果您的应用程序只在数据库中更新和插入记录,就根本没有必要使用实体bean。

    结束语

    从本文的长度就可以看出,调整CMP 2.0 EJB有很多内容。我首先讲述了可用的各种并发策略。然后讨论了一些重要的性能策略:read-mostly模式,事务间缓存,以及选择非常好的缓存大小。最后,我提供了关于在何种情况下使用何种策略的指南。我希望这些分析能帮助你更好地理解EJB。

0
相关文章