Java工业级缓存实战系列(一):多级缓存架构设计与落地(Redis客户端+Redisson全方案)

Java工业级缓存实战系列(一):多级缓存架构设计与落地(Redis客户端+Redisson全方案)

前言

在高并发Java应用中,缓存是提升系统性能的核心技术之一------它通过"以空间换时间"的逻辑,将热点数据临时存储在内存中,大幅减少数据库IO开销,让接口响应时间从毫秒级降至微秒级。不同于MyBatis一级/二级缓存的"ORM层局部缓存"定位,本文聚焦的是"业务级全局缓存",覆盖从单机到分布式、从基础实现到高级优化的全链路方案。

本文作为系列开篇,核心目标是分方案讲透实现,按场景明确选型:先分别落地"Redis原生客户端(Lettuce/Jedis)"和"Redisson分布式工具"两大技术路线,再通过深度对比明确不同场景的最优选择,最终给出工业级融合落地实践。无论你是需要解决中小并发的单体应用,还是支撑百万级QPS的分布式集群,都能从本文找到可直接复用的方案。

系列衔接说明:本文聚焦多级缓存的基础架构与核心实现,解决缓存穿透、击穿、雪崩三大经典问题;下一篇将基于本文方案,补充布隆过滤器作为缓存穿透的终极解决方案,形成"基础缓存+高级防护"的完整技术闭环。

一、缓存核心认知与架构设计

1.1 缓存核心分类与价值

缓存本质是"数据的临时存储介质",按存储位置可分为两大类,核心差异直接决定了选型逻辑:

缓存类型 典型实现 核心优势 核心局限 核心价值
本地缓存(JVM级) Caffeine、Guava Cache、ConcurrentHashMap 无网络开销,查询性能极致(微秒级) 跨服务数据不一致,受JVM内存限制 本地热点数据加速,减少分布式缓存访问压力
分布式缓存(跨应用) Redis、Memcached 全局数据一致,容量可横向扩展 存在网络IO开销(毫秒级) 跨服务数据共享,支撑分布式架构下的缓存需求

多级缓存架构的核心逻辑:将两者结合,形成"本地缓存优先查询,分布式缓存兜底同步"的链路------既利用本地缓存的高性能,又通过分布式缓存保证跨服务一致性,是工业级应用的标配架构。

1.2 缓存三大核心问题(定义+业务影响)

缓存架构设计的核心是"利用优势,规避风险",三大经典问题是绕不开的重点,直接决定系统稳定性:

  • 缓存穿透:查询"不存在的数据"(如用户ID=99999,数据库无记录),缓存无法命中,所有请求直接穿透到数据库。高并发场景下,大量无效请求会压垮数据库,导致服务不可用。
  • 缓存击穿:热点Key(如爆款商品ID、首页配置Key)过期瞬间,大量并发请求同时穿透到数据库,导致数据库瞬时压力飙升,甚至触发熔断。
  • 缓存雪崩:某一时间段内,大量缓存Key集中过期(如凌晨1点批量更新缓存),或缓存集群故障(如Redis主从切换失败),所有请求全部穿透到数据库,引发数据库雪崩崩溃。

1.3 分布式缓存技术选型前提

分布式缓存的核心选型维度的是"性能、并发支持、集群适配、开发效率、运维成本",结合实际项目需求,形成两大技术路线:

  1. Redis原生客户端路线(Lettuce/Jedis):轻量高效,专注于Redis基础命令的执行(缓存CRUD),适合需要极致性能、简单缓存需求的场景;
  2. Redisson路线:基于Redis封装的分布式服务框架,不仅能实现缓存功能,还提供分布式锁、延迟队列等高级功能,适合分布式架构下的复杂需求,开发效率更高。

两者并非替代关系,而是互补关系------实际项目中常"基础缓存用Redis原生客户端,高级功能用Redisson",兼顾性能与开发效率。

二、本地缓存基础落地(Caffeine首选)

2.1 主流本地缓存方案对比

本地缓存的核心诉求是"高性能、低开销",主流方案的选型逻辑如下(生产环境优先Caffeine):

方案 性能 功能完整性 内存占用 适用场景 推荐优先级
Caffeine 最优 完善(过期策略、容量限制、缓存统计、异步加载) 绝大多数生产场景(首选)
Guava Cache 良好 完善(过期策略、容量限制) 旧项目兼容、无需极致性能
ConcurrentHashMap 较高 基础(无过期、无淘汰机制) 简单临时缓存、少量固定数据

核心结论:Caffeine是当前Java本地缓存的最优选择------性能比Guava Cache快10倍以上(基于W-TinyLFU淘汰算法),支持更多工业级特性,且Spring Boot 2.3+已默认集成,无需额外依赖。

2.2 工业级Caffeine配置与实战

(1)统一常量类(系列共用,标准化配置)
java 复制代码
/**
 * 缓存统一常量类(本地缓存+分布式缓存共用)
 */
public class CacheConstants {
    // 本地缓存前缀(避免Key冲突)
    public static final String LOCAL_CACHE_PREFIX = "local:cache:";
    // 分布式缓存前缀(Redis/Redisson共用)
    public static final String DISTRIBUTED_CACHE_PREFIX = "distributed:cache:";

    // 本地缓存核心配置
    public static final int LOCAL_CACHE_MAX_SIZE = 10000; // 最大容量(JVM堆内存10%-15%)
    public static final long LOCAL_CACHE_EXPIRE_SECONDS = 30 * 60; // 写入后过期(30分钟)
    public static final long LOCAL_CACHE_HOT_EXPIRE_SECONDS = 2 * 60 * 60; // 热点数据过期(2小时)

