在 Vert.x 应用中使用 Caffeine 的 AsyncLoadingCache
本文将向您展示如何在 Vert.x 应用中使用 Caffeine 的 AsyncLoadingCache
。
您将构建什么
您将构建一个在网页中轮播图片的应用。
图片将由服务器从一个在 https://http.cat 公开的 API 下载。
每张图片都以 🐱 的形式代表一个 HTTP 状态码。
准备好了吗?
图片由 Tomomi Imura (@girlie_mac) 创建 |
您需要什么
-
一个文本编辑器或 IDE,
-
Java 11 或更高版本,
-
Maven 或 Gradle。
创建项目
此项目的代码包含功能等效的 Maven 和 Gradle 构建文件。
使用 Maven
以下是您应该使用的 pom.xml
文件的内容
pom.xml
<?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>
<groupId>io.vertx.howtos</groupId>
<artifactId>async-loading-cache-caffeine-howto</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<vertx.version>5.0.0.CR2</vertx.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<mainClass>io.vertx.howtos.caffeine.CatsVerticle</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
使用 Gradle
假设您使用 Kotlin DSL 的 Gradle,您的 build.gradle.kts
文件应如下所示
build.gradle.kts
plugins {
java
application
}
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
}
dependencies {
implementation(platform("io.vertx:vertx-stack-depchain:5.0.0.CR2"))
implementation("io.vertx:vertx-core")
implementation("io.vertx:vertx-web")
implementation("io.vertx:vertx-web-client")
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
}
application {
mainClass = "io.vertx.howtos.caffeine.CatsVerticle"
}
网页实现
index.html
网页主要包含
-
body 中的一个
<img>
标签,以及 -
一个脚本,在页面加载后改变图片的
src
属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Rotating Cats Page</title>
<script type="application/javascript">
window.onload = function () { (2)
const codes = [ (3)
100,
200, 201, 204, 206,
301, 302, 303, 304, 307, 308,
401, 403, 404, 406, 407, 409, 410, 412, 416, 418, 425, 451,
500, 501, 502, 503, 504,
]
setInterval(function () { (4)
let img = document.getElementById("cat-img"); (5)
img.src = "/api/cats/" + codes[Math.floor(Math.random() * codes.length)]; (6)
}, 250)
}
</script>
</head>
<body>
<div>
<img src="/api/cats/200" id="cat-img" alt="Cat image"/> (1)
</div>
</body>
</html>
1 | body 中 id 设置为 cat-img 的 img 标签 |
2 | 页面加载时运行脚本函数 |
3 | 定义一些 HTTP 状态码 |
4 | 以 250 毫秒的固定延迟周期性执行一个函数 |
5 | 使用其 id 从 DOM 中检索 img 元素 |
6 | 使用随机选择的 HTTP 状态码更新 src 属性 |
服务器实现
我们将需要一个 Vert.x Web 客户端实例来获取图片
request = WebClient.create(vertx)
.request(GET, new RequestOptions().setHost("http.cat").setPort(443).setSsl(true))
.as(BodyCodec.buffer());
我们还需要一个缓存,因为我们不希望后端 API 过载
cache = Caffeine.newBuilder() (1)
.expireAfterWrite(Duration.ofMinutes(1)) (2)
.recordStats() (3)
.executor(cmd -> context.runOnContext(v -> cmd.run())) (4)
.buildAsync((key, exec) -> CompletableFuture.supplyAsync(() -> { (5)
Future<Buffer> future = fetchCatImage(key); (6)
return future.toCompletionStage(); (7)
}, exec).thenComposeAsync(Function.identity(), exec));
vertx.setPeriodic(20000, l -> { (8)
CacheStats stats = cache.synchronous().stats();
log.info("Stats: " + stats);
});
1 | 创建一个缓存构建器 |
2 | 配置缓存以在 1 分钟后使项目过期 |
3 | 启用统计信息记录 |
4 | 定义一个在 verticle 上下文中调用任务的执行器 |
5 | 使用缓存执行器创建一个异步加载器,它必须返回一个 CompletableFuture |
6 | 获取猫咪图片 |
7 | 将 Vert.x Future 转换为 CompletionStage |
8 | 定期记录缓存统计信息 |
执行器定义和复杂的加载器实现在这里并非严格必需。 事实上,我们将部署一个 verticle 实例,将缓存绑定到一个字段,并始终从事件循环中调用其方法。如果这是您的用例,您可以将设置简化为 但是,如果您计划部署 verticle 的多个实例并在它们之间共享缓存,请坚持使用之前的实现。它保证异步加载器始终在正确的上下文中被调用。 |
获取猫咪图片包括使用相应的 HTTP 状态码作为 URI 向后端发送请求
private Future<Buffer> fetchCatImage(int code) {
return request.uri("/" + code)
.send()
.expecting(HttpResponseExpectation.SC_OK)
.map(HttpResponse::body);
}
使用 Vert.x Web,为我们的 API 和静态文件创建 HTTP 服务器非常简单
Router router = Router.router(vertx);
router.get("/api/cats/:id").produces("image/*").handler(this::handleImageRequest);
router.get().handler(StaticHandler.create());
return vertx.createHttpServer()
.requestHandler(router)
.listen(8080)
.onSuccess(v -> log.info("Server started on port 8080"));
我们将这样实现图片请求处理
private void handleImageRequest(RoutingContext rc) {
Integer code = Integer.valueOf(rc.pathParam("id")); (1)
CompletableFuture<Buffer> completableFuture = cache.get(code); (2)
Future<Buffer> future = Future.fromCompletionStage(completableFuture, context); (3)
future.onComplete(ar -> { (4)
if (ar.succeeded()) {
rc.response()
.putHeader("Cache-Control", "no-store") (5)
.end(ar.result());
} else {
rc.fail(ar.cause());
}
});
}
1 | 从请求路径中检索指定的代码 |
2 | 调用 Caffeine(如果需要,图片将从后端透明加载) |
3 | 将 Caffeine 返回的 CompletableFuture 转换为 Vert.x Future |
4 | 完成后,将图片字节(或失败信息)发送到客户端 |
5 | 指示浏览器禁用图片缓存(否则,它只会针对给定代码向我们的服务器查询一次!) |
运行应用程序
CatsVerticle
需要一个 main
方法
public static void main(String[] args) {
Vertx vertx = Vertx.vertx(); (1)
vertx.deployVerticle(new CatsVerticle()).await(); (2)
}
1 | 创建一个 Vert.x 实例 |
2 | 部署 CatsVerticle |
您可以从以下方式运行应用程序
-
通过从
CatsVerticle
类运行main
方法,或者 -
使用 Maven:
mvn compile exec:java
,或者 -
使用 Gradle:
./gradlew run
(Linux, macOS) 或gradlew run
(Windows)。
浏览至 https://:8080。
您应该会看到猫咪图片在网页中轮播

