网站首页 > java教程 正文
本文介绍基于企业应用和微服务系统的常用的 Java 开源工具类,这类工具功能强大、性能良好,在项目中应用可提高系统性能,增强代码可维护性。本文以高速缓存 Caffeine为例简要分析开源工具,了解开源工具是如何把性能提高到极致的。
高速缓存 Caffeine
说到缓存,就要说一下现代计算机 CPU 的组成:CPU 除中央处理器外还有一级缓存和二级缓存,甚至三级缓存;在 CPU 中,缓存的作用是弥补低速外部存储和高速处理的 CPU 之间不匹配的缺陷。
在现代系统中,我们面对的是高并发快速响应的需求目标,但一直横亘在我们面前的难题是DataBase的速度不能大幅度地提升,也就无法实现目标的快速响应。
借鉴现代计算机结构中的解决办法,在系统中开始引入缓存。
Caffeine 的主要作者是 Ben Manes,Ben 是 Google 的前成员,也是 ConcurrentLinkedHashMap的数据结构作者。
Caffeine 的开发原因是 Ben 想用 Java 8 重写 Guava Cache 库,因此 API 在设计上与 Guava Cache 几乎一致,并且提供了 Guava Cache 的适配器,使得 Guava Cache 可以使Caffeine,从而极为平滑地从 Guava Cache 迁移至 Caffeine。
虽然说是重写了 Guava Cache,但与 Guava Cache 使用了不同的设计,Caffeine 使用了更先进的算法(Window-TinyLFU)和更优秀的数据结构(Striped Ring Buffer、TimeWheel),带来了极为灵活的配置、超强的性能,以及高命中率(最接近 Optimal 的命中率)。
Caffeine 不是一个分布式缓存,也不支持持久化。那为什么依然要使用它呢?
因为在一个系统中,我们设计缓存部分的时候,为了获得更好的性能和稳定性,不能只考虑分布式缓存,还应该考虑用 Caffeine 实现一级缓存,甚至是虚拟机内的多级缓存。
一、安装 Caffeine
在 Pom 中加入以下配置即可使用 Caffeine:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
<!--加入 guava-testlib 是为了在时间淘汰策略中配置基准时钟,而不采用系统时钟-->:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<version>28.0-jre</version>
<scope>compile</scope>
</dependency>
Caffenine 目前的最新版本是 3.1.1,适用于 JDK 11,本书以 JDK 8 为主,所以使用 2.x 版本。Caffeine 的缓存架构在 3.x 版本中并没有变化。
二、Caffeine 的基本使用方法
Caffeine 作为虚拟机内缓存,使用方式相对简单,它支持手动填充、同步自动载入、异步手动填充和异步自动载入四种填充数据的办法。获得一个同步载入的 Cache:
//manualLoads
Cache<String, SkuInfo> cache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.maximumSize(100)
.build();
String key = "11757834";
SkuInfo skuInfo = new SkuInfo(key);
cache.put(key, skuInfo);
//取出缓存对象,如果没有,则返回空
SkuInfo cachedSkuInfo = cache.getIfPresent(key);
//移出 Key
cache.invalidate(key);
skuInfo = cache.get(key, k -> service.query(k));
以上创建了一个最大容量为 100 个、写过期是 1 分钟的缓存。这里的写过期是指如果缓存项写入或被替换超过 1 分钟,则自动被删除。缓存的管理是 Caffeine 的重点,将在后面详细介绍。
SkuInfo 是需要缓存的对象,定义如下:
public class SkuInfo {
private final String key;
private String name ;
public SkuInfo(String key) {
this.key = key;
this.name = "商品 "+key;
}
@Override
public String toString() {
return "SkuInfo{" + "key='" + key + '\'' + ", name='" + name + '\'' + '}';
}
}
手动填充数据就是由用户明确地控制数据的获取、更新和移除。实体可以直接使用cache.put(key, value)方法写入缓存,如果是用于更新操作,那么会覆盖原有的相同 Key 的实体。
推荐使用 cache.get(key, k -> value)方法获取值,可以原子性计算并插入缓存,避免与写操作竞争。
可以使用 cache.invalidate(key)方法使数据立刻从 Cache 中移除,而不用等待 Caffeine 在维护周期内清理不可用的 Value。
大多数场景下使用自动载入方式,以下代码是建立一个自动载入缓存 LoadingCache:
//synchronizeLoading
LoadingCache<String, SkuInfo> cache = Caffeine.newBuilder()
.maximumSize(2)
.expireAfterWrite(1, TimeUnit.MINUTES)
//同步加载
.build( k -> service.query(k));
String key = "11757834";
SkuInfo skuInfo = cache.get(key);
System.out.println(skuInfo);
异步手动填充是当数据不在缓存中时,使用 Future 异步填充数据。与手动填充的区别在于,get 方法返回的不是 Value,而是 CompletableFuture,等待异步执行完成后,交由用户自己做一些后续的处理。
以下代码是获得一个 AsyncCache:
AsyncCache<String, SkuInfo> cache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(1, TimeUnit.MINUTES)
.buildAsync();
String key = "11757834";
CompletableFuture<SkuInfo> completableFuture = cache.get(key,k -> service.query(k));
completableFuture.thenAccept(skuInfo -> {
System.out.println(skuInfo);;
});
异步自动载入 AsyncLoadingCache 的 API 与手动自动载入的 API 很相似,buildAsync 可选传入 CacheLoader 和 AsyncCacheLoader。如果希望直接同步返回值,那么传入 CacheLoader;如果希望异步返回值,则传入 AsyncCacheLoader。
可以在构建 Cache 时使用 Caffeine.executor方法指定一个线程池,默认的线程执行使用的是 ForkJoinPool.commonPool(),实际项目建议自定义一个线程池便于管理。
//asyncLoading
AsyncLoadingCache<String, SkuInfo> cache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(1, TimeUnit.MINUTES)
.executor(pool)
.buildAsync(k -> service.query(k));
String key = "11757834";
CompletableFuture<SkuInfo> completableFuture = cache.get(key);
completableFuture.thenAccept(skuInfo -> {
System.out.println(skuInfo);
});
如果想将异步缓存转为同步调用,则可以调用 cache.synchronous()方法,返回一个映射相同Cache 的 LoadingCache 同步视图,任何在此视图上的操作,都会映射至底层 Cache,如果操作被阻塞,那么会一直等待阻塞结束才继续进行。
cache.asMap()方法返回一个映射相同 Cache 的 ConcurrentMap 视图,任何一种 Cache 都具有此方法。返回的视图是线程安全的,并且保持和底层 Cache 的弱一致性,任何对此视图的操作和修改都会反映到 Cache 中;对视图进行迭代时,如果对底层缓存进行诸如驱逐/删除等操作,那么将无法反映在视图上。
内容摘自《高性能Java系统权威指南》第五章
本书特点:
内容上,总结作者从事Java开发20年来在头部IT企业的高并发系统经历的真实案例,极具参考意义和可读性。
对于程序员和架构师而言,Java 系统的性能优化是一个超常规的挑战。这是因为 Java 语言和 Java 运行平台,以及 Java 生态的复杂性决定了 Java 系统的性能优化不再是简单的升级配置或者简单的 “空间换时间”的技术实现,这涉及 Java 的各种知识点。
本书从高性能、易维护、代码增强以及在微服务系统中编写Java代码的角度来描述如何实现高性能Java系统,结合真实案例,让读者能够快速上手实战。
风格上,本书的风格偏实战,读者可以下载书中的示例代码并运行测试。读者可以从任意一章开始阅读,掌握性能优化知识为公司的系统所用。
本书适合:
中高级程序员和架构师;
以及有志从事基础技术研发、开源工具研发的极客阅读;
也可以作为 Java 笔试和面试的参考书。
猜你喜欢
- 2025-01-01 Java开发者的福音 - Hutool工具(缓存组件)
- 2025-01-01 一文搞懂JAVA 中的引用
- 2025-01-01 Java并发编程(20)CPU处理器中高速缓存的数据结构
- 2025-01-01 Java 缓冲输入输出是什么?怎么用?
- 2025-01-01 网站慢?试试这个Java实时缓存高招!
- 2025-01-01 实战派 | Java项目中玩转Redis6.0客户端缓存
- 2025-01-01 13 如何利用缓存实现万级并发扣减
- 2025-01-01 Java线程池newCachedThreadPool和newFixedThreadPool对比
- 2025-01-01 JVM简介—1.Java内存区域
- 2025-01-01 不同业务场景该如何选择缓存的读写策略?
你 发表评论:
欢迎- 04-24Java Collections 工具类集合框架中常用算法解析
- 04-24桶排序的简单理解
- 04-24Java集合框架底层实现原理大揭秘
- 04-24Java 集合框架全面解析:选对数据结构,提升开发效率
- 04-24c#集合排序
- 04-24Java面试中常被问到的集合类深度解读
- 04-24VBA技术资料MF278:对集合进行排序
- 04-24Spring 最常用的 7 大类注解,史上最强整理
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)