多层缓存设计

是什么?

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

多级缓存

缓存层级策略

  • 本地缓存

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

  • 分布式缓存

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

  • 数据库

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

面临的问题

  • 时间差

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

  • 网络延迟

通知消息可能延迟或丢失

  • 节点故障

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

  • 并发更新

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

解决方式

  • 主动失效策略

主动更新通知删除缓存

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

总结

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

相关推荐
来了老板2 小时前
超越日志与权限:深度解析Python装饰器原理与高阶实战场景
后端
祁梦2 小时前
Redis从入门到入土 --- 黑马点评判断秒杀资格
java·后端
lisus20072 小时前
GO并发统计文件大小
开发语言·后端·golang
Memory_荒年2 小时前
限流算法:当你的系统变成“网红景点”,如何避免被游客挤垮?
java·后端
我命由我123452 小时前
Git 问题:Author identity unknown*** Please tell me who you are.
java·服务器·git·后端·学习·java-ee·学习方法
AskHarries2 小时前
网站被人疯狂爬了 1.5TB 流量
后端
xiaoye37082 小时前
Spring 内置注解 和自定义注解的异同
java·后端·spring
6+h2 小时前
【Spring】Service层常用注解详解
java·后端·spring
木易士心3 小时前
告别手写SQL? Cursor智能生成实战指南与避坑技巧
后端·ai编程