Introduction to cache2k – cache2k简介

最后修改: 2020年 2月 21日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll take a look at cache2k — a lightweight, high-performance, in-memory Java caching library.

在本教程中,我们将了解cache2k–一个轻量级、高性能、内存中的Java缓存库。

2. About cache2k

2.关于cache2k

The cache2k library offers fast access times due to non-blocking and wait-free access to cached values. It also supports integration with Spring Framework, Scala Cache, Datanucleus, and Hibernate.

cache2k库由于对缓存值的非阻塞和无等待访问,提供了快速的访问时间。它还支持与Spring Framework、Scala Cache、Datanucleus和Hibernate的集成。

The library comes with many features, including a set of thread-safe atomic operations, a cache loader with blocking read-through, automatic expiry, refresh-ahead, event listeners, and support for the JCache implementation of the JSR107 API. We’ll discuss some of these features in this tutorial.

该库具有许多功能,包括一套线程安全的原子操作、具有阻塞功能的缓存加载器穿透式自动过期刷新-ahead、事件监听器,以及支持JSR107 API的JCache实现。我们将在本教程中讨论这些功能中的一部分。

It’s important to note that cache2k is not a distributed caching solution like Infispan or Hazelcast.

需要注意的是,cache2k不是像InfispanHazelcast那样的分布式缓存解决方案。

3. Maven Dependency

3.Maven的依赖性

To use cache2k, we need to first add the cache2k-base-bom dependency to our pom.xml:

要使用cache2k,我们首先需要将cache2k-base-bom依赖性添加到我们的pom.xml

<dependency>
    <groupId>org.cache2k</groupId>
    <artifactId>cache2k-base-bom</artifactId>
    <version>1.2.3.Final</version>
    <type>pom</type>
</dependency>

4. A Simple cache2k Example

4.一个简单的cache2k例子

Now, let’s see how we can use cache2k in a Java application with the help of a simple example.

现在,让我们借助一个简单的例子来看看我们如何在Java应用程序中使用cache2k。

Let’s consider the example of an online shopping website. Let’s suppose that the website is offering a twenty percent discount on all sports products and a ten percent discount on other products. Our goal here is to cache the discount so that we do not calculate it every time.

让我们考虑一个网上购物网站的例子。假设该网站对所有运动产品提供百分之二十的折扣,对其他产品提供百分之十的折扣。我们在这里的目标是将折扣缓存起来,这样我们就不用每次都计算了。

So, first, we’ll create a ProductHelper class and create a simple cache implementation:

因此,首先,我们将创建一个ProductHelper类,并创建一个简单的缓存实现。

public class ProductHelper {

    private Cache<String, Integer> cachedDiscounts;
    private int cacheMissCount = 0;

    public ProductHelper() {
        cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
          .name("discount")
          .eternal(true)
          .entryCapacity(100)
          .build();
    }

    public Integer getDiscount(String productType) {
        Integer discount = cachedDiscounts.get(productType);
        if (Objects.isNull(discount)) {
            cacheMissCount++;
            discount = "Sports".equalsIgnoreCase(productType) ? 20 : 10;
            cachedDiscounts.put(productType, discount);
        }
        return discount;
    }

    // Getters and setters

}

As we can see, we’ve used a cacheMissCount variable to count the number of times discount is not found in the cache. So, if the getDiscount method uses the cache to get the discount, the cacheMissCount will not change.

我们可以看到,我们使用了一个cacheMissCount变量来计算缓存中没有找到折扣的次数。所以,如果getDiscount方法使用缓存来获取折扣,cacheMissCount将不会改变。

Next, we’ll write a test case and validate our implementation:

接下来,我们将写一个测试用例并验证我们的实现。

@Test
public void whenInvokedGetDiscountTwice_thenGetItFromCache() {
    ProductHelper productHelper = new ProductHelper();
    assertTrue(productHelper.getCacheMissCount() == 0);
    
    assertTrue(productHelper.getDiscount("Sports") == 20);
    assertTrue(productHelper.getDiscount("Sports") == 20);
    
    assertTrue(productHelper.getCacheMissCount() == 1);
}

Finally, let’s take a quick look at the configurations we’ve used.

最后,让我们快速浏览一下我们所使用的配置。

The first one is the name method, which sets the unique name of our cache. The cache name is optional and is generated if we don’t provide it.

第一个是name方法,它设置我们的缓存的唯一名称。缓存名称是可选的,如果我们不提供它,它就会被生成。

Then, we’ve set eternal to true to indicate that the cached values do not expire with time. So, in this case, we can choose to remove elements from the cache explicitly. Otherwise, the elements will get evicted automatically once the cache reaches its capacity.

然后,我们把eternal设为true,以表明缓存的值不会随着时间而过期。所以,在这种情况下,我们可以选择明确地从缓存中删除元素。否则,一旦缓存达到其容量,这些元素将被自动驱逐。

Also, we’ve used the entryCapacity method to specify the maximum number of entries held by the cache. When the cache reaches the maximum size, the cache eviction algorithm will remove one or more entries to maintain the specified capacity.

另外,我们还使用了entryCapacity方法来指定缓存所持有的最大条目数。当缓存达到最大容量时,缓存驱逐算法将移除一个或多个条目以保持指定的容量。

We can further explore the other available configurations in the Cache2kBuilder class.

我们可以在Cache2kBuilder类中进一步探索其他可用配置。

5. cache2k Features

5.cache2k特点

Now, let’s enhance our example to explore some of the cache2k features.

现在,让我们加强我们的例子来探索cache2k的一些功能。

5.1. Configuring Cache Expiry

5.1.配置缓存过期

So far, we’ve allowed a fixed discount for all sports products. However, our website now wants the discount to be available only for a fixed period of time.

