JVM:在 Clojure 中使用全局原子作为应用程序缓存存储——合适吗?

JVM: Using global atom as an application cache storage in Clojure - Is it appropriate?

我有一个高负载 应用程序,许多用户使用各种 GET 参数请求它。想象一下,对不同的民意调查给​​出不同的答案。保存投票,显示最新投票结果。

为了缓解背压问题,我正在考虑创建一个顶级原子来存储所有投票的所有最新投票结果。

所以工作流程是这样的:

  1. 启动应用程序 => 应用程序提取最新的投票结果并填充原子。

  2. 新请求进来 => 在该原子中为特定轮询增加投票计数器,将投票有效负载添加到 core.async 队列侦听器(在单独的线程中工作)以将其持久化到数据库最终。

我要实现的目标:

每个新请求都会以近乎即时的响应时间获取最新的轮询结果(避免网络调用持久存储)

这种方法的一个明显缺点是如果我们需要重新部署它会导致一些临时数据丢失。不是很严重,可以推迟部署。

我对这种棘手的方法感兴趣而不只是使用 RabbitMQ/Kafka 的原因是它听起来像一个非常酷和简单的架构,只有很少的 "moving parts"(仅 JVM + 数据库)完成工作。

数据越多越好。让我们在一个原子中增加一个计数器:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require
    [criterium.core :as crit]))

(def cum (atom 0))

(defn incr []
  (swap! cum inc))

(defn timer []
  (spy :time
    (crit/quick-bench
      (dotimes [ii 1000] incr))))

(dotest
  (timer))

结果

-------------------------------
   Clojure 1.10.1    Java 14
-------------------------------

Testing tst.demo.core
Evaluation count : 1629096 in 6 samples of 271516 calls.
             Execution time mean : 328.476758 ns
    Execution time std-deviation : 37.482750 ns
   Execution time lower quantile : 306.738888 ns ( 2.5%)
   Execution time upper quantile : 393.249204 ns (97.5%)
                   Overhead used : 1.534492 ns

所以对 incr 的 1000 次调用只需要大约 330 纳秒。 ping google.com 需要多长时间?

PING google.com (172.217.4.174) 56(84) bytes of data.
64 bytes from lax28s01-in-f14.1e100.net (172.217.4.174): icmp_seq=1 ttl=54 time=14.6 ms
64 bytes from lax28s01-in-f14.1e100.net (172.217.4.174): icmp_seq=2 ttl=54 time=14.9 ms
64 bytes from lax28s01-in-f14.1e100.net (172.217.4.174): icmp_seq=3 ttl=54 time=15.0 ms
64 bytes from lax28s01-in-f14.1e100.net (172.217.4.174): icmp_seq=4 ttl=54 time=17.8 ms
64 bytes from lax28s01-in-f14.1e100.net (172.217.4.174): icmp_seq=5 ttl=54 time=16.9 ms

我们称之为 15 毫秒。所以比例是:

ratio = 15e-3 / 330e-9  =>  45000x

您对 atom 的操作被网络 I/O 时间所淹没,因此将应用程序状态存储在 atom 中没有问题,即使对于大量用户也是如此。

您可能还想知道 the Datomic database 已经声明数据库中的并发是由单个原子管理的。