JVM: Использование глобального атома в качестве хранилища кэша приложения в Clojure - это уместно? - PullRequest
0 голосов
/ 02 мая 2020

У меня есть приложение с высокой нагрузкой , многие пользователи запрашивают его с различными параметрами GET. Представьте, что вы даете разные ответы на разные опросы. Сохраните голосование, покажите последние результаты опроса.

Чтобы смягчить проблему противодавления, я думал о создании атома верхнего уровня для хранения всех последних результатов опроса для всех опросов.

Итак, рабочий процесс примерно так:

  1. загрузить приложение => приложение получает последние результаты опроса и заполняет атом.

  2. поступает новый запрос = > увеличить счетчик голосов в этом атоме для определенного опроса c, добавить полезную нагрузку голосования в слушатель очереди core.asyn c (работающий в отдельном потоке), чтобы в конечном итоге сохранить его в базе данных.

Цель, которую я пытаюсь достичь:

каждый новый запрос получает последние результаты опроса с почти мгновенным временем ответа (избегайте сетевых вызовов в постоянное хранилище)

Очевидное Недостаток этого подхода в том случае, если нам потребуется его повторное развертывание, это приведет к некоторой временной потере данных. Это не очень важно, развертывание может быть отложено.

Причина, по которой я заинтересован в этом хитром подходе, а не просто в использовании RabbitMQ / Kafka, заключается в том, что он звучит как действительно классная и простая архитектура с очень небольшим количеством " движущиеся части "(просто JVM + база данных), чтобы выполнить работу.

1 Ответ

1 голос
/ 03 мая 2020

Больше данных всегда хорошо. Давайте время увеличим счетчик в атоме:

(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 нс. Сколько времени занимает пинг 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

Ваши операции с атомом перегружены временем сетевого ввода-вывода, поэтому нет проблем с сохранением состояния приложения в атоме даже для большого числа пользователей.

Вам также может быть интересно узнать, что база данных Datomi c заявляет, что параллелизм в базе данных управляется одним атомом.

...