    // 分布式缓存核心配置
    public static final long DISTRIBUTED_CACHE_DEFAULT_EXPIRE_SECONDS = 60 * 60; // 默认过期(1小时)
    public static final long DISTRIBUTED_CACHE_HOT_EXPIRE_SECONDS = 3 * 60 * 60; // 热点数据过期(3小时)
    public static final long DELAY_DOUBLE_DELETE_MILLIS = 500; // 延迟双删时间(500毫秒)

    // 分布式锁配置
    public static final String LOCK_PREFIX = "distributed:lock:";
    public static final long LOCK_WAIT_TIME = 10; // 锁等待时间(10秒)
    public static final long LOCK_LEASE_TIME = 30; // 锁持有时间(30秒)
}
(2)Caffeine配置类(Spring Bean管理,全局复用)
java 复制代码
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Configuration
public class CaffeineConfig {

    /**
     * 本地缓存核心Bean(工业级最优配置)
     */
    @Bean
    public LoadingCache<String, Object> localCache() {
        return Caffeine.newBuilder()
                // 最大容量(避免OOM,根据JVM堆内存调整)
                .maximumSize(CacheConstants.LOCAL_CACHE_MAX_SIZE)
                // 写入后过期策略(平衡一致性与性能)
                .expireAfterWrite(CacheConstants.LOCAL_CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS)
                // 开启缓存统计(命中率、缺失率,用于监控优化)
                .recordStats()
                // 异步加载线程池(核心线程数=CPU核心数,避免阻塞业务线程)
                .executor(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()))
                // 缓存移除监听器(日志打印,便于排查缓存失效原因)
                .removalListener((key, value, cause) -> 
                        System.out.printf("本地缓存移除:key=%s,原因=%s%n", key, cause.name()))
                // 缓存缺失时的加载逻辑(此处留空,业务层手动实现,灵活度更高)
                .build(key -> null);
    }
}
(3)本地缓存业务层封装(工业级规范)
java 复制代码
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

/**
 * 本地缓存业务层(封装核心操作,便于扩展与维护)
 */
@Service
public class LocalCacheService {

    @Resource
    private LoadingCache<String, Object> localCache;

    /**
     * 缓存存入(自动拼接前缀,避免Key冲突)
     */
    public void put(String key, Object value) {
        put(key, value, CacheConstants.LOCAL_CACHE_EXPIRE_SECONDS);
    }

    /**
     * 缓存存入(自定义过期时间,适配热点数据)
     */
    public void put(String key, Object value, long expireSeconds) {
        if (key == null || value == null) {
            throw new IllegalArgumentException("缓存Key/Value不能为空");
        }
        String cacheKey = CacheConstants.LOCAL_CACHE_PREFIX + key;
        localCache.put(cacheKey, value);
        // 热点数据延长过期时间(可选,根据业务标记)
        if (expireSeconds > CacheConstants.LOCAL_CACHE_EXPIRE_SECONDS) {
            localCache.policy().expireAfterWrite().ifPresent(policy -> 
                    policy.setExpiresAfter(cacheKey, expireSeconds, TimeUnit.SECONDS));
        }
    }

    /**
     * 缓存获取(未命中返回null,异常降级避免影响业务)
     */
    public Object get(String key) {
        if (key == null) {
            return null;
        }
        String cacheKey = CacheConstants.LOCAL_CACHE_PREFIX + key;
        try {
            return localCache.get(cacheKey);
        } catch (Exception e) {
            System.err.printf("本地缓存查询异常:key=%s,异常信息=%s%n", key, e.getMessage());
            return null; // 异常降级,放行到分布式缓存
        }
    }

    /**
     * 缓存删除(支持批量删除)
     */
    public void remove(String key) {
        if (key == null) {
            return;
        }
        String cacheKey = CacheConstants.LOCAL_CACHE_PREFIX + key;
        localCache.invalidate(cacheKey);
    }

    public void removeBatch(Iterable<String> keys) {
        if (keys == null) {
            return;
        }
        Iterable<String> cacheKeys = () -> keys.iterator()
                .forEachRemaining(k -> CacheConstants.LOCAL_CACHE_PREFIX + k);
        localCache.invalidateAll(cacheKeys);
    }

    /**
     * 获取缓存统计信息(生产监控必备)
     */
    public String getCacheStats() {
        com.github.benmanes.caffeine.cache.Stats stats = localCache.stats();
        return String.format("本地缓存命中率:%.2f%%,缺失率:%.2f%%,加载成功数:%d,加载失败数:%d",
                stats.hitRate() * 100,
                stats.missRate() * 100,
                stats.loadSuccessCount(),
                stats.loadFailureCount());
    }
}

三、分布式缓存方案一:Redis原生客户端实现(Lettuce+Jedis)

3.1 Redis客户端深度对比(Lettuce vs Jedis)

Redis原生客户端是直接与Redis交互的工具,核心对比决定了选型优先级:

对比维度 Lettuce(Spring Boot 2.x+默认) Jedis(传统客户端)
线程模型 异步非阻塞(基于Netty驱动) 阻塞式IO(BIO)
并发性能 高(单连接支持多线程并发,连接复用) 中(单线程独占一个连接,需手动管理连接池)
集群支持 原生支持(集群/哨兵/单机模式无缝切换) 需额外引入jedis-cluster依赖,手动适配集群
开发成本 低(Spring Data Redis原生整合,零配置) 中(需手动配置连接池,处理线程安全)
序列化支持 支持自定义序列化(Fastjson2/Jackson) 需手动封装序列化逻辑
适用场景 高并发、分布式集群、Spring Boot项目、核心业务 旧项目迁移、低并发场景、需直接操作Redis原生命令
避坑要点 1. 连接池参数优化(避免连接耗尽);2. 超时时间配置(避免无限阻塞);3. 集群分片Key设计 1. 避免连接泄露(用完必须归还连接池);2. 控制并发数(避免连接池过载);3. 手动处理主从切换

