缓存远程EJB 3.0引用

时间:2010-06-22 10:54:28

标签: java caching ejb-3.0

我在想如何通过jndi节省查找远程ejb参考的时间。我有一个需要快速工作的应用程序,但它也必须调用远程ejb,这会降低它的速度。

所以我的解决方案是这样的: 我使用apache commons-pool库并将其StackObjectPool实现用于我的远程ejb引用缓存。

private static final ObjectPool pool = new StackObjectPool(new RemoteEjbFactory());

工厂看起来像这样:

public static class RemoteEjbFactory extends BasePoolableObjectFactory {

    @Override
    public Object makeObject() {
        try {
            return ServiceLocator.lookup(jndi);
        } catch (NamingException e) {
            throw new ConfigurationException("Could not find remote ejb by given name", e);
        }
    }
}

然后我通过从池中借用它来获取对象(如果池中没有自由对象,它使用工厂创建一个):

SomeEjbRemote someEjb = null;
try {
        someEjb = (SomeEjbRemoteImpl) pool.borrowObject();
        someEjb.invokeRemoteMethod();
} catch (Throwable t) {
        if (someEjb != null) {
            pool.invalidateObject(someEjb);
        }
        pool.clear(); // Maybe its not neccessary
        someEjb = (SomeEjbRemoteImpl) pool.borrowObject();
        someEjb.invokeRemoteMethod(); // this time it should work
}

当然,在成功召唤之后将ejb返回游泳池

finally {
    try {
         pool.returnObject(someEjb);
    } catch (Exception e) {
        logger.error("Could not return object to pool.", e);
    }
}

据我所知,无法保证远程引用将保持连接,因此如果我们使用缓存的远程ejb捕获异常,我们只会使该对象无效并重试。

您如何看待这种方法?这是对的吗?也许其他一些解决方案,建议?

2 个答案:

答案 0 :(得分:5)

我正在回答JBoss AS,因为我对其他AS的经验有限:

远程JNDI引用只是(负载平衡)无连接代理(请参阅JBoss clustering proxy architecture)。序列化它们很好,这意味着您可以将它们保存为其他EJB中的成员,并像您一样缓存它们(我不知道您的池是否序列化了您的对象,某些缓存是否存在)。

关于代理无效: 代理将仅在方法调用期间打开连接,因此本身没有“连接”状态。代理还可以具有多个IP地址和负载平衡。在JBoss中,节点列表在每次方法调用时都会动态更新,因此引用过时的风险很小。如果所有节点都关闭或代理保持不活动状态而所有节点IP地址都过时,则仍有可能发生这种情况。根据池重用策略(LRU或其他?),其余缓存代理一旦出现无效的概率会有所不同。公平策略将最大限度地降低池中包含非常旧条目的风险,您希望在此方案中避免使用这些条目。

在制定公平政策的情况下,出于同样原因而变得陈旧的可能性会增加,而“一旦一个人过时的清晰汇集”政策就有意义了。此外,您需要考虑其他节点的情况。就像现在一样,你的算法将进入忙循环查找引用,而另一个节点关闭。我将为重试实现指数退避,或者只是将其视为致命的失败并使异常成为运行时异常,具体取决于您是否可以使用远程EJB消失一段时间。并使您捕获的异常(如RemoteCommunicationFailedException),避免捕获一般异常或错误,如异常,错误或Throwable。

您必须问自己的另一个问题是您想要的并发数量。通常,代理对于SLSB是线程安全的,而对于SFSB则是单线程。 SFSB本身不是线程安全的,并且SLSB默认情况下序列化访问。这意味着除非您启用对EJB 3.1 bean的并发访问(请参阅tss link),否则每个线程需要一个远程引用。即:池化N个SLSB远程引用将为您提供N个线程并发访问。如果启用并发访问并将SLSB编写为带有注释@ConcurrencyAttribute(NO_LOCK)的线程安全bean,则只需一个代理即可获得无限的并发,并删除整个池。你的选择。

编辑:

ewernli是对的,线程安全的SLSB代理在每次调用时在服务器上创建一个新实例。这在4.3.14中指定:

  

不需要任何限制   反对并发客户端访问   无状态会话bean因为   容器将每个请求路由到a   不状态的不同实例   会话bean类。

这意味着您根本不需要任何池。只需使用一个远程参考。

答案 1 :(得分:5)

来自规范

  

3.4.9 并发访问会话Bean参考

     

这是允许的   获取会话bean引用和   尝试调用相同的引用   对象同时来自多个   线程。但是,由此产生的客户端   每个线程上的行为取决于   。的并发语义   目标bean。有关详细信息,请参见第4.3.14节和第4.8.5节   会话bean的并发行为。

§4.3.14的摘要:

如果bean是 SLSB ,则每个调用都将由应用程序中的一个EJB提供。服务器池。该应用程序。服务器同步调用EJB实例,因此永远不会同时访问每个EJB实例。

对于 SFSB ,每次调用都会调度到一个特定的EJB实例和应用程序。服务器不同步通话。因此,对远程引用的两个并发调用可能会导致对EJB实例的并发访问,然后引发javax.ejb.ConcurrentAccessException。客户端负责正确同步对远程引用的访问。

§4.8.5是关于EJB单例的,可能不是你正在使用的。

我假设您使用SLSB,因此您不需要在客户端拥有池:查找远程bean一次,并使用来自多个线程的相同引用。

然而,您可以执行基准以查看使用多个引用是否可以提高性能,但是与远程调用本身的成本相比,增益(如果有)可能是可忽略的。

如果那时你仍然决定有多个远程引用,我会建议另一个设计。基于您的问题,我假设您有一个多线程应用程序。您可能已经为线程使用池,因此参考池可能是多余的。如果每个线程在创建时获得远程引用,并且线程被池化,则不会有那么多远程查找并且简化了设计。

我的2美分