I was thinking how could i save time on looking up remote ejb reference through jndi. I had an application that needed to work very fast, but it also had to call remote ejb which slowed it down.
So my solution was something like this: I took apache commons-pool library and used its StackObjectPool implementation for my remote ejb references cache.
private static final ObjectPool pool = new StackObjectPool(new RemoteEjbFactory());
Factory looks something like this:
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);
}
}
}
Then i take object by borrowing it from pool (if no free object in pool it uses factory to create one):
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
}
And of course returning ejb back to pool after successful invokacion
finally {
try {
pool.returnObject(someEjb);
} catch (Exception e) {
logger.error("Could not return object to pool.", e);
}
}
As i understand there is no guarantee that remote reference will stay connected so if we catch exception using cached remote ejb, we just invalidate that object and retry.
What do you think about such approach? Is it correct? Maybe some other solutions, advices?
From the spec
3.4.9 Concurrent Access to Session Bean References
It is permissable to acquire a session bean reference and attempt to invoke the same reference object concurrently from multiple threads. However, the resulting client behavior on each thread depends on the concurrency semantics of the target bean. See Section 4.3.14 and Section 4.8.5 for details of the concurrency behavior for session beans.
Summary of § 4.3.14:
If the bean is SLSB, each call will be served by one EJB in the app. server pool. The app. server synchronizes the calls the EJB instances, so each EJB instance are never accessed concurrently.
For SFSB, each call is dispatch to one specific EJB instance, and the app. server does not synchronises the call. So two concurrent calls to the remote reference might lead to a concurrent access to the EJB instance which raises then a javax.ejb.ConcurrentAccessException
. The client is responsible of the correct synchronization of the access to the remote reference.
And § 4.8.5 is about EJB singleton, probably not what you are using.
I assume you use SLSB, so you don't need to have a pool on the client-side: look up the remote bean once, and use the same reference from multiple threads.
You could however do a benchmark to see if using multiple reference improves performance, but the gain -- if any -- is probably neglectable compare to the cost of the remote invocation itself.
If then you still decide to have more than one remote reference, I would suggest an other design. Based on your question I assume you have a multi-threaded app. You probable already use a pool for the threads, so a pool for the reference is maybe redundant. If each thread gets a remote reference when it is created, and threads are pooled, there won't be that many remote lookup and the design is simplified.
My 2 cents
I'm answering for JBoss AS since I have limited experience with other AS:s.
Remote JNDI-references are just (load-balancing) connection-less proxies (see JBoss clustering proxy architecture). Serializing them is fine, which means that you can keep them as members in other EJBs, and cache them as you do (I don't know if your pool serializes your objects, some caches do).
Regarding invalidation of proxies: The proxies will open a connection only for the duration of the method call, and therefore does not have a 'connected' state per se. The proxies can additionally have multiple IP-addresses and load-balance. In JBoss the node list is dynamically updated at every method call, so the risk of a reference to go stale is small. Still there is a chance for this to happen if all nodes go down or the proxy remains inactive while all node IP addresses go stale. Depending on the pool reuse policy (LRU or other?) the the probability that the rest of the cached proxies are invalid once one is will vary. A fair policy will minimize the risk of having very old entries in the pool, which you would like to avoid in this scenario.
With a fair policy in place, the probability that all go stale for the same reason increases, and your 'clear pool once one is stale' policy would make sense. Additionally, you need to take the case of the other node being down into account. As it is now, your algorithm would go into a busy-loop looking up references while the other node is down. I would implement an exponential back-off for the retries, or just consider it a fatal failure and make the exception a runtime exception, depending on whether you can live with the remote EJB being gone for a while or not. And make the exception you catch specific (like RemoteCommunicationFailedException), avoid catching generic exceptions or errors like Exception, Error or Throwable.
Another question you must ask yourself is the amount of concurrency that you want. Normally proxies are thread-safe for SLSBs and single thread only for SFSBs. SFSBs themselves are not thread safe, and SLSBs serialize access per default. This means that unless you enable concurrent access to your EJB 3.1 beans (see tss link) you will need one remote reference per thread. That is: pooling N SLSB remote references will give you N threads concurrent access. If you enable concurrent access and write your SLSB as a thread-safe bean with the annotation @ConcurrencyAttribute(NO_LOCK)
you can get unlimited concurrency with just one proxy, and drop your whole pool. Your pick.
EDIT:
ewernli was right, the thread-safe SLSB proxy creates one new instance on the server per call. This is specified in 4.3.14:
There is no need for any restrictions against concurrent client access to stateless session beans because the container routes each request to a different instance of the stateless session bean class.
This means that you don't need any pool at all. Just use one remote ref.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With