核心结论:无特殊场景一律选择Lettuce------Spring Boot默认集成,开发成本低,异步非阻塞模型适配高并发,集群支持完善;Jedis仅作为"旧项目兼容"或"需原生命令操作"的备用方案。

3.2 Lettuce工业级实现(首选方案)

(1)前置依赖(Spring Boot原生整合)

无需额外引入Lettuce依赖,Spring Boot Starter Data Redis已默认集成:

xml 复制代码
<!-- Spring Data Redis(默认集成Lettuce) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 序列化依赖(Fastjson2,解决Redis存储乱码问题) -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.41</version>
</dependency>
(2)工业级配置(application.yml)
yaml 复制代码
spring:
  redis:
    # 集群配置(单机模式:直接配置host和port,无需cluster节点)
    cluster:
      nodes:
        - 192.168.1.100:6379
        - 192.168.1.101:6379
        - 192.168.1.102:6379
      max-redirects: 3 # 集群最大重定向次数
    # 基础配置
    password: your-redis-password # 生产环境必须配置,避免裸奔
    timeout: 3000ms # 连接超时+读取超时(避免无限阻塞)
    database: 0 # 选择Redis数据库(默认0,按业务隔离)
    # Lettuce连接池配置(核心优化,提升并发性能)
    lettuce:
      pool:
        max-active: 16 # 最大连接数(CPU核心数*2,避免连接池耗尽)
        max-idle: 8 # 最大空闲连接数(与max-active保持一致,减少连接创建开销)
        min-idle: 4 # 最小空闲连接数(保证基础并发需求)
        max-wait: -1ms # 最大等待时间(-1表示无限制,避免线程阻塞)
      shutdown-timeout: 100ms # 关闭超时时间(优雅关闭连接,避免资源泄露)
(3)RedisTemplate配置(序列化优化)

默认RedisTemplate使用JDK序列化,会导致存储乱码、占用空间大,需替换为Fastjson2序列化:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.serializer.SerializerFeature;

@Configuration
public class RedisLettuceConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        // 1. Key序列化:StringRedisSerializer(避免Key乱码,兼容Redis命令行查询)
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setHashKeySerializer(keySerializer);

        // 2. Value序列化:Fastjson2(高效、无乱码、支持复杂对象)
        Fastjson2RedisSerializer valueSerializer = new Fastjson2RedisSerializer(Object.class);
        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashValueSerializer(valueSerializer);

        // 3. 初始化RedisTemplate(必须调用,否则配置不生效)
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 自定义Fastjson2 Redis序列化器(工业级配置)
     */
    public static class Fastjson2RedisSerializer<T> extends org.springframework.data.redis.serializer.RedisSerializer<T> {
        private final Class<T> clazz;

        public Fastjson2RedisSerializer(Class<T> clazz) {
            this.clazz = clazz;
        }

        @Override
        public byte[] serialize(T t) {
            if (t == null) {
                return new byte[0];
            }
            // 序列化配置:日期格式化、禁用循环引用、空值保留(避免反序列化丢失字段)
            return JSON.toJSONBytes(t,
                    JSONWriter.Feature.WriteDateUseDateFormat,
                    JSONWriter.Feature.DisableCircularReferenceDetect,
                    JSONWriter.Feature.WriteNullsAsEmpty);
        }

        @Override
        public T deserialize(byte[] bytes) {
            if (bytes == null || bytes.length == 0) {
                return null;
            }
            return JSON.parseObject(bytes, clazz);
        }
    }
}
(4)Lettuce业务层封装(工业级规范)
java 复制代码
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * Redis Lettuce客户端业务层(基础缓存CRUD,高并发首选)
 */
@Service
public class RedisLettuceCacheService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 缓存存入(默认过期时间)
     */
    public void put(String key, Object value) {
        put(key, value, CacheConstants.DISTRIBUTED_CACHE_DEFAULT_EXPIRE_SECONDS);
    }

    /**
     * 缓存存入(自定义过期时间,支持热点数据延长过期)
     */
    public void put(String key, Object value, long expireSeconds) {
        if (key == null || value == null) {
            throw new IllegalArgumentException("缓存Key/Value不能为空");
        }
        String cacheKey = CacheConstants.DISTRIBUTED_CACHE_PREFIX + key;
        try {
            redisTemplate.opsForValue().set(
                    cacheKey,
                    value,
                    expireSeconds,
                    TimeUnit.SECONDS
            );
        } catch (Exception e) {
            // 缓存写入异常降级(仅日志打印,不影响业务主流程)
            System.err.printf("Lettuce缓存写入异常:key=%s,异常信息=%s%n", key, e.getMessage());
        }
    }

    /**
     * 缓存存入空值(解决缓存穿透问题)
     */
    public void putNullValue(String key) {
        put(key, new NullValue(), 60); // 空值缓存1分钟,避免占用过多内存
    }

    /**
     * 缓存获取(未命中返回null,空值缓存返回NullValue)
     */
    public Object get(String key) {
        if (key == null) {
            return null;
        }
        String cacheKey = CacheConstants.DISTRIBUTED_CACHE_PREFIX + key;
        try {
            Object value = redisTemplate.opsForValue().get(cacheKey);
            // 空值缓存判断(避免穿透到数据库)
            return value instanceof NullValue ? null : value;
        } catch (Exception e) {
            System.err.printf("Lettuce缓存查询异常:key=%s,异常信息=%s%n", key, e.getMessage());
            return null; // 异常降级,放行到数据库
        }
    }

    /**
     * 缓存删除(支持单Key和批量删除)
     */
    public void remove(String key) {
        if (key == null) {
            return;
        }
        String cacheKey = CacheConstants.DISTRIBUTED_CACHE_PREFIX + key;
        try {
            redisTemplate.delete(cacheKey);
        } catch (Exception e) {
            System.err.printf("Lettuce缓存删除异常:key=%s,异常信息=%s%n", key, e.getMessage());
        }
    }

    public void removeBatch(Iterable<String> keys) {
        if (keys == null) {
            return;
        }
        Iterable<String> cacheKeys = () -> keys.iterator()
                .forEachRemaining(k -> CacheConstants.DISTRIBUTED_CACHE_PREFIX + k);
        try {
            redisTemplate.delete(cacheKeys);
        } catch (Exception e) {
            System.err.printf("Lettuce缓存批量删除异常,异常信息=%s%n", e.getMessage());
        }
    }

    /**
     * 延迟双删(解决Redis主从同步延迟导致的脏读)
     */
    public void delayDoubleRemove(String key) {
        // 立即删除
        remove(key);
        // 延迟500毫秒再次删除(适配主从同步延迟)
        CacheThreadPool.DELAY_DELETE_EXECUTOR.schedule(
                () -> remove(key),
                CacheConstants.DELAY_DOUBLE_DELETE_MILLIS,
                TimeUnit.MILLISECONDS
        );
    }

    /**
     * 空值缓存占位符(避免与业务空值混淆)
     */
    private static class NullValue {}
}

