Using a singleton service

Java EE provides singletons through the use of the @Singleton annotation. In practice, this annotation will guarantee that your class will be loaded only once per JVM or per application server instance. Although this is a powerful feature, if your business scenario can't cope with this behavior (duplicate component instances, one per JVM), you need another approach; the singleton service offered by WebLogic can offer an elegant solution for such cases. This feature guarantees that a given singleton class will have only one instance across a cluster, automatically managing failover and migration to another server instance in case of failure.

In the singleton ReservationCodeBean class there is a functionality that generates control numbers used to identify the reservations. The actual implementation is perfectly fine for a single server application, but running this application on multiple servers will end up creating several instances of ReservationCodeBean, one per JVM and each with its own counter variable, which may generate duplicate control numbers.

This functionality will be moved to a WebLogic singleton service, which will make it a cluster-wide singleton providing the necessary business function in a safe manner.

Creating a singleton service

To enable this new service, we're going to create a simple JAR file to hold it and then set it up as a domain library. Follow these steps:

  1. On Eclipse, create a new Java project named TokenService.
  2. Since this service will be remotely called through an RMI call, it's necessary to implement the java.rmi.Remote interface. Create a new Java Interface named ITokenService under the com.packt.store.services package using the following content:
    public interface ITokenService extends Remote {
      public static final String JNDI_ENTRY = "TokenService";
      public String generate() throws RemoteException;
    }
  3. It's also required to implement the weblogic.cluster.singleton.SingletonService interface so WebLogic can manage the class. Create a new Java class named TokenService under the same package, with the following content:
    public class TokenService implements ITokenService,SingletonService {
    
      // ideally would be moved to an external source, like a DB
      private int counter = 0; 
      
      private Logger logger = Logger.getLogger(TokenService.class.getCanonicalName());
      private SimpleDateFormat now = new SimpleDateFormat("yyyyMMdd-hhmmss");
    
      @Override
      public String generate() {
        return String.format("%1$s-%2$06d", now.format(new Date()), ++counter);
      }
    
      @Override
      public void activate() {
        logger.fine("Attempting to bind TokenService...");
    
        Context jndiCtx = null;
        try {
          jndiCtx = new InitialContext();
          jndiCtx.rebind(JNDI_ENTRY, this);
    
          logger.info("TokenService activated on this server.");
        } catch (NamingException e) {
          logger.severe("Error during TokenService activation: " + e.getMessage());
        } finally {
          if (jndiCtx != null)
            try {
              jndiCtx.close();
            } catch (NamingException e) {
              e.printStackTrace();
            }
        }
      }
    
      @Override
      public void deactivate() {
        logger.fine("Attempting to unbind TokenService...");
    
        Context jndiCtx = null;
        try {
          jndiCtx = new InitialContext();
          jndiCtx.unbind(JNDI_ENTRY);
    
          logger.fine("TokenService was deactivated on this server.");
        } catch (NamingException e) {
          e.printStackTrace();
        } finally {
          if (jndiCtx != null)
            try {
              jndiCtx.close();
            } catch (NamingException e) {
              e.printStackTrace();
            }
        }
      }

    Tip

    Besides the activate() and deactivate() methods, the content of the generate() method was extracted from ReservationCodeBean.

  4. Some of the required classes are missing on the classpath. To fix it, add weblogic.jar into the build path of the application. The file can be found under $MW_HOME/wlserver/server/lib.
  5. Export this Java project as a JAR file using Eclipse. This project doesn't have any special requirements like a MANIFEST file does, for example. Save the JAR file as TokenService.jar.
  6. The file must be available to the WebLogic classpath, so copy TokenService.jar into the tickets domain /lib folder.

    Tip

    If you are running the solution using different physical machines, the JAR file must be physically copied to all of them.

  7. Restart all running WebLogic servers so they can load the singleton class into the server classloader.
  8. Open the WebLogic console at http://localhost:7001/console.
  9. Under domain structure (left menu) expand Environment and then click on Clusters.
  10. Click on ticketsCluster, browse to the Migration tab, and change the Migration Basis field to Consensus.

    Note

    When running services or applications on a cluster, WebLogic tries to balance instances of these subsystems into all server instances of such a cluster. There are some other services that must run pinned to a single instance and, depending on the runtime situation, they migrate these services to a different server. But, there must be a way to store and keep the information about who owns that particular component; to avoid a Single Point Of Failure (SPOF), WebLogic offers two options for leasing these services: Database leasing and Consensus leasing. Database leasing would require a High Availability (HA) database, such as Oracle RAC, but for our examples we're going to use Consensus, which is basically a less sophisticated mechanism that relies on Node Manager information about the health of our servers.

  11. Still on the ticketsCluster page, click on the Singleton Services tab.
  12. Click on the New button and use the following values:
    • Singleton Service Name: TokenService
    • Class Name: com.packt.store.services.TokenService
  13. Click on Next and, in the next screen, select which server will be the preferred or primary server for this service. Every time the environment is restarted, the service will bind to this selected server. Select ticketMS_A, as shown in the following screenshot:
    Creating a singleton service
  14. Click on Finish. You should have the following singleton service settings:
    Creating a singleton service

At this point, you can check the ticketMS_A log file and you should see the activation log message indicating that the configuration was successful and that the service is currently running on this server. In order to test the failover capabilities, kill or shutdown the ticketMS_A Managed Server and monitor the ticketMS_B log.

Jun 10, 2013 1:56:32 PM com.packt.store.services.TokenService activate
INFO: ReservationService activated on this server.

Adjusting the service client

Now, we need to modify the ReservationCodeBean class to consume the singleton service. This is a very straightforward process:

  1. Open Eclipse, right-click on the project the Store, and select Properties from the context menu.
  2. Click on Java Build Path on the left of the window and then click on the Libraries tab on the right side of the window.
  3. Click on Add External JARs… and look for the TokenService.jar file.
  4. Click on OK to confirm and close the Properties window.
  5. Open the ReservationCodeBean class and create an attribute as follows:
    ITokenService tokenService;
  6. Still in the class, add the following method:
      private String getControlNumber() throws Exception {
        if (tokenService == null) {
          try {
            Context ctx = new InitialContext();
            tokenService = (ITokenService) ctx.lookup(TokenService.JNDI_ENTRY);
          } catch (NamingException ex) {
            ex.printStackTrace();
            throw new Exception("Control number was not generated!");
          }
        }
    
        return tokenService.generate();
      }
  7. Now, in the generate() method, replace the code at the control variable with a call to getControlNumber():
    String control = this.getControlNumber();
  8. Save and close the file.
  9. Publish the Store web application.

At this point, the Store web application, deployed on multiple nodes, will consume the singleton service TokenService that is primarily hosted on ticketMS_A Managed Server. If ticketMS_A goes offline due to a failure or simply during a normal shutdown process, the service will be migrated to ticketMS_B automatically and any new request to the bean will be able to find it, even if the bean is on a different server now.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.174.0