DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Low-Code Development: Leverage low and no code to streamline your workflow so that you can focus on higher priorities.

DZone Security Research: Tell us your top security strategies in 2024, influence our research, and enter for a chance to win $!

Launch your software development career: Dive head first into the SDLC and learn how to build high-quality software and teams.

Open Source Migration Practices and Patterns: Explore key traits of migrating open-source software and its impact on software development.

Related

  • Getting Started With NCache Java Edition (Using Docker)
  • Harnessing the Power of SIMD With Java Vector API
  • Scaling Java Microservices to Extreme Performance Using NCache
  • Understanding Lazy Evaluation in Java Streams

Trending

  • A Complete Guide To Implementing GraphQL for Java
  • Essential Monitoring Tools, Troubleshooting Techniques, and Best Practices for Atlassian Tools Administrators
  • Linting Excellence: How Black, isort, and Ruff Elevate Python Code Quality
  • Open-Source Dapr for Spring Boot Developers
  1. DZone
  2. Coding
  3. Java
  4. Java Caching Strategies With NCache

Java Caching Strategies With NCache

Explore the open-source library NCache and discover how it can help implement various caching strategies in Java applications.

By 
Abhinav Pandey user avatar
Abhinav Pandey
·
Apr. 23, 24 · Tutorial
Like (1)
Save
Tweet
Share
1.5K Views

Join the DZone community and get the full member experience.

Join For Free

Caching is a technique to store frequently accessed data in a temporary storage area. This helps to reduce the load on the primary data source and improves the performance of the application. There are various caching strategies available, and choosing the right one is crucial.

In this article, we'll explore the open-source library, NCache, and see how it can help implement different caching strategies in Java applications.

Introduction to NCache

NCache is an open-source distributed caching solution that helps improve the performance and scalability of applications. It provides features like data caching, session caching, and object caching to store data in memory and reduce the load on the primary data source. 

NCache supports various technologies like Java, .NET, and Node.js, making it a versatile choice for caching in different types of applications.

Installing NCache

Before installing NCache, it's important to understand the system prerequisites.

Let's go over a few ways to install NCache on a machine running Java:

  • Windows — NCache provides a Windows installer that helps us install and configure NCache using a GUI. Alternatively, we can use a CLI to install NCache.
  • Linux — For Linux, NCache provides a tar.gz file-based installation.

Docker Image

NCache also provides a docker image to enable easy installation across systems. We can use the below commands to install NCache with Docker.

Pulling an Image

Shell
 
docker pull alachisoft/ncache:latest-java 


Starting NCache

Shell
 
docker run --name ncache -itd --network host alachisoft/ncache:latest-java
  • -itd starts the container in detached mode and opens it for interaction through a terminal.
  • --network host tells the host that the container will connect directly with the host's network.

Interacting With the Container

To interact with the application using a bash terminal, we can use:

Shell
 
docker exec -it ncache bash


Similarly, to use Powershell on Windows, we can use:

Shell
 
docker exec -it ncache PowerShell


Using NCache in Java

NCache provides a Java client library that can be used to interact with the NCache server and perform caching operations. Let's look at how we can use NCache in Java applications. 

Before we start coding on the client side, we'll need to set up a cache using NCache.

Adding NCache Dependency

We'll start by adding the NCache dependency to our project. Here's an example of how to add the NCache dependency using Maven:

XML
 
<dependency>
    <groupId>com.alachisoft.ncache</groupId>
    <artifactId>ncache-client</artifactId>
    <version>5.3.3</version>
</dependency>


Please note: The version number may vary based on the latest release.

Code Setup

We'll set up a simple Java application with a data layer, service layer, and caching layer to demonstrate the usage of NCache. Using this example, we'll explore different caching patterns and how they can be implemented using NCache.

Let's define a simple POJO class to represent a user:

Java
 
public class User {
    private int id;
    private String name;
    // getters, setters, and constructors
}


Next, we'll create a database layer that simulates interactions with a database:

Java
 
public class DatabaseService {
    public User getUser(int id) {
        // Simulate fetching user data from the database
        return new User(id, "John Doe");
    }
    // other database operations
}


We'll not focus on the actual database operations, as the focus of this article is on caching. To demonstrate the caching patterns, we'll create a service in the coming sections that interacts with the caching layer and the data layer(if needed).

Connecting to NCache

To connect to the NCache server, we can follow one of the below methods:

Using Config File

We can define connection properties in a configuration file and use it to connect to the NCache server. Here's an example of how to define the connection properties in a client.ncconf file:

XML
 
<configuration>
  <ncache-server connection-retries="5" retry-connection-delay="0" retry-interval="1" command-retries="3" command-retry-interval="0.1" client-request-timeout="90" connection-timeout="5" port="9800" local-server-ip="20.200.20.38" enable-keep-alive="true" keep-alive-interval="30" />
  <cache id="demoCache" client-cache-id="" client-cache-syncmode="optimistic" default-readthru-provider="" default-writethru-provider="" load-balance="True" enable-client-logs="False" log-level="error">
    <server name="20.200.20.38"/>
    <server name="20.200.20.23"/>
  </cache>
</configuration>


Let's look at a few important attributes in the configuration file:

  • ncache-server: Defines the connection properties like connection retries, retry interval, and timeouts.
  • cache: Defines the cache properties like cache ID, synchronization mode, and server details.
  • cache/server: Specifies the server IP or hostname where the cache is running.

This file should be placed either in the application folder or at %NCHOME%\config in Windows or /opt/ncache/config in Linux.

Using Code

Alternatively, we can connect to the NCache server programmatically by specifying the connection properties in the code:

Java
 
public class NCacheService {
    private final Cache cache;

    public NCacheService() throws Exception {
        CacheConnectionOptions cacheConnectionOptions = new CacheConnectionOptions();
        cacheConnectionOptions.UserCredentials = new Credentials("domain\\user-id", "password");
        cacheConnectionOptions.ServerList = new List<ServerInfo>() {
            new ServerInfo("remoteServer",9800);
        };
        cache = CacheManager.GetCache("demoCache", cacheConnectionOptions);
    }
}


Here, we have a small code snippet that connects to the NCache server using the CacheConnectionOptions class. We specify the server details and user credentials to connect to the cache. Similarly, we can provide all properties that are available in the configuration file programmatically.

Please note that if both the configuration file and code-based connection properties are provided, the code-based properties take precedence. 

Code-based connection is useful when we need to dynamically change the connection properties based on the environment or other factors. Configuration files are more suitable when the connection properties are static and do not change frequently.

Caching Patterns

Caching patterns define how data is stored and retrieved from the cache. Let's explore some common caching patterns and how they can be implemented using NCache.

Cache-Aside

One of the simplest caching patterns is the Cache-Aside pattern. In this pattern, the application code is responsible for checking the cache before accessing the primary data source. If the data is not found in the cache, it is fetched from the primary data source and stored in the cache for future access.

UML diagram of reading data using the cache aside pattern

Fig 1. Reading Data Using Cache Aside Pattern


To demonstrate this, let's define a service class that implements the Cache-Aside pattern using NCache:

Java
 
public class CacheAsideService {
    private final DatabaseService databaseService;
    private final CacheService cacheService;

    public CacheAsideService() {
        databaseService = new DatabaseService();
        cacheService = new NCacheService();
    }

    public User getUser(int id) {
        User user = cacheService.getUser(id);
        if (user == null) {
            user = databaseService.getUser(id);
            cacheService.addUser(user);
        }
        return user;
    }
        public void addUser(User user) {
        databaseService.addUser(user);
        cacheService.addUser(user); //optional
    }
    // other service methods
}


To demonstrate read operations, we have the getUser method:

  • It checks the cache for the user data.
  • If the data is found in the cache, it returns the data.
  • If the data is not found in the cache, it fetches the data from the database and stores it in the cache. It then returns the data.

Next, we have the addUser method:

  • It adds the user data to the database.
  • Next, it adds the user data to the cache. This is optional and depends on the use case. Since caches have limited capacity, it's important to decide whether it's essential to write data to the cache immediately or let it be populated on demand.

Read-Through

The Read-Through pattern is a caching pattern where the primary data source resides behind the cache. When a cache miss occurs, the cache fetches the data from the primary data source and stores it in the cache for future access. In this pattern, the code does not interact directly with the primary data source.

Reading Data Using Read-Through Cache

Fig 2. Reading Data Using Read-Through Cache


To enable this behavior in NCache, the cache server needs to be configured to fetch the data from the primary data source when a cache miss occurs. This is handled by configuring a Read-Through provider that specifies how to fetch the data from the primary data source.

