我有一个 高负荷 应用,很多用户用各种GET参数来请求。想象一下,对不同的投票给出不同的答案。保存投票,显示最新投票结果。
为了缓解背压问题,我在考虑创建一个顶层原子来存储所有投票的最新投票结果。
启动一个应用程序 => 应用程序拉入最新的投票结果并填充原子。
新的请求进来 => 在该原子中为特定的投票增加投票计数器,将投票有效载荷添加到core.async队列监听器(在一个单独的线程中工作),最终将其持久化到数据库中。
每一个新的请求都能得到最新的投票结果,并且有近乎即时的响应时间(避免网络调用持久化存储)。
这种方法有一个明显的缺点,就是万一我们需要重新部署,会造成一些暂时的数据损失。这不是很关键,部署可能会被推迟。
我之所以对这种棘手的方法感兴趣,而不仅仅是使用RabbitMQKafka,是因为它听起来像一个非常酷和简单的架构,只有很少的 "移动部件"(只有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
所以1000个电话 incr
只需要330 ns左右。 需要多长时间才能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
你对原子的操作会被网络IO时间所淹没 所以在原子中存储应用状态是没有问题的 即使对于大量的用户来说也是如此
你可能也有兴趣知道 Datomic数据库 曾表示,数据库中的并发是由一个原子管理的。