东莞网站设计建设公司,做三国的网站,专业网站设计公司价格,网站制作怎样容易文章目录 一.什么是多级缓存#xff1f;1.本地缓存2.远程缓存3.缓存层级4.加载策略 二.适合/不适合的业务场景1.适合的业务场景2.不适合的业务场景 三.Redis与Caffine的对比1. 序列化2. 进程关系 四.各本地缓存性能测试对比报告(官方)五.本地缓存Caffine如何使用1. 引入maven依… 文章目录 一.什么是多级缓存1.本地缓存2.远程缓存3.缓存层级4.加载策略 二.适合/不适合的业务场景1.适合的业务场景2.不适合的业务场景 三.Redis与Caffine的对比1. 序列化2. 进程关系 四.各本地缓存性能测试对比报告(官方)五.本地缓存Caffine如何使用1. 引入maven依赖2.关于Caffine的各api操作介绍 六.多级缓存方案与实现思路七.小结 一.什么是多级缓存
多级缓存技术是一种通过多个层次的缓存来提高数据访问速度和降低延迟的策略。多级缓存通过在不同层次上缓存数据来减少对底层存储系统的访问次数提高系统的整体性能。在Java中常见的多级缓存结构包括本地缓存与远程缓存。
1.本地缓存
Caffeine/Guava/jdk下的线程安全Map等等,因为Caffine性能最高我这里本地缓存都代指Caffine。在应用程序的内存中存储数据访问速度极快但容量有限。 Caffeine是一个基于Java 8的高性能缓存库它提供了高性能、高命中率、低内存占用的特性被誉为最快的缓存之一 Caffeine是一个基于Java 8的高性能缓存库它提供了高性能、高命中率、低内存占用的特性被誉为最快的缓存之一。 JDK内置的Map可作为缓存的一种实现方式然而严格意义来讲其不能算作缓存的范畴。原因如下一是其存储的数据不能主动过期二是无任何缓存淘汰策略。 2.远程缓存
如Redis/Memcached在网络中存储数据容量大但访问速度相对较慢。因为我没用过Memcached,这里远程缓存代指Redis。
3.缓存层级
一级缓存本地缓存直接与应用程序关联适合频繁访问的数据。二级缓存远程缓存作为一级缓存的补充存储相对较不常访问的数据。
4.加载策略
先从本地缓存获取数据如果不存在再去远程缓存获取最后如仍不存在则从数据库获取并缓存到远程和本地。
这种多级缓存结构能有效提高应用程序性能降低数据库压力。
二.适合/不适合的业务场景
1.适合的业务场景
Caffeine 适合需要快速访问、短期存储的数据场景如频繁查询的热点数据、计算结果缓存等尤其是在高并发环境下表现优异。 它特别适用于以下业务场景
常用数据的枚举值例如类目数据这类数据变更频率低且对实时性要求不高适合使用Caffeine进行缓存。依赖第三方系统的一些不频繁变更的键值对先在本地缓存中查找如果存在则直接返回不存在则调用第三方系统获取数据并存入本地缓存中。这种模式适用于那些不是经常变化的数据可以减少对外部系统的依赖提高系统响应速度。
2.不适合的业务场景
Caffeine不适合实时性要求高或数据变更频繁的场景对于需要持久存储的数据或是数据更新频繁且需要实时一致性的场景就不太适合因为 Caffeine 的数据是保存在内存中的可能会导致数据丢失或不一致。因为这些场景对数据的实时性和准确性要求极高而Caffeine的设计初衷是为了提供高性能的本地缓存而不是实时同步外部数据源的变化。此外Caffeine也不适合需要强一致性保证的数据存储因为它主要关注性能和命中率而不是数据的一致性。
总的来说Caffeine适合那些对数据变更频率不高、对实时性要求不是特别严格的应用场景通过减少对外部数据源的访问次数提高系统的整体性能和响应速度。 个人认为其实不单单是我们本地缓存就是分布式缓存Redis也不适合数据变更频繁的业务场景。引入缓存的本质是为了提高性能减少db操作但是面对db修改频繁的场景又是引入本地缓存又是分布式缓存又用其他中间件去解决这个不一致性(更何况哪天你们公司真正高并发起来这个不一致性还无法完全解决这就是系统的一个坑埋在这了)所以个人觉得db修改频繁就不应该使用缓存!!! 网上人家经常说什么高并发下如何保证缓存与数据库一致性 比如1.通过延时双删。2.使用canal增量日志并提供增量数据的订阅与消费获取到变更数据则更新缓存 3.使用消息队列等等一系列措施。个人觉得这本来就是个伪命题高并发下你对数据变更频繁的场景使用缓存真的就合适吗真正高并发下用了这些但凡一丁点中间件的网络波动一致性也是无法完全保证的高并发下缓存与数据库一致性就是个无法完全解不了的问题只能减少不一致。 当然如果并发量少使用上述的方案基本不会有问题但是想想我们这个使用缓存 中间件的成本真的就比查询一次db低吗。 三.Redis与Caffine的对比
从横向对常用的缓存进行对比有助于加深对缓存的理解有助于提高技术选型的合理性。下面对比缓存Redis、Caffeine。
1. 序列化
Redis必须实现序列化。进程间数据传输因此必须实现序列化。大多数情况下涉及内网网络传输作为缓存数据库使用持久化是标配。Caffeine不需要实现序列化。Map对象的改进型接口不涉及任何形式的网络传输和持久化因此完全不需要实现序列化接口。
2. 进程关系
Redis与业务进程独立业务系统重启对缓存服务无影响Redis服务与业务服务独立互相影响较小Caffeine附着于业务进程业务系统重启缓存数据会全部丢失纯内存型缓存与业务系统属于同一个JVM
四.各本地缓存性能测试对比报告(官方)
以下是Caffeine官方给出的基准测试结果在与其他的本地缓存性能对比中身居第一位。Caffeine的读写性能要远好于Guava甚至超过不带缓存特性的ConcurrentHashMap。 具体详见官方给出的基本测试报告https://github.com/ben-manes/caffeine/wiki/Benchmarks-zh-CN
生成计算 在这个 基准测试 中缓存是无界且被完全填充的并且生成计算的结果将返回一个常量。这个基准测试体现了生成计算元素的时候将当前元素加锁产生的开销。如果调用不存在Caffeine 首先会进行一次无锁的预筛选在进行原子操作。绘图的场景是所有线程对(“sameKey”)进行查询并基于Zipf在各个线程中查询不同的key(“spread”)。 读 (100%) 在这个基准测试中 8 线程对一个配置了最大容量的缓存进行并发读。
读 (75%) / 写 (25%) 在这个基准测试 中对一个配置了最大容量的缓存6 线程 进行并发读2 线程进行并发写。
写 (100%) 在这个基准测试 中8 线程对一个配置了最大容量的缓存进行并发写。
五.本地缓存Caffine如何使用
通过官方的基准测试所以我们既然要用到本地缓存机制(例如需要用到缓存过期、过期监听、淘汰策略)等选型那就用性能最厉害的Caffine它支持多种缓存策略如基于大小、时间的过期策略等。下面是一些常用的操作 API 及其示例代码。
1. 引入maven依赖 !-- 本地缓存 --dependencygroupIdcom.github.ben-manes.caffeine/groupIdartifactIdcaffeine/artifactIdversion2.9.3/version/dependency2.关于Caffine的各api操作介绍
1. 创建缓存import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;CacheString, String cache Caffeine.newBuilder()// 设置最大缓存条目数.maximumSize(100) // 设置写入后的过期时间.expireAfterWrite(10, TimeUnit.MINUTES) // 初始的缓存空间大小.initialCapacity(20)// 缓存的最大条数.maximumSize(100).removalListener(((key,value,cause)-{log.info(缓存失效通知,key{},原因{},key,cause);})).build();
2. 基本的缓存操作添加、查询、删除缓存值// 存入缓存
cache.put(key1, value1);// 获取缓存值
String value cache.getIfPresent(key1);
System.out.println(value); // 输出: value1// 缓存中有key2则返回缓存中的值缓存中没有key2的值则通过loadFromDatabase方法从数据库或其他来源加载数据并存入缓存。
String value cache.get(key2, key - loadFromDatabase(key));
// 输出从数据库加载的值
System.out.println(value); // 删除某个缓存项
cache.invalidate(key1); // 清空所有缓存项
cache.invalidateAll(); 3. 异步加载缓存
Caffeine 也支持异步加载缓存当缓存项不存在时异步调用加载方法。AsyncCacheString, String asyncCache Caffeine.newBuilder().maximumSize(100).expireAfterWrite(10, TimeUnit.MINUTES).buildAsync();// 异步获取缓存值
CompletableFutureString futureValue asyncCache.get(key3, key - loadFromDatabaseAsync(key));// 异步处理获取结果
futureValue.thenAccept(value - System.out.println(Value: value));4. 基于时间的过期策略
Caffeine 支持基于时间的缓存过期机制如写入后的过期、访问后的过期等。写入后过期
CacheString, String cache Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES) // 写入后 5 分钟过期.build();访问后过期
CacheString, String cache Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES) // 访问后 5 分钟过期.build();
5. 基于缓存大小的淘汰策略
你可以通过 maximumSize 或 maximumWeight 方法设置缓存的大小限制。按照缓存项的数量限制
CacheString, String cache Caffeine.newBuilder().maximumSize(100) // 最多存储 100 条记录.build();按照缓存项的权重限制
CacheString, String cache Caffeine.newBuilder().maximumWeight(1000) // 总权重限制为 1000.weigher((key, value) - value.length()) // 以值的长度为权重.build();6. 基于软引用或弱引用的缓存
Caffeine 支持使用软引用或弱引用存储缓存值当 JVM 内存不足时可以自动回收这些缓存。使用弱引用存储键
CacheString, String cache Caffeine.newBuilder().weakKeys() // 使用弱引用存储键.build();使用软引用存储值
CacheString, String cache Caffeine.newBuilder().softValues() // 使用软引用存储值.build();7. 统计缓存命中率
Caffeine 支持记录缓存的命中率、加载时间等统计信息。
CacheString, String cache Caffeine.newBuilder().maximumSize(100).recordStats() // 启用统计信息.build();// 获取统计信息
System.out.println(cache.stats());8. 与 LoadingCache 的结合
LoadingCache 是 Caffeine 提供的一个更高级的缓存操作类它支持自动同步加载数据的功能。LoadingCacheString, String loadingCache Caffeine.newBuilder().maximumSize(100).expireAfterWrite(10, TimeUnit.MINUTES).build(key - loadFromDatabase(key)); // 自动加载缓存// 直接获取缓存值如果缓存中没有则调用 loadFromDatabase
String value loadingCache.get(key1);
System.out.println(value);总结Caffeine 提供了丰富的 API 来满足不同业务场景的缓存需求。它不仅支持基本的缓存操作还提供了多种淘汰策略、异步缓存以及统计功能适用于多种场景。
从上面8点中有没人发现第8点:loadingCache.get(“key1”)与第2点 cache.get(“key2”, key - loadFromDatabase(key)); 功能基本一致都是实现缓存中有则从缓存中取缓存中没有则从db查询并存入缓存中。 只不过是加载逻辑的定义不同一个是在 build() 时预定义一个是每次 get() 时传递加载逻辑。
何时选择 LoadingCache 如果所有的键加载逻辑相同你可以事先定义加载方式并希望缓存缺失时自动加载数据LoadingCache 是理想的选择。它提供了简洁的接口和良好的同步处理。何时选择 Cache.get(key, keyMapper) 如果每个键的加载逻辑不同或你希望在每次获取时灵活指定加载方式那么 Cache.get(key, keyMapper) 更加合适。它提供了更大的灵活性来动态处理不同的缓存加载需求。两种方案实现缓存中有从缓存中取 db查询再塞入缓存总结 LoadingCache 适用于需要统一加载策略、且不需要每次都指定加载逻辑的场景。Cache.get(key, keyMapper) 适用于需要根据具体情况动态指定加载逻辑的场景更加灵活但相对复杂。你可以根据自己的业务场景选择合适的缓存操作方式。
六.多级缓存方案与实现思路
下面是一个简单的多级缓存实现示例结合了Caffeine作为本地缓存和Redis作为远程缓存。我们在项目里面可以把缓存定义成配置bean, redis可以使用RedisTemplate。 这样一个多级缓存机制就实现啦是不是很简单。
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import redis.clients.jedis.Jedis;import java.util.concurrent.TimeUnit;public class MultiLevelCache {private final CacheString, String localCache;private final Jedis remoteCache;public MultiLevelCache() {// 创建本地缓存CaffinelocalCache Caffeine.newBuilder()// 设置最大缓存条目数.maximumSize(100) // 设置写入后的过期时间.expireAfterWrite(10, TimeUnit.MINUTES) // 初始的缓存空间大小.initialCapacity(20)// 缓存的最大条数.maximumSize(100).removalListener(((key,value,cause)-{log.info(缓存失效通知,key{},原因{},key,cause);})).build();// Redis连接remoteCache new Jedis(localhost); }public String getData(String key) {// 先从本地缓存获取String value localCache.getIfPresent(key);if (value ! null) {return value;}// 本地缓存未命中尝试从远程缓存获取value remoteCache.get(key);if (value ! null) {// 更新本地缓存localCache.put(key, value); return value;}// 最后从数据库获取假设为getDataFromDatabase方法value getDataFromDatabase(key);// 更新远程和本地缓存remoteCache.set(key, value);localCache.put(key, value);return value;}private String getDataFromDatabase(String key) {// 模拟数据库查询return DatabaseValueFor: key;}
}七.小结
主要介绍了什么是多级缓存什么是本地缓存、什么是分布式缓存本地缓存比分布式缓存快的原因。各本地缓存的性能对比中Caffine的性能是最高的Caffine的Api使用,多级缓存的设计与实现等等。谈到接口性能优化我们除了sql调优还能从哪些方面优化ok,当然是多级缓存技术方案啦合适的业务场景下使用redis配合本地缓存效率又能提升些。除了缓存技术呢 ok比如使用数据传输上的压缩像请求参数或者使用OpenFeign进行rpc调用响应值等等这些都可以使用GZIP压缩数据传输。 像OpenFeign底层http连接是通过jdk下的URLConnection我们可以引入Apach 下的HttpClient, 或者okhttp 等这些底层有用到连接池可以复用连接等等。这些全都是我们接口性能的一些优化手段。