From the code perspective, the Read-Through pattern is transparent to the application code. The cache handles the data fetching and storing operations. Let's see how the code changes for the Read-Through pattern:

Java
 
public class ReadThruCacheService {

    private final DatabaseService databaseService;
    private final Cache cache;

    private final ReadThruOptions readThruOptions = new ReadThruOptions(ReadMode.ReadThru, "provider-name");

    public ReadThruCacheService() throws Exception {
        databaseService = new DatabaseService();
        cache = CacheManager.getCache("mycache");
    }

    public User getUser(String id) throws CacheException {
        User user = cache.get(id, readThruOptions, User.class);
        if (user == null) {
            user = databaseService.getUser(id);
            cache.add(id, new CacheItem(user));
        }
        return user;
    }
        // other service methods
}


In this code snippet, we define a ReadThruCacheService class that interacts with the cache using the Read-Through pattern.

  • We specify the Read-Through options that define the behavior of the cache when a cache miss occurs. It requires a provider name that identifies the Read-Through provider configured in the cache server.
  • When fetching the user data, we use the cache.get method with the Read-Through options. If the data is not found in the cache, the cache fetches the data from the primary data source using the configured Read-Through provider.

Write-Through/Write-Behind

The Write-Through pattern is used to synchronize the cache with the primary data source when data is updated. When a write operation occurs:

  • the code calls the cache to update the data,
  • the cache updates the primary data source first,
  • if the primary data source update is successful, the cache updates the data in the cache.

Writing Data Using Write-Through Cache

Fig 3. Writing Data Using Write-Through Cache


Since this process is synchronous, it can impact the application's performance. It is recommended to use the Write-Through pattern for critical data that needs to be consistent across the cache and the primary data source. If performance is a concern, the Write-Behind pattern can be used. In this pattern, the cache updates the primary data source asynchronously, reducing the impact on the application's performance.

To implement the Write-Through pattern in NCache, we need to configure a Write-Through provider that specifies how to update the primary data source when a write operation occurs. Similarly, to use asynchronous updates, we can configure a Write-Behind provider that enables asynchronous updates.

Once we have the providers configured, let's look at the code changes for the Write-Through pattern:

Java
 
public class WriteThruCacheService {

    private final DatabaseService databaseService;
    private final Cache cache;

    private final WriteThruOptions writeThruOptions = new WriteThruOptions(WriteMode.WriteThru, "provider-name");

    public WriteThruCacheService() throws Exception {
        databaseService = new DatabaseService();
        cache = CacheManager.getCache("mycache");
    }

    public void addUser(User user) throws CacheException {
        cache.add(user.getId(), new CacheItem(user), writeThruOptions);
    }
        // other service methods
}


We provide WriteThruOptions to the cache when adding/updating data. The cache then updates the primary data source using the configured Write-Through provider. Here, we have used the WriteMode.WriteThru option, which ensures that the cache updates the primary data source synchronously. Similarly, we can use the WriteMode.WriteBehind option for asynchronous updates. 

Comparing Caching Patterns

Now that we have explored different caching patterns and how they can be implemented using NCache, let's compare them based on their use cases, pros, and cons:

Pattern Use Case Pros Cons
Cache-Aside Frequently accessed data Simple to implement Cache updates and eviction needs to be handled through the client code
Read-Through Data that is read more often than written Transparent to application code Performance impact on cache misses
Write-Through Critical data that needs to be consistent Ensures data consistency across cache and primary data source Synchronous updates can impact performance
Write-Behind Performance-critical applications Asynchronous updates reducthe e impact on performance Data may be inconsistent temporarily

Conclusion

Choosing the right caching pattern is crucial for the performance and scalability of applications. NCache provides a robust caching solution that supports various caching patterns like Cache-Aside, Read-Through, Write-Through, and Write-Behind. 

In this article, we explored how these caching patterns can be implemented using NCache in Java applications. By understanding the use cases, pros, and cons of each pattern, developers can make informed decisions on selecting the appropriate caching strategy for their applications.

Cache (computing) Java (programming language) Java performance

Opinions expressed by DZone contributors are their own.

Related

  • Getting Started With NCache Java Edition (Using Docker)
  • Harnessing the Power of SIMD With Java Vector API
  • Scaling Java Microservices to Extreme Performance Using NCache
  • Understanding Lazy Evaluation in Java Streams

Partner Resources


Comments

ABOUT US

  • About DZone
  • Send feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: