1. In a distributed environment, deploying a user-defined data structure as cluster wide service can provide access to all its operations, from anywhere in the grid. For example, you can have your own implementation of a distributed SecureMap, which auto-encrypts its values and is deployed as a service on all the cluster nodes.

    In GridGain, you can implement your own custom data structures and deploy them as distributed services on the grid. You can also access them from clients via service proxies which connect to remotely deployed distributed services. This allows to invoke your own services from anywhere, regardless of the deployment topology, be that a cluster-singleton, a node singleton, or any other deployment.

    As an example, let's define a simple counter service as MyCounterService interface.

    public class MyCounterService {
        /**
         * Increment counter value and return the new value.
         */
        int increment() throws GridException;
         
        /**
         * Get current counter value.
         */
        int get() throws GridException;
    }
    

    An implementation of a distributed service has to implement both, GridService and MyCounterService interfaces.

    The easiest way to implement our counter service is to store the counter value in a GridGain distributed cache. The key for this counter value is the name of the service. This allows us to create multiple instances of this counter service with different names, hence having different counters within the grid.

    public class MyCounterServiceImpl implements GridService, MyCounterService {
        private static final long serialVersionUID = 0L;
        
        // Auto-inject grid instance. 
        @GridInstanceResource
        private Grid grid;
        
        /** Instance of distributed cache. */
        private GridCache<String, Integer> cache;
     
        /** Service name. */
        private String name;
     
        /**
         * Service initialization.
         */
        @Override public void init(GridServiceContext ctx) throws Exception {      
            cache = grid.cache("myCounterCache");
     
            name = ctx.name();
             
            System.out.println("Service was initialized: " + ctx.name());
        }
     
        /**
         * Cancel this service.
         */
        @Override public void cancel(GridServiceContext ctx) {
            System.out.println("Service was cancelled: " + ctx.name());
        }
        
        /**
         * Start service execution.
         */
        @Override public void execute(GridServiceContext ctx) throws Exception {
            // Our counter service does not need to have any execution logic
            // of its own, as it is only accessed via MyCounterService public API.
            System.out.println("Executing distributed service: " + ctx.name());
        }
      
        /**
         * Get current counter value.
         */
        @Override public int get() throws GridException {
            Integer i = cache.get(name);
    
            // If there is no counter, then we return 0.  
            return i == null ? 0 : i;
        }
     
        /**
         * Increment our counter. 
         */
        @Override public int increment() throws GridException {
            // Since the cache is partitioned, 'transformAndCompute(...)' method
            // ensures that the closure will execute on the cluster member where 
            // GridGain caches our counter value.
            return cache.transformAndCompute(name, new MyTransformClosure());
        }
     
        /**
         * GridGain transform closure which increments the value
         * currently stored in cache.
         */
        private static class MyTransformClosure implements GridClosure<Integer, GridBiTuple<Integer, Integer>> {
            @Override public GridBiTuple<Integer, Integer> apply(Integer i) {
                int newVal = i == null ? 1 : i + 1;
     
                // First value in the tuple is the new value to store in cache,
                // and the 2nd value is to be returned from 'transformAndCompute(...)' call.
                // In our case, both values are the same.
                return new GridBiTuple<>(1, 1);
            }      
        }
    }
    

    We can now create a service proxy and invoke our distributed service.

    try (Grid g = GridGain.start("examples/config/example-cache.xml")) {
        //Get an instance of GridServices for remote nodes.
        GridServices svcs = grid.forRemotes().services();
        try {
             // Deploy node singleton. An instance of the service
             // will be deployed on every remote cluster node.
             svcs.deployNodeSingleton("myCounterService", new MyCounterServiceImpl()).get();
    
             // Get service proxy for remotely deployed service.
             // Since service was deployed on all remote nodes, our 
             // proxy is *not sticky* and will load-balance service 
             // invocations across all remote nodes in the cluster.
             MyCounterService cntrSvc = grid.services().
                serviceProxy("myCounterService", MyCounterService.class, /*not-sticky*/false);
    
             // Invoke a remote distributed counter service.
             cntrSvc.increment();
    
             // Print latest counter value from a remote counter service.
             System.out.println("Incremented value : " + cntrSvc.get());
        }
        finally {
            // Undeploy all services.
            grid.services().cancelAll();
        }
    }
    

    In the above example, cntrSvc is a proxy for the remotely deployed service, myCounterService. You can find more information about GridGain distributed services here.



  2. When storing data in a distributed cache, Map is the most obvious data structure. But, there are times when applications need to process data in the order it is received. GridGain In-Memory Data Fabric, in addition to providing standard key-value map-like storage, has an implementation of fast Distributed Blocking Queue.

    As an implementation of java.util.concurrent.BlockingQueue, GridGain Distributed Queue also supports all operations from java.util.Collection interface. Distributed Queues can be created in either collocated or non-collocated mode.

    Collocated queues are best suited when you have many small-sized queues. In this mode, you can have many queues, with all elements for each queue cached on the same node, making contains(…), get(…), and iterate(…) operations fast. The only constraint is that data should fit in memory allocated for a single node unless you configure GridGain to evict data to off-heap memory or disk.

    Non-collocated queues, on the other hand, are useful when you have large unbounded queues. Queue elements are distributed across all nodes in the cluster, allowing to utilize memory available across all the nodes for queue entries. However, certain operations, like iterate(…), can be slower since it requires going through multiple cluster nodes.

    Here is a simple example of how to create a queue in GridGain:

    try (Grid g = GridGain.start("examples/config/example-cache.xml")) {  
        // Initialize new FIFO queue.
        GridCacheQueue<String> queue = g.cache("partitioned_tx").dataStructures().queue(
                    "myQueue",     // Queue name.
                    20,            // Queue capacity. 0 for unbounded queue.
                    true,          // Collocated.
                    true           // Create if it does not exist.
                 );
     
        // Put items in queue.
        for (int i = 0; i < queue.capacity(); i++)
            queue.put(Integer.toString(i));
     
        // Read items from queue.
        for (int i = 0; i < queue.size(); i++)
            System.out.println("Queue item read from queue head: " + queue.take());
    }
    

    For more information on Distributed Queues you can refer to GridGain examples and documentation. 



  3. Failing-over web session caching is problematic when you run multiple application servers. It is not uncommon for web applications to run in a cluster to distribute the load of high volume of web requests. But what if one of the application servers crashes? The load balancer will just route the web request to another available application server, but all of user’s session data is lost. In simple words, you may be filling your shopping cart with your favorite items, but if the application server serving your request crashes, you will end up with an empty cart.

    A feasible solution here would be to cache all your web sessions in GridGain cache. GridGain In-Memory Data Fabric WebSessions Cache is a distributed cache that maintains a copy of all web sessions’ data in memory.


                                       
    So, when an application server fails, web requests get routed to some other application server that simply fetches the web session from GridGain cache.

    This process happens in the background and is so seamless that it does not affect the users’ experience. Not only that, GridGain also ensures fault tolerance by either replicating or partitioning the data, which is easily configurable, across all grid nodes in the cluster. And so, no session data is lost.

    Moreover, a web request can now be sent to any active application server, that can access the session data from GridGain cluster, and so, you may choose to turn off the Sticky Connections support of the load balancer.

    With just a few simple steps you can enable web sessions caching with GridGain in your application. All you need to do is:

    1.     Download GridGain and add the following jars to your application’s classpath:
    ·       gridgain.jar
    ·       gridgain-web.jar
    ·       gridgain-log4j.jar
    ·       gridgain-spring.jar

    Or, if you have a Maven based project, add the following to your application's pom.xml

    <dependency>
          <groupId>org.gridgain</groupId>
          <artifactId>gridgain-fabric</artifactId>
          <version> ${gridgain.version}</version>
          <type>pom</type>
    </dependency>

    <dependency>
        <groupId>org.gridgain</groupId>
        <artifactId>gridgain-web</artifactId>
        <version> ${gridgain.version}</version>
    </dependency>

    <dependency>
        <groupId>org.gridgain</groupId>
        <artifactId>gridgain-log4j</artifactId>
        <version>${gridgain.version}</version>
    </dependency>
                  Make sure to replace  ${gridgain.version}with actual GridGain version.
         
    2.     Configure GridGain cache in either PARTITIONED mode

    <bean class="org.gridgain.grid.cache.GridCacheConfiguration">
        <!-- Cache name. -->
        <property name="name" value="partitioned"/>
       
        <!-- Cache mode. -->
        <property name="cacheMode" value="PARTITIONED"/>
        <property name="backups" value="1"/>
        ...
    </bean>

    or REPLICATED mode

    <bean class="org.gridgain.grid.cache.GridCacheConfiguration">
        <!-- Cache name. -->
        <property name="name" value="replicated"/>
       
        <!-- Cache mode. -->
        <property name="cacheMode" value="REPLICATED"/>
        ...
    </bean>

    You can also choose to use the default cache configuration, specified in GRIDGAIN_HOME/config/default-config.xml, shipped with GridGain installation.

    3.     Declare a context listener in the application’s web.xml.
    ...

    <listener>
       <listener-class>org.gridgain.grid.startup.servlet.GridServletContextListenerStartup</listener-class>
    </listener>

    <filter>
       <filter-name>GridGainWebSessionsFilter</filter-name>
       <filter-class>org.gridgain.grid.cache.websession.GridWebSessionFilter</filter-class>
    </filter>

    <!-- You can also specify a custom URL pattern. -->
    <filter-mapping>
       <filter-name>GridGainWebSessionsFilter</filter-name>
       <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- Specify GridGain configuration (relative to META-INF folder or GRIDGAIN_HOME). -->
    <context-param>
       <param-name>GridGainConfigurationFilePath</param-name>
       <param-value>config/default-config.xml </param-value>
    </context-param>

    <!-- Specify the name of GridGain cache for web sessions. -->
    <context-param>
       <param-name>GridGainWebSessionsCacheName</param-name>
       <param-value>partitioned</param-value>
    </context-param>

    ...

    4.     Optional – Set eviction policy for stale web sessions data lying in cache.

    <bean class="org.gridgain.grid.cache.GridCacheConfiguration">
        <!-- Cache name. -->
        <property name="name" value="session-cache"/>

        <!-- Set up LRU eviction policy with 10000 sessions limit. -->
        <property name="evictionPolicy">
            <bean class="org.gridgain.grid.cache.eviction.lru.GridCacheLruEvictionPolicy">
                <property name="maxSize" value="10000"/>
            </bean>
        </property>
        ...
    </bean> 


    Conclusion

    The main advantage of GridGain web sessions caching is that it ensures that the user session data is always available no matter which application server the user’s web request is routed to. Sticky Connections support is also not required since the web sessions’ data is now available to all application servers.

    Another advantage is that in GridGain, data is always stored in memory vs. maintaining a copy of sessions’ data on disk. Therefore, the performance of your application does not get compromised while recovering the session data owned by the failed application server. 

Blog Archive
Loading
Dynamic Views theme. Powered by Blogger.