Introduction to Clustered Singleton

Photo of Lenny Primak by Lenny Primak

In a clustered environment, it is sometimes necessary to limit to one instance of an EJB or CDI bean for an entire cluster.

  Payara Server & Payara Micro  Download Here 

 

Currently in Java EE specification (soon to be Jakarta EE), there is no way to accomplish this. Payara Server and Payara Micro's Clustered Singleton feature aims to easily fill in this gap.

 

How to Implement Clustered Singleton

The code below demonstrates simple clustered singleton for both EJB and CDI beans:

 
Simple Clustered Singleton
import fish.payara.cluster.Clustered;
import javax.ejb.Singleton;
import javax.enterprise.context.ApplicationScoped;
 
@Clustered @Singleton
public class MyClusteredSingletonEJB implements Serializable { /* ... */ }
 
@Clustered @ApplicationScoped
public class MyClusteredSingletonCDI implements Serializable { /* ... */ }

 

How to Make an Existing Singleton Clustered

  • Add @Clustered to @Singleton or @ApplicationScoped bean
  • Make the bean Serializable

Availability / Payara Platform Versions

Clustered Singletons are available in the Payara Platform 4.182 and Payara Platform 5.181 and later.

 

How it Works

The @Singleton / @ApplicationScoped bean is deployed and is available to all members in the cluster.  Prior to every method invocation, latest version of the bean is retrieved from a Hazelcast map.  After every method invocation, possibly updated version of the bean is stored in a Hazelcast map.  During this process, a Hazelcast distributed lock for the bean is optionally acquired, so there is only one method call can be executed in a particular cluster at-a-time, so no data corruption / race conditions are possible.

 

Differences From Other Vendor's Implementations

The main difference is that the Payara Platform Clustered Singleton implementation stores the bean in Hazelcast map across the cluster. This makes it easier to manage and eliminates the need to administer nodes that the bean is deployed to, and managing primary / backup nodes is not necessary with the Payara Platform. Other vendors have configuration requirements where to deploy the singleton, and a list of backup nodes that requires management.

 

Examples and Documentation

Examples can be found here: clustered-singleton

Documentation can be found here: https://docs.payara.fish/documentation/payara-server/public-api/clustered-singleton.html

 

Locking and Distributed Locking

Annotation Parameter
Default
Description
@Clustered(lock = DistributedLockType.INHERIT) Yes For EJBs, this setting enables the lock by default, or follows the @ConcurrencyManagement setting for the bean
@Clustered(lock = DistributedLockType.LOCK) No distributed lock is enabled, not applicable to EJBs
@Clustered(lock = DistributedLockType.LOCK_NONE) No distributed lock is disabled


Other @Clustered Annotation Parameters

Annotation Parameter
Default
Description
@Clustered(callPostConstructOnAttach = true|false) true Controls whether more than one instance calls postConstruct()
@Clustered(callPreDestoyOnDetach = true|false) true controls whether more than one instance calls preDestroy()
@Clustered(keyName = "XXX") bean name sets the key for the Hazelcast map under which the bean is stored

 

@Clustered and EJB Timers

With persistent timers (default), they execute only on a single node in a cluster, so they work correctly with Clustered Singletons

 

@Clustered and @Startup / @Initialized @ApplicationScoped

Care must be taken in this scenario. By default, @PostConsctuct is called for every instance in the cluster for these beans. This behavior needs to be disabled by using @Clustered(callPostConstructOnAttach = false) and possibly callPreDestoyOnDetach = false as well, so only the @PostConstruct method will be called only once per cluster. @Observed @Initialized methods are called for every instance in the cluster. The only valid method for cluster-wide initialization is @PostConstruct method configured as described above.

 

@Clustered and Transactions

Transactions behave the same way for Clustered beans as non-clustered. Since all calls are local, and locks work in the same way, there is no behavioural differences.

 

CDI Considerations and Notes

Since there are no default locks for CDI, @Clustered CDI @ApplicationScoped beans do not have distributed locks on by default.
It is highly recommended to enable distributed locks for CDI Clustered beans: @Clustered(lock = DistributedLockType.LOCK) so there is no chance of race condition / data corruption for CDI Clustered beans.

 

Requirements and Assumptions

  • Hazelcast is required to be enabled (default on Payara Platform 5)
  • @Clustered only works with @ApplicationScoped or @Singleton annotation, and in no other circumstance
  • All fields in the @Clustered @Singleton class need to be correctly Serializable
  • Works correctly only if accessed via @Injected proxy, not through direct access
  • Works with EJB timers correctly only if all timers are persistent (default)
  • All method calls to @Clustered beans operate on a local instance
  • Data corruption is possible if distributed lock is turned off and there is a race condition between method calls to the same bean on multiple cluster nodes simultaneously. In such a case, last method call wins the singleton state
  • Clustered singletons are stored in Hazelcast map under "Payara/(EJB/CDI)/singleton/<singletonComponentName>/[lock]"

EJB XML configuration

glassfish-ejb-jar.xml have additional elements described below. The elements have the same meaning and work the same way as annotation parameters described above.

Clustered Singleton elements are added to glassfish-ejb-jar.xml as follows:

 

<glassfish-ejb-jar>
    <enterprise-beans>
        <ejb>
        ... additional elements ...
        </ejb>
    </enterprise-beans>
</glassfish-ejb-jar>

 

Additional elements:

<clustered-bean>true|false</clustered-bean>
<clustered-key-name>key-name</clustered-key-name>
<clustered-lock-type>inherit|none</clustered-lock-type>
<clustered-attach-postconstruct>true|false</clustered-attach-postconstruct>
<clustered-detach-predestroy>true|false</clustered-detach-predestroy>

Comments