多层缓存设计

是什么?

在数据从源头到用户的访问路径上,设置多个不同速度、不同层次的存储介质(缓存层)来暂存数据

多级缓存

缓存层级策略

  • 本地缓存

热点数据、配置信息、用户会话等

  • 分布式缓存

共享数据、大容量数据、需要持久化的数据

  • 数据库

完整的业务数据、需要事务保证的数据

面临的问题

  • 时间差

更新分布式缓存后,本地缓存还是旧数据

  • 网络延迟

通知消息可能延迟或丢失

  • 节点故障

某些节点可能收不到更新通知

  • 并发更新

多个节点同时更新可能导致数据不一致

解决方式

  • 主动失效策略

主动更新通知删除缓存

  • TTL过期策略

配置缓存失效时间

  • 版本号机制

每次更新递增版本号

多级缓存解决什么问题

  • 追求极致速度

通过将数据放在离用户最近、速度最快的地方(内存/本地),大幅提升访问速度。

  • 保护后端系统

层层拦截请求,有效减少对数据库和后端服务的压力,避免系统被高流量冲垮。

  • 平衡成本与性能

用少量昂贵的快设备(内存)配合大量便宜的慢设备(磁盘),在有限成本下实现高性能和高吞吐。

涉及的技术

本地缓存技术

Caffeine demo
typescript 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.jysemel</groupId>
        <artifactId>cache</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>cache-caffeine</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>3.1.8</version>
        </dependency>
    </dependencies>

</project>


package com.jysemel.cache;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

import java.util.concurrent.TimeUnit;

public class CaffeineDemo {
    public static void main(String[] args) {
        // 1. 创建缓存,配置最大条目数100,写入后10分钟过期
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(100)                     // 最多缓存100个条目
                .expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
                .recordStats()                          // 开启统计信息(可选)
                .build();

        // 2. 存入数据
        cache.put("key1", "value1");
        cache.put("key2", "value2");

        // 3. 查询数据
        String value1 = cache.getIfPresent("key1");
        System.out.println("key1 -> " + value1); // 输出:key1 -> value1

        // 4. 使用get方法,如果key不存在则通过函数加载(自动存入缓存)
        String value3 = cache.get("key3", CaffeineDemo::computeValue);
        System.out.println("key3 -> " + value3); // 输出:key3 -> computed:key3

        // 5. 再次获取key3(直接从缓存返回,不会调用computeValue)
        String value3Again = cache.getIfPresent("key3");
        System.out.println("key3 again -> " + value3Again);

        // 6. 查看统计信息
        System.out.println("缓存统计:" + cache.stats());
    }

    // 模拟一个较慢的数据加载方法
    private static String computeValue(String key) {
        System.out.println("计算 " + key + " 的值...");
        return "computed:" + key;
    }
}
GuavaCache demo
typescript 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.jysemel</groupId>
        <artifactId>cache</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>cache-guava-cache</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>33.2.0-jre</version> <!-- 使用最新稳定版 -->
        </dependency>
    </dependencies>

</project>


package com.jysemel.cache;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import java.util.concurrent.TimeUnit;

public class GuavaCacheDemo {

    public static void main(String[] args) throws Exception {
        // 1. 创建缓存:最大容量100,写入后10分钟过期,开启统计信息
        Cache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(100)                       // 最多缓存100个条目
                .expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
                .recordStats()                           // 开启命中率统计
                .build();

        // 2. 存入数据
        cache.put("key1", "value1");
        cache.put("key2", "value2");

        // 3. 查询数据
        String value1 = cache.getIfPresent("key1");
        System.out.println("key1 -> " + value1); // 输出:key1 -> value1

        // 4. 使用Callable方式:如果key不存在,则通过给定的Callable加载并自动存入缓存
        String value3 = cache.get("key3", () -> {
            System.out.println("执行加载逻辑...");
            return "computed:" + "key3";
        });
        System.out.println("key3 -> " + value3); // 输出:key3 -> computed:key3

        // 5. 再次获取key3(直接从缓存返回,不会再次执行Callable)
        String value3Again = cache.getIfPresent("key3");
        System.out.println("key3 again -> " + value3Again);

        // 6. 查看统计信息
        System.out.println("缓存统计:" + cache.stats());
    }
}
Ehcache demo
typescript 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.jysemel</groupId>
        <artifactId>cache</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>cache-ehcache</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.10.8</version> <!-- 使用最新稳定版 -->
        </dependency>
    </dependencies>

</project>


package com.jysemel.cache;

import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.config.units.MemoryUnit;

