数据

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

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

您将构建什么

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

图片将由服务器从一个在 https://http.cat 公开的 API 下载。

每张图片都以 🐱 的形式代表一个 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 方法

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

您可以从以下方式运行应用程序

  1. 通过从 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 周期性任务。