一段时间后,检查程序输出。您应该会看到类似以下内容:
Mar 22, 2022 3:45:17 PM io.vertx.howtos.caffeine.CatsVerticle lambda$start$4 INFO: Stats: CacheStats{hitCount=52, missCount=28, loadSuccessCount=28, loadFailureCount=0, totalLoadTime=2514949257, evictionCount=0, evictionWeight=0} Mar 22, 2022 3:45:37 PM io.vertx.howtos.caffeine.CatsVerticle lambda$start$4 INFO: Stats: CacheStats{hitCount=132, missCount=28, loadSuccessCount=28, loadFailureCount=0, totalLoadTime=2514949257, evictionCount=0, evictionWeight=0} Mar 22, 2022 3:45:57 PM io.vertx.howtos.caffeine.CatsVerticle lambda$start$4 INFO: Stats: CacheStats{hitCount=212, missCount=28, loadSuccessCount=28, loadFailureCount=0, totalLoadTime=2514949257, evictionCount=0, evictionWeight=0} Mar 22, 2022 3:46:17 PM io.vertx.howtos.caffeine.CatsVerticle lambda$start$4 INFO: Stats: CacheStats{hitCount=267, missCount=53, loadSuccessCount=52, loadFailureCount=0, totalLoadTime=3337599348, evictionCount=28, evictionWeight=28} Mar 22, 2022 3:46:37 PM io.vertx.howtos.caffeine.CatsVerticle lambda$start$4 INFO: Stats: CacheStats{hitCount=344, missCount=56, loadSuccessCount=56, loadFailureCount=0, totalLoadTime=3480880213, evictionCount=28, evictionWeight=28}
注意 hitCount
、missCount
或 evictionCount
的变化。
总结
本文档涵盖了
-
用于发出 HTTP 请求的 Vert.x web 客户端,
-
Vert.x web 服务器和路由器,
-
Caffeine 的异步加载缓存与 Vert.x 应用的集成,
-
Vert.x 周期性任务。