高并发处理 --- Caffeine内存缓存库

目录

一.什么是Caffeine?

使用场景:

二.如何使用Caffeine?

1.导入依赖:

2.在java项目中使用:

三.对缓存项的驱逐:

[1.容量驱逐(Maximum Size):](#1.容量驱逐(Maximum Size):)

[2.过期驱逐(Expire After Write / Access):](#2.过期驱逐(Expire After Write / Access):)

3.驱逐指定缓存项(主动驱逐):

[(1)使用 invalidate 方法驱逐指定缓存项:](#(1)使用 invalidate 方法驱逐指定缓存项:)

[(2)使用 invalidateAll 方法驱逐所有缓存项:](#(2)使用 invalidateAll 方法驱逐所有缓存项:)

[(3)使用 invalidate 方法并批量驱逐多个缓存项:](#(3)使用 invalidate 方法并批量驱逐多个缓存项:)

[(4)使用 invalidate 方法与条件驱逐:](#(4)使用 invalidate 方法与条件驱逐:)

[4.驱逐回调(Eviction Listener):](#4.驱逐回调(Eviction Listener):)

(1)注册驱逐监听器:

(2)缓存项因过期被驱逐时执行回调:

[四.在 Spring Boot 项目中使用 Caffeine 作为缓存:](#四.在 Spring Boot 项目中使用 Caffeine 作为缓存:)

[1.添加 Caffeine 依赖:](#1.添加 Caffeine 依赖:)

2.创建配置类:

[3.在 Controller 层注入 Cache 实例:](#3.在 Controller 层注入 Cache 实例:)

五.Caffeine与Redis的区别:

[何时使用 Caffeine,何时使用 Redis?](#何时使用 Caffeine,何时使用 Redis?)


一.什么是Caffeine?

Caffeine 是一个高性能的 Java 缓存库,它被设计用于提供快速、可靠的缓存服务。它基于 Google Guava 的缓存系统,但进行了许多优化,旨在提升性能、减少内存消耗,并为缓存提供更多的灵活性。Caffeine 的设计目标是创建一个高效、低延迟的内存缓存系统,能够在高并发的场景下提供卓越的表现。

Caffeine 通过优化缓存的存储和访问机制,在 CPU 缓存和垃圾回收方面做了大量改进,从而能够在高并发下实现高效的缓存管理。

Caffeine 是一个 本地缓存 ,它的缓存存储在应用服务器的内存中。每个实例都有自己的缓存空间,因此缓存是 本地的,仅对当前的应用实例可见。如果我们有多个应用实例或多个服务,Caffeine 缓存不会自动共享,它的作用范围限制在当前的应用进程中。这种缓存通常用于加速对本地应用数据的访问,减少对数据库的频繁查询。

使用场景:

Caffeine 是一个通用的内存缓存库,适用于多种场景,包括但不限于:

  • Web 应用缓存:缓存数据库查询结果、HTTP 请求的响应数据等。
  • 分布式系统中的本地缓存:例如,在分布式系统中,每个服务的本地缓存。
  • 计算密集型操作的缓存:例如缓存计算结果,避免重复计算。
  • API 请求缓存:缓存通过远程服务调用得到的数据,避免重复请求相同的数据。

二.如何使用Caffeine?

1.导入依赖:

XML 复制代码
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.0</version>
</dependency>

2.在java项目中使用:

Caffeine 提供了丰富的 API 来创建和管理缓存,下面是一个简单的示例。

java 复制代码
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

import java.util.concurrent.TimeUnit;

public class CaffeineExample {
    public static void main(String[] args) {
        // 创建一个缓存实例
        Cache<String, String> cache = Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES) // 设置写入后10分钟过期
                .maximumSize(100) // 设置最大缓存条目数
                .build();

        // 存入缓存
        cache.put("key1", "value1");
        cache.put("key2", "value2");

        // 获取缓存
        String value = cache.getIfPresent("key1");  // 返回value1
        System.out.println("Cached value for key1: " + value);

        // 如果缓存中没有数据,使用一个加载函数填充缓存
        String loadedValue = cache.get("key3", key -> "loadedValue");
        System.out.println("Loaded value for key3: " + loadedValue);
    }
}
  • Caffeine.newBuilder():用于构建 Caffeine 缓存的构建器。
  • expireAfterWrite(10, TimeUnit.MINUTES):指定缓存项在写入后的 10 分钟过期。
  • maximumSize(100):指定最大缓存容量为 100 个条目,超过这个限制的缓存项会被逐出。
  • getIfPresent(key):如果缓存存在,则返回对应的缓存值。
  • get(key, function):如果缓存不存在,会通过提供的加载函数自动加载并缓存。

三.对缓存项的驱逐:

在 Caffeine 缓存中,驱逐(Eviction)指的是当缓存达到一定容量限制或过期时,自动移除缓存项的过程。Caffeine 提供了多种方式来控制缓存项的驱逐,包括基于缓存容量限制、缓存过期时间以及自定义驱逐策略

Caffeine 提供了以下几种常见的驱逐策略:

  • 容量限制驱逐:当缓存的大小超过指定的最大容量时,Caffeine 会移除最不常用的缓存项。
  • 过期时间驱逐:当缓存项过期后,Caffeine 会自动移除该项。
  • 主动驱逐:可以根据需求主动驱逐特定缓存项。

1.容量驱逐(Maximum Size):

通过设置最大缓存容量,Caffeine 会根据缓存的容量进行驱逐,移除不常用的缓存项(通常是 LRU 策略,即最少使用的项会先被移除)。

java 复制代码
Cache<String, String> cache = Caffeine.newBuilder()
        .maximumSize(100)  // 设置最大缓存容量为100
        .build();

当缓存的条目数超过最大容量时,Caffeine 会自动驱逐最少使用的条目。

2.过期驱逐(Expire After Write / Access):

Caffeine 提供了基于时间的缓存驱逐策略,可以指定缓存项的失效时间:

  • expireAfterWrite:缓存项在写入后的指定时间后失效。
  • expireAfterAccess:缓存项在访问后的指定时间后失效。
java 复制代码
Cache<String, String> cache = Caffeine.newBuilder()
        .expireAfterWrite(10, TimeUnit.MINUTES)  // 写入后10分钟失效
        .expireAfterAccess(5, TimeUnit.MINUTES)  // 访问后5分钟失效
        .maximumSize(100)
        .build();

当缓存项超过指定的过期时间后,Caffeine 会自动移除这些缓存项。

3.驱逐指定缓存项(主动驱逐):

Caffeine 允许在运行时主动驱逐缓存中的某个指定项。这对于一些特殊情况非常有用,比如在缓存项的数据发生变化时,主动移除某个缓存项。

(1)使用 invalidate 方法驱逐指定缓存项:

我们可以使用 invalidate 方法来驱逐指定的缓存项。如果需要移除一个特定的缓存项,可以直接传入键名。

java 复制代码
cache.invalidate("key1");  // 移除指定的缓存项

(2)使用 invalidateAll 方法驱逐所有缓存项:

如果需要清空缓存中的所有项,可以使用 invalidateAll 方法。

java 复制代码
cache.invalidateAll();  // 移除所有缓存项

(3)使用 invalidate 方法并批量驱逐多个缓存项:

java 复制代码
cache.invalidate("key1", "key2", "key3");  // 移除多个指定的缓存项

(4)使用 invalidate 方法与条件驱逐:

我们还可以根据特定条件来选择性地驱逐缓存项。例如,您可以实现一个条件判断逻辑来决定是否驱逐某个缓存项。

java 复制代码
cache.invalidateIf((key, value) -> key.startsWith("prefix"));  // 根据条件驱逐缓存项

4.驱逐回调(Eviction Listener):

Caffeine 提供了一个强大的驱逐监听器功能,允许在缓存项被驱逐时执行回调。这个功能对于缓存管理非常有用,特别是需要记录缓存被移除的原因,或者需要清理与缓存相关的资源。

(1)注册驱逐监听器:

我们可以使用 removalListener 方法来注册一个驱逐监听器。当缓存项被移除时,监听器会被触发。

java 复制代码
Cache<String, String> cache = Caffeine.newBuilder()
        .maximumSize(100)
        .removalListener((key, value, cause) -> {
            System.out.println("Removed key: " + key + ", cause: " + cause);
        })
        .build();

回调参数说明

  • key:被驱逐的缓存项的键。
  • value:被驱逐的缓存项的值。
  • cause:驱逐的原因(如容量满、过期、手动移除等)。

驱逐 的原因(cause)可以是以下几种:

  • EXPIRED:缓存项过期。
  • SIZE:因为达到容量限制而被移除。
  • MANUAL:手动驱逐。
  • REPLACED:被替代的缓存项。
  • COLLECTED:由于垃圾回收导致的移除。

(2)缓存项因过期被驱逐时执行回调:

java 复制代码
Cache<String, String> cache = Caffeine.newBuilder()
        .expireAfterWrite(5, TimeUnit.MINUTES)
        .removalListener((key, value, cause) -> {
            if (cause == RemovalCause.EXPIRED) {
                System.out.println("Cache item expired: " + key);
            }
        })
        .build();

Caffeine 缓存提供了灵活且强大的驱逐机制,允许开发者根据不同的需求进行缓存项的移除和清理。常见的驱逐方式包括基于容量的驱逐、过期时间的驱逐、主动驱逐以及驱逐监听器。

  • 最大容量maximumSize 设定缓存的最大容量,超过容量时会自动驱逐最不常用的缓存项。
  • 过期策略expireAfterWriteexpireAfterAccess 提供了基于时间的缓存项失效机制。
  • 主动驱逐 :可以使用 invalidate 方法手动移除缓存项。
  • 驱逐监听器 :通过 removalListener 监听缓存项的驱逐事件,以便做进一步的处理。

四.在 Spring Boot 项目中使用 Caffeine 作为缓存:

假设我们正在开发一个 Spring Boot 项目,其中包含一个频繁查询数据库的 UserService,为了提升性能,我们决定在缓存中存储用户信息。对于某些高频率访问的数据(如用户基本信息),使用内存缓存可以显著提高响应速度。为了避免频繁地访问数据库,我们使用 Caffeine 来缓存用户数据。

1.添加 Caffeine 依赖:

pom.xml 中添加 Caffeine 依赖:

XML 复制代码
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.0</version>
</dependency>

2.创建配置类:

java 复制代码
package cn.mybatisplus.redis_mq.config;


import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class CaffeineConfig {

    @Bean
    public Cache<Long,Object> ObjectCache(){
        return Caffeine.newBuilder()
                .expireAfterAccess(5, TimeUnit.MINUTES)  // 5 分钟后过期
                .refreshAfterWrite(10, TimeUnit.MINUTES) // 每10分钟自动刷新缓存
                .maximumSize(100)
                .build();
    }
}

3.在 Controller 层注入 Cache 实例:

Controller 中,使用 @Autowired 注解将 Cache<Long, Object> 缓存实例注入进来,直接使用缓存进行操作。

java 复制代码
import com.github.benmanes.caffeine.cache.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/products")
public class ProductsController {

    @Autowired
    private Cache<Long, Object> objectCache;

    // 模拟一个从缓存中获取数据的接口
    @GetMapping("/getUserInfo")
    public String getUserInfo(@RequestParam Long userId) {
        // 检查缓存中是否存在该用户数据
        Object cachedData = objectCache.getIfPresent(userId);

        if (cachedData != null) {
            return "Cache hit: " + cachedData;
        } else {
            // 模拟从数据库获取用户数据
            String userInfo = "User Info for " + userId;

            // 将查询到的数据放入缓存中
            objectCache.put(userId, userInfo);
            // 当缓存中没有对应 key 的数据时,它会被调用来加载数据并将结果存入缓存。
            objectCache.get(userId,key -> orderService.createOrder(key));

            return "Cache miss: " + userInfo;
        }
    }
}

如果希望在缓存失效或需要手动控制缓存刷新时使用更多功能,Caffeine 提供了丰富的 API,可以在代码中进行进一步的定制。例如:

java 复制代码
objectCache.invalidate(userId);  // 删除缓存
objectCache.refresh(userId);     // 刷新缓存

五.Caffeine与Redis的区别:

Caffeine 和 Redis 都是常见的缓存解决方案,但它们各自有不同的优势和适用场景:

特性 Caffeine Redis
存储类型 内存缓存 分布式缓存(存储在外部内存中,如内存、磁盘等)
性能 本地内存缓存,速度非常快,低延迟 由于需要网络请求,相较于 Caffeine 较慢
适用范围 单机应用,内存容量较小的缓存,低延迟缓存访问 分布式系统,跨机器的缓存,支持持久化存储
缓存容量 受限于 JVM 内存(可配置最大容量) 可配置持久化存储,容量几乎无限
缓存失效策略 支持过期时间、LRU/LFU 淘汰策略 支持过期时间、LRU、LRU + 分布式缓存持久化策略
持久化支持 不支持持久化,只在 JVM 中缓存 支持持久化(内存、磁盘、备份等)
分布式支持 仅适用于单机应用 支持分布式缓存,多个节点可以共享缓存
内存使用 内存缓存,适合小规模、高性能访问 内存/磁盘结合,适合大规模、高可用的分布式缓存

何时使用 Caffeine,何时使用 Redis?

①使用 Caffeine:如果你的应用只需要单机缓存,数据量相对较小,且要求极低的延迟(例如,数据库查询、网页请求缓存),Caffeine 是一个很好的选择。

场景

  • 单机应用,数据存储量较小(例如,用户的最近访问信息、缓存一些经常访问的配置数据等)。
  • 对缓存响应时间要求非常严格,需要高性能低延迟的缓存。
  • 不需要跨服务器共享缓存,且缓存数据可以在应用关闭后丢失。

②使用 Redis:如果你需要跨多个应用共享缓存,或者需要缓存的数据需要持久化,或者系统是分布式的,Redis 是更适合的选择。

场景

  • 分布式系统或微服务架构中需要共享缓存(例如,用户会话、订单状态等)。
  • 数据缓存需要持久化,即便系统重启,缓存数据也要保留。
  • 需要高可用和高扩展性的缓存解决方案,Redis 支持集群、复制、持久化等特性。

在实际项目中,我们可以根据业务需求选择合适的缓存解决方案,甚至结合 Caffeine 和 Redis 使用,例如使用 Caffeine 缓存本地数据,使用 Redis 缓存跨应用的数据。

相关推荐
遥夜人间1 小时前
Redis之缓存更新策略
redis·缓存
小安同学iter3 小时前
Redis入门(Java中操作Redis)
数据库·redis·缓存
敲上瘾6 小时前
线程池的封装(c/c++)
linux·服务器·c++·算法·缓存·池化技术
一代...7 小时前
【Redis】Redis基本命令(1)
数据库·redis·缓存
成工小白7 小时前
Redis的下载安装和使用(超详细)
数据库·redis·缓存
dl8106727319 小时前
Redis的IO多路复用
数据库·redis·缓存
Doris Liu.17 小时前
macOS取证分析——Safari浏览器、Apple Mail数据和Recents数据库
数据库·macos·缓存·sqlite·safari·电子数据取证·macos取证
kinlon.liu20 小时前
使用Redis实现分布式限流
数据库·redis·分布式·缓存
烂漫心空1 天前
Windows 系统如何使用Redis 服务
数据库·数据仓库·redis·mysql·缓存·数据库架构
江畔独步1 天前
Redis清空缓存
数据库·redis·缓存