到目前为止,我们允许所有体育产品有固定的折扣。然而,我们的网站现在希望这个折扣只在一个固定的时间段内提供。

To take care of this new requirement, we’ll configure the cache expiry using the expireAfterWrite method:

为了满足这个新要求,我们将使用expireAfterWrite方法配置缓存过期。

cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
  // other configurations
  .expireAfterWrite(10, TimeUnit.MILLISECONDS)
  .build();

Let’s now write a test case to check the cache expiry:

现在让我们写一个测试用例来检查缓存的到期情况。

@Test
public void whenInvokedGetDiscountAfterExpiration_thenDiscountCalculatedAgain() 
  throws InterruptedException {
    ProductHelper productHelper = new ProductHelper();
    assertTrue(productHelper.getCacheMissCount() == 0);
    assertTrue(productHelper.getDiscount("Sports") == 20);
    assertTrue(productHelper.getCacheMissCount() == 1);

    Thread.sleep(20);

    assertTrue(productHelper.getDiscount("Sports") == 20);
    assertTrue(productHelper.getCacheMissCount() == 2);
}

In our test case, we’ve tried to get the discount again after the configured duration has passed. We can see that unlike our previous example, the cacheMissCount has been incremented. This is because the item in the cache is expired and the discount is calculated again.

在我们的测试案例中,我们试图在配置的持续时间过后再次获得折扣。我们可以看到,与我们之前的例子不同,cacheMissCount已经被递增了。这是因为缓存中的项目已经过期,折扣被再次计算。

For an advanced cache expiry configuration, we can also configure an ExpiryPolicy.

对于高级缓存过期配置,我们还可以配置一个ExpiryPolicy

5.2. Cache Loading or Read-Through

5.2.缓存加载或穿透阅读

In our example, we’ve used the cache aside pattern to load the cache. This means we’ve calculated and added the discount in the cache on-demand in the getDiscount method.

在我们的例子中,我们使用了cache aside模式来加载cache。这意味着我们在getDiscount方法中按需计算并添加了缓存中的折扣。

Alternatively, we can simply use the cache2k support for the read-through operation. In this operation, the cache will load the missing value by itself with the help of a loader. This is also known as cache loading.

另外,我们可以简单地使用cache2k支持的通读操作。在这个操作中,缓存将在加载器的帮助下自行加载丢失的值。这也被称为缓存加载。

Now, let’s enhance our example further to automatically calculate and load the cache:

现在,让我们进一步加强我们的例子,自动计算和加载缓存。

cachedDiscounts = Cache2kBuilder.of(String.class, Integer.class)
  // other configurations
  .loader((key) -> {
      cacheMissCount++;
      return "Sports".equalsIgnoreCase(key) ? 20 : 10;
  })
  .build();

Also, we’ll remove the logic of calculating and updating the discount from getDiscount:

另外,我们将从getDiscount中删除计算和更新折扣的逻辑。

public Integer getDiscount(String productType) {
    return cachedDiscounts.get(productType);
}

After that, let’s write a test case to make sure that the loader is working as expected:

之后,让我们写一个测试案例,以确保加载器按预期工作。

@Test
public void whenInvokedGetDiscount_thenPopulateCacheUsingLoader() {
    ProductHelper productHelper = new ProductHelper();
    assertTrue(productHelper.getCacheMissCount() == 0);

    assertTrue(productHelper.getDiscount("Sports") == 20);
    assertTrue(productHelper.getCacheMissCount() == 1);

    assertTrue(productHelper.getDiscount("Electronics") == 10);
    assertTrue(productHelper.getCacheMissCount() == 2);
}

5.3. Event Listeners

5.3.事件监听器

We can also configure event listeners for different cache operations like insert, update, removal, and expiry of cache elements.

我们还可以为不同的缓存操作配置事件监听器,如缓存元素的插入、更新、移除和过期。

Let’s suppose we want to log all the entries added in the cache. So, let’s add an event listener configuration in the cache builder:

假设我们想记录缓存中添加的所有条目。因此,让我们在缓存构建器中添加一个事件监听器配置。

.addListener(new CacheEntryCreatedListener<String, Integer>() {
    @Override
    public void onEntryCreated(Cache<String, Integer> cache, CacheEntry<String, Integer> entry) {
        LOGGER.info("Entry created: [{}, {}].", entry.getKey(), entry.getValue());
    }
})

Now, we can execute any of the test cases we’ve created and verify the log:

现在,我们可以执行我们所创建的任何一个测试案例,并验证日志。

Entry created: [Sports, 20].

It’s important to note that the event listeners execute synchronously except for the expiry events. If we want an asynchronous listener, we can use the addAsyncListener method.

需要注意的是,事件监听器是同步执行的,除了过期事件。如果我们想要一个异步的监听器,我们可以使用addAsyncListener方法。

5.4. Atomic Operations

5.4.原子操作

The Cache class has many methods that support atomic operations. These methods are for operations on a single entry only.

Cache类有许多方法支持原子操作。这些方法只适用于对单一条目的操作。

Among such methods are containsAndRemove, putIfAbsent, removeIfEquals, replaceIfEquals, peekAndReplace, and peekAndPut.

这些方法包括containsAndRemoveputIfAbsentremoveIfEqualsreplaceIfEquals peekAndReplace peekAndPut

6. Conclusion

6.结语

In this tutorial, we’ve looked into the cache2k library and some of its useful features. We can refer to the cache2k user guide to explore the library further.

在本教程中,我们已经研究了cache2k库和它的一些有用的功能。我们可以参考cache2k用户指南来进一步探索该库。

As always, the complete code for this tutorial is available over on GitHub.

一如既往,本教程的完整代码可在GitHub上获得