当前位置:Java -> 使用NCache的Java缓存策略

使用NCache的Java缓存策略

缓存是一种将频繁访问的数据存储在临时存储区域的技术。这有助于减少主数据源上的负载,并提高应用程序的性能。有各种不同的缓存策略可供选择,选择正确的策略至关重要。

在本文中,我们将探讨开源库 NCache,并看看它如何帮助在Java应用程序中实现不同的缓存策略。

NCache简介

NCache是一个开源的分布式缓存解决方案,可以帮助改善应用程序的性能和可扩展性。它提供数据缓存、会话缓存和对象缓存等功能,以将数据存储在内存中,减少对主数据源的负载。

NCache支持Java、.NET和Node.js等各种技术,使其成为不同类型应用程序中缓存的多功能选择。

安装NCache

在安装NCache之前,了解系统先决条件非常重要。

让我们看看如何在运行Java的计算机上安装NCache的几种方法:

Docker镜像

NCache还提供一个Docker镜像,以方便在各个系统中进行安装。我们可以使用以下命令来使用Docker安装NCache

拉取镜像

docker pull alachisoft/ncache:latest-java 


启动NCache

docker run --name ncache -itd --network host alachisoft/ncache:latest-java
  • -itd 将容器以分离模式启动,并通过终端打开进行交互。
  • --network host 告诉主机容器将直接连接到主机的网络。

与容器交互

要使用bash终端与应用程序交互,我们可以使用:

docker exec -it ncache bash


类似地,在Windows上使用Powershell,我们可以使用:

docker exec -it ncache PowerShell


在Java中使用NCache

NCache提供一个Java客户端库,可用于与NCache服务器进行交互并执行缓存操作。让我们看看如何在Java应用程序中使用NCache。

在客户端端编码之前,我们需要先使用NCache设置缓存

添加NCache依赖项

我们将从将NCache依赖项添加到我们的项目开始。以下是如何使用Maven添加NCache依赖项的示例:

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


请注意:版本号可能会根据最新发布有所变化。

代码设置

我们将建立一个简单的Java应用程序,包括数据层、服务层和缓存层,以演示NCache的使用。使用这个示例,我们将探讨不同的缓存模式以及如何使用NCache实现它们。

让我们定义一个简单的POJO类来表示用户:

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


接下来,我们将创建一个模拟与数据库交互的数据库层:

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


我们将不关注实际数据库操作,因为本文的重点是缓存。为了演示缓存模式,我们将在接下来的部分中创建一个服务,该服务将与缓存层和数据层交互(如果需要)。

连接到NCache

要连接到NCache服务器,我们可以按照以下方法之一进行:

使用配置文件

我们可以在配置文件中定义连接属性,并用它来连接NCache服务器。下面是如何在client.ncconf文件中定义连接属性的示例:

<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>


让我们看看配置文件中的几个重要属性:

  • ncache-server:定义连接属性,如连接重试、重试间隔和超时时间。
  • cache:定义缓存属性,如缓存ID、同步模式和服务器详情。
  • cache/server:指定缓存运行的服务器IP或主机名。

此文件应放置在应用程序文件夹中,或者在Windows的%NCHOME%\config或Linux的/opt/ncache/config中。

使用代码

另外,我们可以通过在代码中指定连接属性来以编程方式连接到NCache服务器

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);
    }
}


这里,我们有一个连接到NCache服务器的小代码片段,使用CacheConnectionOptions类连接到NCache。我们指定服务器详细信息和用户凭据来连接缓存。同样,我们可以以编程方式提供配置文件中可用的所有属性。

请注意,如果同时提供了配置文件和基于代码的连接属性,则代码中的属性优先。 

基于代码的连接在我们需要根据环境或其他因素动态更改连接属性时非常有用。当连接属性是静态的且不经常更改时,配置文件更加合适。

缓存模式

缓存模式定义了数据如何存储和从缓存中检索。让我们探索一些常见的缓存模式以及它们如何在NCache中实现。

Cache-Aside

最简单的缓存模式之一就是Cache-Aside模式。在这种模式下,应用程序代码负责在访问主数据源之前检查缓存。如果缓存中未找到数据,则从主数据源获取数据,并将其存储在缓存中以供将来访问。