public class EhcacheDemo {
    public static void main(String[] args) {
        // 1. 创建 CacheManager,并预定义名为 "demoCache" 的缓存配置
        //    使用堆内存,最多存储 100 个条目,并设置存活时间(TTL)为 10 秒
        CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                .withCache("demoCache",
                        CacheConfigurationBuilder.newCacheConfigurationBuilder(
                                        Long.class,                      // Key 类型
                                        String.class,                    // Value 类型
                                        ResourcePoolsBuilder.newResourcePoolsBuilder()
                                                .heap(100, EntryUnit.ENTRIES) // 堆内存中最多 100 个条目
                                                .offheap(10, MemoryUnit.MB)   // 可选的堆外内存
                                )
                                .withSizeOfMaxObjectSize(1, MemoryUnit.MB) // 对象大小限制
                        // .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(10))) // 设置过期策略
                )
                .build(true); // true 表示初始化 CacheManager

        // 2. 获取缓存实例
        Cache<Long, String> demoCache = cacheManager.getCache("demoCache", Long.class, String.class);

        // 3. 存入数据
        System.out.println("存入数据: key=1L, value=Hello Ehcache");
        demoCache.put(1L, "Hello Ehcache");

        // 4. 查询数据
        String value = demoCache.get(1L);
        System.out.println("查询 key=1L -> " + value);

        // 5. 查询不存在的数据
        String nonExist = demoCache.get(99L);
        System.out.println("查询 key=99L -> " + nonExist); // 输出 null

        // 6. 移除数据
        demoCache.remove(1L);
        System.out.println("移除后查询 key=1L -> " + demoCache.get(1L));

        // 7. 关闭 CacheManager(通常在应用关闭时执行)
        cacheManager.close();
    }
}

分布式缓存技术

Redis demo
Memcached demo
typescript 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.jysemel</groupId>
        <artifactId>cache</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>cache-memcached</artifactId>

    <dependencies>
        <dependency>
            <groupId>net.spy</groupId>
            <artifactId>spymemcached</artifactId>
            <version>2.12.0</version> <!-- 使用最新稳定版 -->
        </dependency>
    </dependencies>

</project>


package com.jysemel.cache;

import lombok.SneakyThrows;
import net.spy.memcached.AddrUtil;
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.internal.OperationFuture;

public class MemcachedDemo {

    @SneakyThrows
    public static void main(String[] args) {
        // 1. 创建 Memcached 客户端,连接到本地服务器(默认端口 11211)
        //    支持连接多个服务器:new MemcachedClient(AddrUtil.getAddresses("server1:11211 server2:11211"));
        MemcachedClient client = new MemcachedClient(AddrUtil.getAddresses("localhost:11211"));


        System.out.println("✅ 成功连接到 Memcached");

        // 2. 缓存数据 - set 操作(无论是否存在都会写入)
        String key = "user:1001";
        String value = "Alice";
        int expiry = 3600; // 过期时间(秒),0 表示永不过期

        OperationFuture<Boolean> setFuture = client.set(key, expiry, value);
        System.out.println("set 结果: " + (setFuture.get() ? "成功" : "失败"));

        // 3. 获取数据 - get 操作
        Object cachedValue = client.get(key);
        System.out.println("get 结果: " + cachedValue);

        // 4. add 操作:仅在 key 不存在时添加(相当于 INSERT)
        OperationFuture<Boolean> addFuture = client.add("new:key", expiry, "new value");
        System.out.println("add 结果(首次): " + (addFuture.get() ? "成功" : "失败"));

        // 再次 add 同一个 key 会失败
        OperationFuture<Boolean> addAgainFuture = client.add("new:key", expiry, "another");
        System.out.println("add 结果(再次): " + (addAgainFuture.get() ? "成功" : "失败"));

        // 5. replace 操作:仅在 key 存在时替换(相当于 UPDATE)
        OperationFuture<Boolean> replaceFuture = client.replace("new:key", expiry, "updated value");
        System.out.println("replace 结果: " + (replaceFuture.get() ? "成功" : "失败"));
        System.out.println("replace 后 get: " + client.get("new:key"));

        // 6. delete 操作:删除 key
        OperationFuture<Boolean> deleteFuture = client.delete("new:key");
        System.out.println("delete 结果: " + (deleteFuture.get() ? "成功" : "失败"));

        // 7. 批量获取 - getBulk
        client.set("key1", expiry, "value1");
        client.set("key2", expiry, "value2");
        java.util.Map<String, Object> bulkResult = client.getBulk("key1", "key2");
        System.out.println("批量获取: " + bulkResult);

        // 8. 数值操作(incr/decr)- 需要存储数字类型
        client.set("counter", expiry, "10");
        long newValue = client.incr("counter", 5); // 增加 5
        System.out.println("incr 后: " + newValue);
        newValue = client.decr("counter", 3); // 减少 3
        System.out.println("decr 后: " + newValue);
    }
}

总结

多级缓存是用可控的复杂度,换取极致的性能和系统高可用

相关推荐
devlei3 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
努力的小郑5 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3565 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3565 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁6 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp6 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴7 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友8 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒8 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端
Soofjan9 小时前
Go 内存回收-GC 源码1-触发与阶段
后端