/**
 * 缓存专用线程池(统一管理,避免线程泄露)
 */
class CacheThreadPool {
    public static final java.util.concurrent.ScheduledExecutorService DELAY_DELETE_EXECUTOR =
            java.util.concurrent.Executors.newScheduledThreadPool(
                    5,
                    new java.util.concurrent.ThreadFactory() {
                        private int count = 0;
                        @Override
                        public Thread newThread(Runnable r) {
                            Thread thread = new Thread(r);
                            thread.setName("cache-delay-delete-" + (++count));
                            thread.setDaemon(true); // 守护线程,不影响应用关闭
                            return thread;
                        }
                    }
            );
}

3.3 Jedis工业级实现(备用方案)

(1)前置依赖(排除Lettuce,引入Jedis)
xml 复制代码
<!-- Spring Data Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <!-- 排除Lettuce依赖 -->
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 引入Jedis依赖 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.4.6</version>
</dependency>

<!-- 序列化依赖(Fastjson2) -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.41</version>
</dependency>
(2)Jedis配置类(连接池+RedisTemplate)
java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class RedisJedisConfig {

    /**
     * Jedis连接池配置(工业级参数)
     */
    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(16); // 最大连接数(与Lettuce保持一致)
        poolConfig.setMaxIdle(8); // 最大空闲连接数
        poolConfig.setMinIdle(4); // 最小空闲连接数
        poolConfig.setMaxWaitMillis(-1); // 最大等待时间(-1表示无限制)
        poolConfig.setTestOnBorrow(true); // 借出连接时测试可用性(避免使用无效连接)
        poolConfig.setTestOnReturn(true); // 归还连接时测试可用性
        return poolConfig;
    }

    /**
     * Jedis连接工厂(集群/单机适配)
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig poolConfig) {
        JedisConnectionFactory factory = new JedisConnectionFactory(poolConfig);
        // 单机配置(集群配置需替换为RedisClusterConfiguration)
        factory.setHostName("192.168.1.100");
        factory.setPort(6379);
        factory.setPassword("your-redis-password");
        factory.setTimeout(3000); // 超时时间
        factory.setDatabase(0);
        return factory;
    }

    /**
     * RedisTemplate配置(与Lettuce共用序列化逻辑)
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        // Key序列化:StringRedisSerializer
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setHashKeySerializer(keySerializer);

        // Value序列化:Fastjson2(复用Lettuce的序列化器)
        RedisLettuceConfig.Fastjson2RedisSerializer valueSerializer = new RedisLettuceConfig.Fastjson2RedisSerializer(Object.class);
        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashValueSerializer(valueSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
(3)Jedis业务层封装(与Lettuce API对齐)
java 复制代码
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * Redis Jedis客户端业务层(备用方案,旧项目兼容)
 */
@Service
public class RedisJedisCacheService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    // 以下方法与RedisLettuceCacheService完全一致,API对齐,便于切换
    public void put(String key, Object value) {
        put(key, value, CacheConstants.DISTRIBUTED_CACHE_DEFAULT_EXPIRE_SECONDS);
    }

    public void put(String key, Object value, long expireSeconds) {
        if (key == null || value == null) {
            throw new IllegalArgumentException("缓存Key/Value不能为空");
        }
        String cacheKey = CacheConstants.DISTRIBUTED_CACHE_PREFIX + key;
        try {
            redisTemplate.opsForValue().set(cacheKey, value, expireSeconds, TimeUnit.SECONDS);
        } catch (Exception e) {
            System.err.printf("Jedis缓存写入异常:key=%s,异常信息=%s%n", key, e.getMessage());
        }
    }

    public void putNullValue(String key) {
        put(key, new RedisLettuceCacheService.NullValue(), 60);
    }

    public Object get(String key) {
        if (key == null) {
            return null;
        }
        String cacheKey = CacheConstants.DISTRIBUTED_CACHE_PREFIX + key;
        try {
            Object value = redisTemplate.opsForValue().get(cacheKey);
            return value instanceof RedisLettuceCacheService.NullValue ? null : value;
        } catch (Exception e) {
            System.err.printf("Jedis缓存查询异常:key=%s,异常信息=%s%n", key, e.getMessage());
            return null;
        }
    }

    public void remove(String key) {
        if (key == null) {
            return;
        }
        String cacheKey = CacheConstants.DISTRIBUTED_CACHE_PREFIX + key;
        try {
            redisTemplate.delete(cacheKey);
        } catch (Exception e) {
            System.err.printf("Jedis缓存删除异常:key=%s,异常信息=%s%n", key, e.getMessage());
        }
    }

    public void delayDoubleRemove(String key) {
        remove(key);
        CacheThreadPool.DELAY_DELETE_EXECUTOR.schedule(
                () -> remove(key),
                CacheConstants.DELAY_DOUBLE_DELETE_MILLIS,
                TimeUnit.MILLISECONDS
        );
    }
}

四、分布式缓存方案二:Redisson实现(分布式高级功能首选)

4.1 Redisson核心定位与优势

Redisson 不是单纯的 Redis 客户端,而是「基于 Redis 构建的分布式服务框架」------它将 Redis 的基础命令封装成了开箱即用的分布式工具,核心优势如下:

  1. 高级功能丰富:内置分布式锁、延迟队列、分布式集合、布隆过滤器等,无需手动封装(如分布式锁的自动续期、红锁实现);
  2. API 极度友好 :完全屏蔽 Redis 原生命令,用面向对象的方式操作(如 RLock.lock()RMap.put()),学习成本低;
  3. 高可用设计:自动处理连接重试、锁过期、集群故障转移,无需手动编写容错逻辑;
  4. 集群原生支持:无缝适配 Redis 单机、哨兵、分片集群,配置简单;
  5. 缓存功能完善:支持过期策略、缓存淘汰、持久化,兼顾基础缓存与高级功能。

核心定位:适合分布式架构下需要高级功能(如分布式锁、延迟队列)的场景,或追求快速开发、降低容错成本的项目。

4.2 Redisson工业级配置

(1)前置依赖(Redisson Spring Boot Starter)
xml 复制代码
<!-- Redisson Spring Boot Starter(自动整合Spring) -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.5</version>
</dependency>

<!-- 序列化依赖(Fastjson2,与Redis客户端保持一致) -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.41</version>
</dependency>
(2)application.yml配置(集群/单机兼容)
yaml 复制代码
spring:
  redis:
    password: your-redis-password
    timeout: 3000ms

# Redisson配置(独立配置,更灵活)
redisson:
  config: |
    singleServerConfig:
      address: "redis://192.168.1.100:6379" # 单机模式(集群模式替换为clusterServersConfig)
      password: "your-redis-password"
      timeout: 3000
      connectionPoolSize: 16 # 连接池大小(与Lettuce保持一致)
      connectionMinimumIdleSize: 4 # 最小空闲连接数
    # 集群模式配置(替换singleServerConfig)
    # clusterServersConfig:
    #   nodeAddresses:
    #     - "redis://192.168.1.100:6379"
    #     - "redis://192.168.1.101:6379"
    #   password: "your-redis-password"
    #   timeout: 3000
    #   scanInterval: 2000 # 集群节点扫描间隔
    serializer:
      # 序列化配置(与Redis客户端保持一致,避免数据不一致)
      type: org.redisson.codec.FastJson2Codec
(3)RedissonClient配置类(Spring Bean管理)
java 复制代码
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Value("${redisson.config}")
    private String redissonConfig;

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        // 加载配置(支持字符串、文件、URL多种方式)
        Config config = Config.fromYAML(redissonConfig);
        // 创建RedissonClient实例(全局唯一,线程安全)
        return Redisson.create(config);
    }
}

4.3 Redisson缓存与高级功能实战

(1)Redisson基础缓存实现(分布式Map)
java 复制代码
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * Redisson缓存业务层(基础缓存+高级功能)
 */
@Service
public class RedissonCacheService {

    @Resource
    private RedissonClient redissonClient;

    /**
     * 获取分布式Map(缓存容器)
     */
    private <K, V> RMap<K, V> getCacheMap() {
        // 缓存Key前缀(与Redis客户端保持一致,避免Key冲突)
        return redissonClient.getMap(CacheConstants.DISTRIBUTED_CACHE_PREFIX);
    }

    /**
     * 缓存存入(支持过期时间)
     */
    public void put(String key, Object value) {
        put(key, value, CacheConstants.DISTRIBUTED_CACHE_DEFAULT_EXPIRE_SECONDS);
    }

    public void put(String key, Object value, long expireSeconds) {
        if (key == null || value == null) {
            throw new IllegalArgumentException("缓存Key/Value不能为空");
        }
        RMap<String, Object> cacheMap = getCacheMap();
        try {
            cacheMap.put(key, value);
            // 设置过期时间(Key级过期)
            cacheMap.expire(key, expireSeconds, TimeUnit.SECONDS);
        } catch (Exception e) {
            System.err.printf("Redisson缓存写入异常:key=%s,异常信息=%s%n", key, e.getMessage());
        }
    }

    /**
     * 缓存存入空值(解决缓存穿透)
     */
    public void putNullValue(String key) {
        put(key, new NullValue(), 60);
    }

    /**
     * 缓存获取
     */
    public Object get(String key) {
        if (key == null) {
            return null;
        }
        RMap<String, Object> cacheMap = getCacheMap();
        try {
            Object value = cacheMap.get(key);
            return value instanceof NullValue ? null : value;
        } catch (Exception e) {
            System.err.printf("Redisson缓存查询异常:key=%s,异常信息=%s%n", key, e.getMessage());
            return null;
        }
    }

    /**
     * 缓存删除
     */
    public void remove(String key) {
        if (key == null) {
            return;
        }
        RMap<String, Object> cacheMap = getCacheMap();
        try {
            cacheMap.remove(key);
        } catch (Exception e) {
            System.err.printf("Redisson缓存删除异常:key=%s,异常信息=%s%n", key, e.getMessage());
        }
    }

    /**
     * 空值占位符
     */
    private static class NullValue {}
}
(2)Redisson核心高级功能:分布式锁(解决缓存击穿)
java 复制代码
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * Redisson分布式锁工具类(工业级实现,支持红锁)
 */
