在 Vert.x 应用程序中使用 Caffeine 的 AsyncLoadingCache

本文档将向您展示如何在 Vert.x 应用程序中使用 Caffeine 的 AsyncLoadingCache

您将构建什么

您将构建一个在网页中轮播图像的应用程序。

图像将由服务器从公共 API 下载,该 API 暴露在 https://http.cat

每张图片都以 🐱 的形式代表一个 HTTP 状态码。

准备好了吗?

100
注意
图片由 Tomomi Imura 创建 (@girlie_mac)

您需要什么

  • 文本编辑器或 IDE,

  • Java 11 或更高版本,

  • Maven 或 Gradle。

创建项目

此项目的代码包含功能等效的 Maven 和 Gradle 构建文件。

使用 Maven

以下是您应该使用的 pom.xml 文件的内容

使用 Gradle

假设您使用带有 Kotlin DSL 的 Gradle,您的 build.gradle.kts 文件应如下所示

网页实现

index.html 网页主要由以下部分组成

  1. body 中的一个 <img> 标签,以及

  2. 一个脚本,在页面加载后更改图像的 src 属性。

index.html
<!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-imgimg 标签

  2. 页面加载时运行脚本函数

  3. 定义一些 HTTP 状态码

  4. 以 250 毫秒的固定延迟周期性地调度函数执行

  5. 使用其 id 从 DOM 中检索 img 元素

  6. 使用随机选择的 HTTP 状态码更新 src 属性

服务器实现

我们将需要一个 Vert.x Web 客户端实例来获取图像

Web 客户端设置
    request = WebClient.create(vertx)
      .request(GET, new RequestOptions().setHost("http.cat").setPort(443).setSsl(true))
      .as(BodyCodec.buffer());

我们还需要一个缓存,因为我们不想使后端 API 过载

Caffeine 设置
    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 实例,将缓存绑定到字段,并始终从事件循环中调用其方法。如果这是您的用例,您可以将设置简化为

    cache = Caffeine.newBuilder()
      .expireAfterWrite(Duration.ofMinutes(1))
      .recordStats()
      .buildAsync((key, exec) -> {
        Future<Buffer> future = fetchCatImage(key);
        return future.toCompletionStage().toCompletableFuture();
      });

然而,如果您计划部署 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 方法

Main 方法
  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx(); // (1)
    vertx.deployVerticle(new CatsVerticle()).await(); // (2)
  }
  1. 创建一个 Vert.x 实例

  2. 部署 CatsVerticle

您可以从以下位置运行应用程序

  1. 您的 IDE,通过运行 CatsVerticle 类中的 main 方法,或者

  2. 使用 Maven:mvn compile exec:java,或者

  3. 使用 Gradle:./gradlew run (Linux, macOS) 或 gradlew run (Windows)。

浏览到 https://:8080

您应该会在网页中看到猫咪图片轮播

cats

一段时间后,检查程序输出。您应该会看到类似以下内容:

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}

请注意 hitCountmissCountevictionCount 中的变化。

总结

本文档涵盖了

  1. 用于发出 HTTP 请求的 Vert.x web 客户端,

  2. Vert.x web 服务器和路由器,

  3. Caffeine 异步加载缓存与 Vert.x 应用程序的集成,

  4. Vert.x 周期性任务。


最后发布:2025-02-01 01:26:03 +0000。