UML diagram of reading data using the cache aside pattern

图 1. 使用Cache Aside模式读取数据


为了演示这一点,让我们定义一个服务类,使用NCache实现Cache-Aside模式:

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
}


为了演示读取操作,我们有一个getUser方法:

  • 它检查缓存中的用户数据。
  • 如果在缓存中找到了数据,则返回数据。
  • 如果在缓存中找不到数据,则从数据库获取数据并将其存储在缓存中,然后返回数据。

接下来,我们有addUser方法:

  • 它将用户数据添加到数据库中。
  • 然后,将用户数据添加到缓存中。这是可选的,具体取决于使用情况。由于缓存容量有限,决定立即将数据写入缓存还是按需填充是很重要的。

Read-Through

Read-Through模式是一种缓存模式,其中主数据源位于缓存后面。当发生缓存未命中时,缓存从主数据源获取数据并将其存储在缓存中以供将来访问。在这种模式下,代码不会直接与主数据源交互。

Reading Data Using Read-Through Cache

图 2. 使用Read-Through Cache读取数据


为了在NCache中实现此行为,需要配置缓存服务器以在发生缓存未命中时从主数据源获取数据。这由配置Read-Through提供程序来处理,它指定了如何从主数据源获取数据。

从代码角度来看,Read-Through模式对应用程序代码是透明的。缓存处理数据获取和存储操作。让我们看看Read-Through模式对代码的变化:

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
}


在此代码片段中,我们定义了一个ReadThruCacheService类,它使用Read-Through模式与缓存交互。

  • 我们指定了Read-Through选项,定义了缓存在发生缓存未命中时的行为。它需要一个提供程序名称,用于标识在缓存服务器中配置的Read-Through提供程序。
  • 在获取用户数据时,我们使用带有Read-Through选项的cache.get方法。如果在缓存中找不到数据,缓存使用配置的Read-Through提供程序从主数据源获取数据。

Write-Through/Write-Behind

Write-Through模式用于在数据更新时将缓存与主数据源同步。当发生写操作时:

  • 代码调用缓存更新数据,
  • 缓存首先更新主数据源,
  • 如果主数据源更新成功,则缓存更新缓存中的数据。

使用写透模式写入数据

图3. 使用写透缓存写入数据


由于该过程是同步的,它可能会影响应用程序的性能。建议对需要在缓存和主数据源之间保持一致的关键数据使用写透模式。如果性能是一个问题,可以使用写后模式。在这种模式下,缓存会异步更新主数据源,减少对应用程序性能的影响。

为了在NCache中实现写透模式,我们需要配置一个写透提供程序,指定在发生写操作时如何更新主数据源。类似地,要使用异步更新,我们可以配置一个写后提供程序,启用异步更新。

一旦配置了提供程序,让我们来看看写透模式的代码更改:

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
}


我们在向缓存添加/更新数据时为其提供WriteThruOptions。然后缓存会使用配置的写透提供程序更新主数据源。在这里,我们使用了WriteMode.WriteThru选项,它确保缓存会同步更新主数据源。同样地,我们可以使用WriteMode.WriteBehind选项执行异步更新。

比较缓存模式

现在我们已经探讨了不同的缓存模式以及它们如何在NCache中实现,让我们根据它们的用例、优点和缺点进行比较:

模式 用例 优点 缺点
缓存旁 频繁访问的数据 实现简单 需要通过客户端代码处理缓存更新和删除
读取透 读取频率高于写入的数据 对应用程序代码透明 缓存未命中对性能有影响
写透 需要保持一致的关键数据 确保缓存和主数据源的数据一致性 同步更新可能影响性能
写后 性能至关重要的应用程序 异步更新减轻了性能影响 数据可能暂时不一致

结论

选择适合性能和可伸缩性的正确缓存模式对应用程序至关重要。NCache提供了强大的缓存解决方案,支持多种缓存模式,如缓存旁、读透、写透和写后。

在本文中,我们探讨了如何在Java应用程序中使用NCache实现这些缓存模式。通过了解每种模式的用例、优点和缺点,开发人员可以明智地选择适合其应用程序的缓存策略。

推荐阅读: 28.一个线程运行时异常会发生什么?线程数量过多会造成什么异常?

本文链接: 使用NCache的Java缓存策略