@Component
public class RedissonLockUtil {

    @Resource
    private RedissonClient redissonClient;

    /**
     * 获取分布式锁(非阻塞,失败返回false)
     * @param lockKey 锁Key
     * @return 锁实例(释放锁需调用unlock())
     */
    public RLock tryLock(String lockKey) {
        return tryLock(lockKey, CacheConstants.LOCK_WAIT_TIME, CacheConstants.LOCK_LEASE_TIME);
    }

    /**
     * 自定义等待时间和持有时间
     */
    public RLock tryLock(String lockKey, long waitTime, long leaseTime) {
        if (lockKey == null) {
            throw new IllegalArgumentException("锁Key不能为空");
        }
        String key = CacheConstants.LOCK_PREFIX + lockKey;
        // 获取锁实例(支持红锁:redissonClient.getRedLock(locks))
        RLock lock = redissonClient.getLock(key);
        try {
            // 尝试获取锁:最多等待waitTime秒,持有leaseTime秒后自动释放(避免死锁)
            boolean locked = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            return locked ? lock : null;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        }
    }

    /**
     * 释放锁(必须在finally中调用)
     */
    public void unlock(RLock lock) {
        if (lock != null && lock.isHeldByCurrentThread()) {
            try {
                lock.unlock();
            } catch (Exception e) {
                System.err.printf("释放分布式锁异常,异常信息=%s%n", e.getMessage());
            }
        }
    }
}
(3)Redisson核心高级功能:延迟队列(优化延迟双删)
java 复制代码
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RQueue;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * Redisson延迟队列工具类(替代线程池,更稳定)
 */
@Component
public class RedissonDelayQueueUtil {

    @Resource
    private RedissonClient redissonClient;

    // 延迟队列名称(统一管理)
    private static final String DELAY_QUEUE_NAME = "distributed:delay:queue";

    /**
     * 添加延迟任务(如延迟双删、订单超时取消)
     * @param task 任务内容(需序列化)
     * @param delay 延迟时间
     * @param timeUnit 时间单位
     */
    public <T> void addDelayTask(T task, long delay, TimeUnit timeUnit) {
        if (task == null) {
            throw new IllegalArgumentException("任务内容不能为空");
        }
        // 获取队列实例
        RQueue<T> queue = redissonClient.getQueue(DELAY_QUEUE_NAME);
        RDelayedQueue<T> delayedQueue = redissonClient.getDelayedQueue(queue);
        // 添加延迟任务
        delayedQueue.offer(task, delay, timeUnit);
    }

    /**
     * 监听延迟任务(应用启动时执行,阻塞监听)
     */
    public <T> void listenDelayTask(Class<T> taskClass, DelayTaskHandler<T> handler) {
        RQueue<T> queue = redissonClient.getQueue(DELAY_QUEUE_NAME);
        RDelayedQueue<T> delayedQueue = redissonClient.getDelayedQueue(queue);
        // 循环监听任务(线程安全,阻塞式)
        while (true) {
            try {
                T task = queue.take();
                if (task != null) {
                    handler.handle(task);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
                System.err.printf("处理延迟任务异常,异常信息=%s%n", e.getMessage());
            }
        }
    }

    /**
     * 任务处理接口(回调)
     */
    @FunctionalInterface
    public interface DelayTaskHandler<T> {
        void handle(T task);
    }
}

/**
 * 延迟双删任务(示例)
 */
class CacheDelayDeleteTask {
    private String key;

    // 省略getter/setter/构造方法
}

/**
 * 延迟队列初始化(应用启动时执行)
 */
@Component
class DelayQueueInitializer {

    @Resource
    private RedissonDelayQueueUtil delayQueueUtil;
    @Resource
    private RedissonCacheService redissonCacheService;

    // 应用启动后执行
    public void init() {
        // 监听延迟双删任务
        delayQueueUtil.listenDelayTask(CacheDelayDeleteTask.class, task -> {
            String key = task.getKey();
            redissonCacheService.remove(key);
            System.out.printf("延迟双删任务执行:key=%s%n", key);
        });
    }
}

五、核心选型对比与场景适配(重点)

5.1 三大方案全面对比表

方案 性能 开发效率 集群支持 高级功能 运维成本 学习成本 适用场景
Redis+Lettuce 低-中 原生支持 高并发核心业务、Spring Boot项目、基础缓存CRUD、集群部署
Redis+Jedis 需适配 旧项目迁移、低并发场景、需直接操作Redis原生命令
Redisson 中-高 原生支持 丰富(分布式锁/延迟队列等) 分布式架构、快速开发、需高级功能、中小并发业务

5.2 典型场景选型指南(直接落地参考)

  1. 高并发核心业务(如电商商品详情、支付接口)

    • 选型:Caffeine+Redis+Lettuce
    • 理由:Lettuce异步非阻塞模型支撑高并发,Caffeine减少网络开销,性能最优;
    • 补充:用Redisson分布式锁解决缓存击穿(热点Key过期)。
  2. 旧项目缓存改造(已集成Jedis)

    • 选型:Caffeine+Redis+Jedis
    • 理由:无需大规模修改代码,优化Jedis连接池参数即可提升性能;
    • 避坑:确保连接池复用,避免连接泄露。
  3. 分布式架构+快速开发(如中台系统、内部工具)

    • 选型:Caffeine+Redisson
    • 理由:Redisson开箱即用分布式锁、延迟队列,开发效率高,无需手动封装;
    • 优势:兼顾基础缓存与高级功能,减少依赖冲突。
  4. 分布式锁/延迟队列等高级需求(如秒杀、订单超时取消)

    • 选型:Redisson(必选)
    • 理由:Redisson红锁解决单点故障,自动续期避免死锁,延迟队列稳定可靠;
    • 替代方案:Redis+Lettuce需手动封装,容错成本高。
  5. 中小并发、单体应用(如管理后台、工具类项目)

