Общий кэш Scala и playframework между узлами

#scala #playframework

#scala #playframework

Вопрос:

У меня сложная проблема, и я не могу понять, какое из них является лучшим решением для ее решения.

это сценарий:

  1. У меня есть N серверов под одним балансировщиком нагрузки и базой данных.
  2. Все серверы подключаются к базе данных
  3. Все серверы запускают одно и то же идентичное приложение

Я хочу реализовать кеш, чтобы уменьшить время отклика и свести к минимуму HTTP-вызовы Server -> Database

Я реализовал его и работает как шарм на одном сервере … но мне нужно найти механизм для обновления всех других кэшей на других серверах, когда данные больше не действительны.

пример:

У меня есть сервер A и сервер B, оба имеют свой собственный кеш. При первом запросе извне, например, для получения информации о пользователе, отвечает сервер A.

его кэш пуст, поэтому ему нужно получить информацию из базы данных.

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

третий запрос, снова на сервере A, теперь данные находятся в кэше, он отвечает немедленно без запроса к базе данных.

четвертый запрос на сервере B — это запрос на запись (например, изменение имени пользователя), сервер B может вносить изменения в базу данных и обновлять свой собственный кэш, делая недействительным старого пользователя.

но на сервере A все еще есть старый недопустимый пользователь.

Поэтому мне нужен механизм, позволяющий серверу B обмениваться данными с сервером A (или N другими серверами) для аннулирования / обновления данных в кэше.

каков наилучший способ сделать это в scala play framework?

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

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

Надеюсь, я был ясен.

Спасибо

Ответ №1:

Поскольку вы используете Play, который под капотом уже использует Akka, я предлагаю использовать разделение кластера Akka. При этом экземпляры вашей службы воспроизведения будут образовывать кластер (включая обнаружение сбоев и т. Д.) При запуске и организовывать между собой, Какой экземпляр владеет информацией конкретного пользователя.

Итак, выполняя ваши запросы, первый запрос GET /userinfo/:uid попадает на сервер A. Обработчик запроса хэширует uid (например, с помощью murmur3: важно согласованное хэширование) и преобразует его, например, в сегмент 27. С момента запуска экземпляров это первый раз, когда мы получили запрос с участием пользователя в сегменте 27, поэтому создается сегмент 27, и, допустим, он становится собственностью сервера A. Мы отправляем сообщение (например GetUserInfoFor(uid) ) на новый UserInfoActor , который загружает необходимые данные из базы данных, сохраняет их в своей базе данных.состояние и ответы. Обработчик Play API получает ответ и генерирует ответ на HTTP-запрос.

Для второго запроса он предназначен для того же uid , но попадает на сервер B. Обработчик преобразует его в сегмент 27, и его кластерный сегмент знает, что A владеет этим сегментом, поэтому он отправляет сообщение UserInfoActor на A для того uid , у которого есть данные в памяти. Он отвечает информацией, а обработчик Play API генерирует ответ на HTTP-запрос из ответа.

Таким образом, все последующие запросы (например, третий, тот же GET , что и на сервере A) для получения информации о пользователе не будут касаться базы данных, независимо от того, на какой сервер они попали.

Для четвертого запроса, который, скажем, является POST /userinfo/:uid и попадает на сервер B, обработчик запроса снова хэширует uid сегмент to 27, но на этот раз мы отправляем, например, UpdateUserInfoFor(uid, newInfo) сообщение UserInfoActor на сервер A. Исполнитель получает сообщение, обновляет базу данных, обновляет информацию о пользователе в памяти и отвечает (либо что-то простое, Done либо новая информация). Обработчик запроса генерирует ответ на основе этого ответа.

Это работает очень хорошо: я лично видел, как системы, использующие разделение кластеров, сохраняют терабайты в памяти и работают с постоянной задержкой в миллисекунду, состоящую из одной цифры, для потоковой аналитики с интерактивными запросами. Сбой серверов, и действующие лица, работающие на серверах, перебалансируются до выживших экземпляров.

Важно отметить, что все, что соответствует вашим требованиям, является распределенной системой, и вам требуется строгая согласованность, то есть вы требуете, чтобы оно было недоступно в сетевом разделе (если B не может передать обновление A, у него нет другого выбора, кроме как отклонить запрос). Как только вы начнете говорить о географической избыточности и нескольких интернет-провайдерах, вы будете видеть разделы довольно регулярно. Единственный способ получить доступность в сетевом разделе — это ослабить требования к согласованности и согласиться с тем, что иногда GET он не будет включать последнюю PUT версию / POST / DELETE .

Ответ №2:

Вероятно, это не то, что вы хотите создать самостоятельно. Но существует множество распределенных кэшей, которые вы можете использовать, например, Ehcache или InfiniSpan. Я предлагаю вам изучить один из этих двух.