27人参与 • 2025-05-12 • Redis
在现代分布式系统设计中,缓存是优化服务性能的核心组件。标准实现方案采用远程缓存(如redis/memcached)作为数据库前置层,通过以下机制提升性能:
当系统面临以下场景时,纯远程缓存方案显现局限性:
问题类型 | 表现特征 | 典型案例 |
超高并发读取 | redis带宽成为瓶颈 | 热点商品详情页访问 |
超低延迟要求 | 网络往返耗时不可忽略 | 金融行情数据推送 |
成本控制需求 | 高频访问导致redis扩容 | 用户基础信息查询 |
引入本地缓存构建两级缓存体系:
协同机制:
内存级响应:本地缓存直接存储在应用进程的内存中(如java堆内),访问速度通常在纳秒级(如caffeine的读写性能可达每秒千万次),而远程缓存(如redis)需要网络通信,延迟在毫秒级。
技术选型 | 响应时长 |
本地缓存 | ~100ns |
redis远程缓存 | ~1ms(受网络影响可能更高) |
数据库查询 | ~10ms 甚至更长。 |
本地缓存支持丰富的特性,满足不同业务需求:
get-if-absent-compute
(查不到时自动加载),避免并发重复查询。功能:基础的键值存储与原子操作。
cache<string, string> cache = caffeine.newbuilder().build(); // 写入缓存 cache.put("user:1", "alice"); // 读取缓存(若不存在则自动计算) string value = cache.get("user:1", key -> fetchfromdb(key));
功能:限制缓存大小并淘汰数据。
算法 | 描述 | 适用场景 | 代码示例(caffeine) |
lru | 淘汰最久未访问的数据 | 热点数据分布不均匀 | .maximumsize(100).build() |
lfu | 淘汰访问频率最低的数据 | 长期稳定的热点数据 | .maximumsize(100).build() (w-tinylfu) |
fifo | 按写入顺序淘汰 | 数据顺序敏感的场景 | 需自定义实现 |
功能:自动清理过期数据。
caffeine.newbuilder() .expireafterwrite(10, timeunit.minutes) // 写入后10分钟过期 .expireafteraccess(5, timeunit.minutes) // 访问后5分钟过期 .build();
功能:自动加载数据并支持后台刷新。
asyncloadingcache<string, string> cache = caffeine.newbuilder() .refreshafterwrite(1, timeunit.minutes) // 1分钟后后台刷新 .buildasync(key -> fetchfromdb(key)); // 获取数据(若需刷新,不会阻塞请求) completablefuture<string> future = cache.get("user:1");
功能:线程安全与击穿保护。
// 自动合并并发请求(同一key仅一次加载) loadingcache<string, string> cache = caffeine.newbuilder() .build(key -> { system.out.println("仅执行一次: " + key); return fetchfromdb(key); }); // 并发测试(输出1次日志) intstream.range(0, 100).parallel().foreach( i -> cache.get("user:1") );
功能:记录命中率等指标。
cache<string, string> cache = caffeine.newbuilder() .recordstats() // 开启统计 .build(); cache.get("user:1"); cachestats stats = cache.stats(); system.out.println("命中率: " + stats.hitrate());
功能:缓存数据持久化到磁盘。
// 使用caffeine + rocksdb(需额外依赖) cache<string, byte[]> cache = caffeine.newbuilder() .maximumsize(100) .writer(new cachewriter<string, byte[]>() { @override public void write(string key, byte[] value) { rocksdb.put(key.getbytes(), value); // 同步写入磁盘 } @override public void delete(string key, byte[] value, removalcause cause) { rocksdb.delete(key.getbytes()); } }) .build();
功能:监听缓存变更事件。
cache<string, string> cache = caffeine.newbuilder() .removallistener((key, value, cause) -> system.out.println("移除事件: " + key + " -> " + cause)) .evictionlistener((key, value, cause) -> system.out.println("驱逐事件: " + key + " -> " + cause)) .build();
concurrenthashmap是java集合框架中提供的线程安全哈希表实现,首次出现在jdk1.5中。它采用分段锁技术(jdk8后改为cas+synchronized优化),通过将数据分成多个段(segment),每个段独立加锁,实现了高并发的读写能力。
作为juc(java.util.concurrent)包的核心组件,它被广泛应用于需要线程安全哈希表的场景。
import java.util.concurrent.*; import java.util.function.function; public class chmcache<k,v> { private final concurrenthashmap<k,v> map = new concurrenthashmap<>(16, 0.75f, 32); private final scheduledexecutorservice cleaner = executors.newsinglethreadscheduledexecutor(); // 基础操作 public void put(k key, v value) { map.put(key, value); } // 带ttl的put public void put(k key, v value, long ttl, timeunit unit) { map.put(key, value); cleaner.schedule(() -> map.remove(key), ttl, unit); } // 自动加载 public v get(k key, function<k,v> loader) { return map.computeifabsent(key, loader); } // 批量操作 public void putall(map<? extends k, ? extends v> m) { map.putall(m); } // 清空缓存 public void clear() { map.clear(); } }
guava cache是google guava库中的缓存组件,诞生于2011年。作为concurrenthashmap的增强版,它添加了缓存特有的特性。guava项目本身是google内部java开发的标准库,经过大规模生产环境验证,稳定性和性能都有保障。guava cache广泛应用于各种需要本地缓存的java项目中。
<dependency> <groupid>com.google.guava</groupid> <artifactid>guava</artifactid> <version>31.1-jre</version> </dependency>
import com.google.common.cache.*; import java.util.concurrent.timeunit; public class guavacachedemo { public static void main(string[] args) { loadingcache<string, string> cache = cachebuilder.newbuilder() .maximumsize(1000) // 最大条目数 .expireafterwrite(10, timeunit.minutes) // 写入后过期时间 .expireafteraccess(30, timeunit.minutes) // 访问后过期时间 .concurrencylevel(8) // 并发级别 .recordstats() // 开启统计 .removallistener(notification -> system.out.println("removed: " + notification.getkey())) .build(new cacheloader<string, string>() { @override public string load(string key) throws exception { return loadfromdb(key); } }); try { // 自动加载 string value = cache.get("user:1001"); // 手动操作 cache.put("config:timeout", "5000"); cache.invalidate("user:1001"); // 打印统计 system.out.println(cache.stats()); } catch (executionexception e) { e.printstacktrace(); } } private static string loadfromdb(string key) { // 模拟数据库查询 return "db_result_" + key; } }
caffeine是guava cache作者的新作品,发布于2015年。它专为现代java应用设计,采用window-tinylfu淘汰算法,相比传统lru有更高的命中率。caffeine充分利用java 8特性(如completablefuture),在性能上大幅超越guava cache(3-5倍提升),是目前性能最强的java本地缓存库。
<dependency> <groupid>com.github.ben-manes.caffeine</groupid> <artifactid>caffeine</artifactid> <version>2.9.3</version> </dependency>
import com.github.benmanes.caffeine.cache.*; import java.util.concurrent.timeunit; public class caffeinedemo { public static void main(string[] args) { // 同步缓存 cache<string, data> cache = caffeine.newbuilder() .maximumsize(10_000) .expireafterwrite(5, timeunit.minutes) .expireafteraccess(10, timeunit.minutes) .refreshafterwrite(1, timeunit.minutes) .recordstats() .build(); // 异步加载缓存 asyncloadingcache<string, data> asynccache = caffeine.newbuilder() .maximumweight(100_000) .weigher((string key, data data) -> data.size()) .expireafterwrite(10, timeunit.minutes) .buildasync(key -> loadfromdb(key)); // 使用示例 data data = cache.getifpresent("key1"); completablefuture<data> future = asynccache.get("key1"); // 打印统计 system.out.println(cache.stats()); } static class data { int size() { return 1; } } private static data loadfromdb(string key) { // 模拟数据库加载 return new data(); } }
eehcache是terracotta公司开发的企业级缓存框架,始于2003年。它是jsr-107标准实现之一,支持从本地缓存扩展到分布式缓存。ehcache的特色在于支持多级存储(堆内/堆外/磁盘),适合需要缓存持久化的企业级应用。
最新版本ehcache 3.x完全重构,提供了更现代的api设计。
<dependency> <groupid>org.ehcache</groupid> <artifactid>ehcache</artifactid> <version>3.9.7</version> </dependency>
import org.ehcache.*; import org.ehcache.config.*; import org.ehcache.config.builders.*; import java.time.duration; public class ehcachedemo { public static void main(string[] args) { // 1. 配置缓存管理器 cachemanager cachemanager = cachemanagerbuilder.newcachemanagerbuilder() .with(cachemanagerbuilder.persistence("/tmp/ehcache-data")) .build(); cachemanager.init(); // 2. 配置缓存 cacheconfiguration<string, string> config = cacheconfigurationbuilder .newcacheconfigurationbuilder( string.class, string.class, resourcepoolsbuilder.newresourcepoolsbuilder() .heap(1000, entryunit.entries) // 堆内 .offheap(100, memoryunit.mb) // 堆外 .disk(1, memoryunit.gb, true) // 磁盘 ) .withexpiry(expirypolicybuilder.timetoliveexpiration(duration.ofminutes(10))) .build(); // 3. 创建缓存 cache<string, string> cache = cachemanager.createcache("mycache", config); // 4. 使用缓存 cache.put("key1", "value1"); string value = cache.get("key1"); system.out.println(value); // 5. 关闭 cachemanager.close(); } }
特性 | concurrenthashmap | guava cache | caffeine | ehcache |
基本缓存功能 | ✓ | ✓ | ✓ | ✓ |
过期策略 | ✗ | ✓ | ✓ | ✓ |
淘汰算法 | ✗ | lru | w-tinylfu | lru/lfu |
自动加载 | ✗ | ✓ | ✓ | ✓ |
异步加载 | ✗ | ✗ | ✓ | ✗ |
持久化支持 | ✗ | ✗ | ✗ | ✓ |
多级存储 | ✗ | ✗ | ✗ | ✓ |
命中率统计 | ✗ | 基本 | 详细 | 详细 |
分布式支持 | ✗ | ✗ | ✗ | ✓ |
内存占用 | 低 | 中 | 中 | 高 |
两级缓存与数据库的数据要保持一致,一旦数据发生了修改,在修改数据库的同时,本地缓存、远程缓存应该同步更新。
通过redis pubsub或rabbit mq等消息中间件实现跨节点通知
如果你不想在你的业务代码发送mq消息,还可以适用近几年比较流行的方法:订阅数据库变更日志,再操作缓存。canal 订阅mysql的 binlog日志,当发生变化时向mq发送消息,进而也实现数据一致性。
实现原理:
// 版本号校验示例 public product getproduct(long id) { cacheentry entry = localcache.get(id); if (entry != null) { int dbversion = db.query("select version from products where id=?", id); if (entry.version == dbversion) { return entry.product; // 版本一致,返回缓存 } } // 版本不一致或缓存不存在,从数据库加载 product product = db.loadproduct(id); localcache.put(id, new cacheentry(product, product.getversion())); return product; }
// 组合堆内与堆外缓存 cache<string, object> multilevelcache = caffeine.newbuilder() .maximumsize(10_000) // 一级缓存(堆内) .buildasync(key -> { object value = offheapcache.get(key); // 二级缓存(堆外) if(value == null) value = loadfromdb(key); return value; });
window-tinylfu
算法自动识别热点策略类型 | 适用场景 | 配置示例 |
基于大小 | 固定数量的小对象 | maximumsize(10_000) |
基于权重 | 大小差异显著的对象 | maximumweight(1gb).weigher() |
基于时间 | 时效性强的数据 | expireafterwrite(5min) |
基于引用 | 非核心数据 | softvalues() |
缓存对象生命周期特征:
内存结构影响:
// 典型缓存数据结构带来的内存开销 concurrenthashmap<string, product> cache = new concurrenthashmap<>(); // 实际内存占用 = 键对象 + 值对象 + 哈希表entry对象(约额外增加40%开销)
gc行为变化表现:
// 使用ohc(off-heap cache)示例 ohcache<string, product> ohcache = ohcachebuilder.newbuilder() .keyserializer(new stringserializer()) .valueserializer(new productserializer()) .capacity(1, unit.gb) .build();
优势:
代价:
// 按业务划分独立缓存实例 public class cacheregistry { private static loadingcache<string, product> productcache = ...; // 商品专用 private static loadingcache<integer, userprofile> usercache = ...; // 用户专用 // 独立配置各缓存参数 static { productcache = caffeine.newbuilder() .maximumsize(10_000) .build(...); usercache = caffeine.newbuilder() .maximumweight(100mb) .weigher(...) .build(...); } }
效果:
通过以上的分析和实现,可以通过redis+caffeine实现高性能二级缓存实现。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论