    • 选型:Caffeine+Redis+Lettuce 或 Caffeine+Redisson
    • 理由:配置简单,无需复杂集群,满足性能需求即可。
  6. 需直接操作Redis原生命令(如自定义协议、特殊命令)

    • 选型:Redis+Jedis 或 Redis+Lettuce(execute方法)
    • 理由:Jedis API更贴近原生命令,Lettuce需通过RedisConnection.execute()调用。

5.3 选型决策流程(三步法)

  1. 看并发量:高并发(1万QPS以上)→ 优先Lettuce;中低并发→ 可选Redisson/Jedis;
  2. 看功能需求:需分布式锁/延迟队列→ Redisson;仅基础缓存→ Lettuce/Jedis;
  3. 看技术栈:Spring Boot 2.x+→ 优先Lettuce;旧项目→ 优先Jedis;快速开发→ Redisson。

六、工业级落地实战:多级缓存全链路整合

6.1 方案一:Caffeine+Redis+Lettuce(高并发首选)

(1)全链路流程
  • 查询流程
    本地缓存(Caffeine)→ 命中返回 → 未命中 → Redis(Lettuce)→ 命中返回(同步到本地缓存)→ 未命中 → 分布式锁(Redisson)→ 数据库 → 回写Redis+本地缓存 → 返回;
  • 更新流程
    数据库更新 → 删除本地缓存 → Redis删除(Lettuce)→ 延迟双删(线程池)→ 返回。
(2)实战代码(商品查询示例)
java 复制代码
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.UUID;

@Service
public class ProductService {

    @Resource
    private LocalCacheService localCacheService;
    @Resource
    private RedisLettuceCacheService lettuceCacheService;
    @Resource
    private RedissonLockUtil redissonLockUtil;
    @Resource
    private ProductDAO productDAO; // 数据库DAO(MyBatis/MyBatis-Plus)

    /**
     * 商品查询(高并发全链路缓存逻辑)
     */
    public ProductDTO getProductById(Long productId) {
        if (productId == null || productId <= 0) {
            return null;
        }
        String cacheKey = "product:" + productId;
        ProductDTO productDTO;

        // 1. 查询本地缓存(Caffeine)
        productDTO = (ProductDTO) localCacheService.get(cacheKey);
        if (productDTO != null) {
            System.out.printf("本地缓存命中:key=%s%n", cacheKey);
            return productDTO;
        }

        // 2. 查询Redis缓存(Lettuce)
        productDTO = (ProductDTO) lettuceCacheService.get(cacheKey);
        if (productDTO != null) {
            // 同步到本地缓存
            localCacheService.put(cacheKey, productDTO);
            System.out.printf("Redis缓存命中:key=%s%n", cacheKey);
            return productDTO;
        }

        // 3. 缓存未命中,加分布式锁防击穿(Redisson)
        String lockKey = "product:lock:" + productId;
        RedissonLockUtil.RLock lock = redissonLockUtil.tryLock(lockKey);
        if (lock == null) {
            // 未获取到锁,返回默认值或降级处理
            return null;
        }

        try {
            // 4. 再次查询Redis(避免锁等待期间已被其他线程写入)
            productDTO = (ProductDTO) lettuceCacheService.get(cacheKey);
            if (productDTO != null) {
                localCacheService.put(cacheKey, productDTO);
                return productDTO;
            }

            // 5. 查询数据库
            productDTO = productDAO.selectById(productId);
            if (productDTO != null) {
                // 热点数据延长过期时间(3小时)
                long expireSeconds = CacheConstants.DISTRIBUTED_CACHE_HOT_EXPIRE_SECONDS;
                // 回写Redis+本地缓存
                lettuceCacheService.put(cacheKey, productDTO, expireSeconds);
                localCacheService.put(cacheKey, productDTO, CacheConstants.LOCAL_CACHE_HOT_EXPIRE_SECONDS);
            } else {
                // 数据库无结果,写入空值缓存(防穿透)
                lettuceCacheService.putNullValue(cacheKey);
                localCacheService.put(cacheKey, new RedisLettuceCacheService.NullValue());
            }
        } finally {
            // 释放锁
            redissonLockUtil.unlock(lock);
        }

        return productDTO;
    }

    /**
     * 商品更新(缓存同步流程)
     */
    public boolean updateProduct(ProductDTO productDTO) {
        if (productDTO == null || productDTO.getId() == null) {
            return false;
        }
        String cacheKey = "product:" + productDTO.getId();

        try {
            // 1. 先更新数据库
            boolean success = productDAO.update(productDTO);
            if (!success) {
                return false;
            }

            // 2. 删除本地缓存
            localCacheService.remove(cacheKey);

            // 3. 删除Redis缓存+延迟双删
            lettuceCacheService.delayDoubleRemove(cacheKey);

            return true;
        } catch (Exception e) {
            System.err.printf("更新商品异常:id=%s,异常=%s%n", productDTO.getId(), e.getMessage());
            return false;
        }
    }
}

6.2 方案二:Caffeine+Redisson(快速开发首选)

(1)全链路流程
  • 查询流程:本地缓存(Caffeine)→ 命中返回 → 未命中 → Redisson分布式Map → 命中返回(同步到本地缓存)→ 未命中 → Redisson分布式锁 → 数据库 → 回写双缓存 → 返回;
  • 更新流程:数据库更新 → 删除本地缓存 → Redisson分布式Map删除 → Redisson延迟队列二次删除 → 返回。
(2)实战代码(简化版商品查询)
java 复制代码
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class ProductRedissonService {

    @Resource
    private LocalCacheService localCacheService;
    @Resource
    private RedissonCacheService redissonCacheService;
    @Resource
    private RedissonLockUtil redissonLockUtil;
    @Resource
    private ProductDAO productDAO;

    public ProductDTO getProductById(Long productId) {
        if (productId == null || productId <= 0) {
            return null;
        }
        String cacheKey = "product:" + productId;
        ProductDTO productDTO;

        // 1. 本地缓存查询
        productDTO = (ProductDTO) localCacheService.get(cacheKey);
        if (productDTO != null) {
            return productDTO;
        }

        // 2. Redisson缓存查询
        productDTO = (ProductDTO) redissonCacheService.get(cacheKey);
        if (productDTO != null) {
            localCacheService.put(cacheKey, productDTO);
            return productDTO;
        }

        // 3. 分布式锁防击穿
        RLock lock = redissonLockUtil.tryLock(cacheKey);
        if (lock == null) {
            return null;
        }

        try {
            productDTO = (ProductDTO) redissonCacheService.get(cacheKey);
            if (productDTO != null) {
                localCacheService.put(cacheKey, productDTO);
                return productDTO;
            }

            // 4. 数据库查询+回写缓存
            productDTO = productDAO.selectById(productId);
            if (productDTO != null) {
                redissonCacheService.put(cacheKey, productDTO, CacheConstants.DISTRIBUTED_CACHE_HOT_EXPIRE_SECONDS);
                localCacheService.put(cacheKey, productDTO, CacheConstants.LOCAL_CACHE_HOT_EXPIRE_SECONDS);
            } else {
                redissonCacheService.putNullValue(cacheKey);
                localCacheService.put(cacheKey, new RedissonCacheService.NullValue());
            }
        } finally {
            redissonLockUtil.unlock(lock);
        }

        return productDTO;
    }
}

6.3 缓存三大问题解决方案落地(分方案)

问题类型 Redis+Lettuce/Jedis解决方案 Redisson解决方案
缓存穿透 1. 空值缓存(1分钟过期);2. 参数校验;3. 后续补充布隆过滤器 1. 空值缓存;2. 参数校验;3. Redisson布隆过滤器
缓存击穿 1. Redis SET NX命令实现互斥锁;2. 热点Key延长过期时间 1. 分布式可重入红锁(自动续期);2. 热点Key延长过期
缓存雪崩 1. 过期时间随机化(±30秒);2. Redis集群(主从+哨兵);3. Sentinel熔断降级 1. 过期时间随机化;2. Redis集群;3. Redisson熔断降级;4. 缓存预热

七、总结与下一篇预告

7.1 核心收获

本文围绕"Java工业级多级缓存",落地了三大技术方案,核心收获如下:

  1. 掌握本地缓存(Caffeine)的工业级配置与封装,理解其"高性能、低开销"的核心优势;
  2. 精通Redis原生客户端(Lettuce/Jedis)的实现细节,明确Lettuce的首选地位与Jedis的备用场景;
  3. 学会Redisson的核心用法,利用其分布式锁、延迟队列等高级功能解决缓存击穿、同步延迟等痛点;
  4. 明确不同场景的选型逻辑,能根据并发量、功能需求快速选择最优方案,并实现全链路整合;
  5. 解决缓存三大经典问题,形成"参数校验+空值缓存+分布式锁+过期随机化"的基础防护体系。

7.2 下一篇预告

本文的空值缓存方案在"海量无效Key场景"下存在明显局限:

  • 大量空值缓存占用Redis宝贵内存;
  • 空值缓存存在"过期窗口",窗口期内的无效请求仍会穿透到数据库。

下一篇《Java工业级缓存实战系列(二):缓存穿透终极解决方案------布隆过滤器(Redisson+Redis Bloom)》将聚焦:

  1. 布隆过滤器核心原理(二进制向量+多哈希函数);
  2. 双方案落地:Redisson布隆过滤器(快速开发)与Redis Bloom+Lettuce(极致性能);
  3. 布隆过滤器与本文多级缓存架构的无缝整合,形成"布隆拦截+空值缓存+多级缓存"的三重防护体系。

7.3 实战扩展建议

  1. 序列化统一:所有方案统一使用Fastjson2或Jackson,避免不同客户端序列化不一致导致的脏数据;
  2. 动态配置:将缓存容量、过期时间、连接池参数等配置到Nacos/Apollo,支持动态调整,无需重启应用;
  3. 监控告警:接入Prometheus+Grafana,监控缓存命中率(目标≥90%)、Redis内存使用率(阈值≤70%)、分布式锁竞争率,设置告警阈值;
  4. 压测验证:针对高并发场景做压测,验证Lettuce连接池参数、Redisson锁性能,提前发现瓶颈;
  5. 缓存预热:应用启动时,通过CommandLineRunner批量加载热点数据到Caffeine+Redis,避免冷启动穿透。
相关推荐
__万波__8 小时前
二十三种设计模式(二十)--解释器模式
java·设计模式·解释器模式
网安_秋刀鱼9 小时前
【java安全】反序列化 - CC1链
java·c语言·安全
零度@9 小时前
Java消息中间件-Kafka全解(2026精简版)
java·kafka·c#·linq
钱多多_qdd9 小时前
springboot注解(二)
java·spring boot·后端
Cosmoshhhyyy9 小时前
《Effective Java》解读第32条:谨慎并用泛型和可变参数
java·python
帅气的你9 小时前
面向Java程序员的思维链(CoT)提示词写法学习指南
java
一只小小Java9 小时前
Java面试场景高频题
java·开发语言·面试
沛沛老爹9 小时前
Web开发者快速上手AI Agent:基于Function Calling的12306自动订票系统实战
java·人工智能·agent·web转型
CRUD酱9 小时前
后端使用POI解析.xlsx文件(附源码)
java·后端
亓才孓9 小时前
多态:编译时看左边,运行时看右边
java·开发语言