У меня есть приложение, назовите его Сервис 1, которое потенциально может выполнять множество тех же запросов к другому приложению, назовите его Сервис 2. В качестве примера, x количество людей используют Сервис 1, что приводит к x запросам (которые являются точными тот же запрос) к Сервису 2. Каждый ответ кэшируется в Сервисе 1.
В настоящее время у нас есть синхронизированный метод, который проверяет, был ли сделан тот же запрос в течение определенного порогового времени. Проблема, с которой мы сталкиваемся, заключается в том, что когда сервер находится под большой нагрузкой, когда метод synchronized блокирует потоки, кубернеты не могут выполнять проверки работоспособности, поэтому кубернеты перезапускают службу. Причина, по которой мы хотим предотвратить дублирование запросов, двоякая: 1) мы не хотим забивать службу 2 и 2) если мы уже делаем запрос, мы не хотим делать его снова, просто дождитесь результата, который уже вернусь.
Какое решение является наиболее быстрым и масштабируемым, чтобы избежать дублирования запросов без блокировки и отключения сервера?
Есть ли причина, по которой вы поддерживаете этот кеш на своем собственном сервере вместо службы поставщика кеша, такой как MemcacheD или Redis?
@NirajChauhan Мы знаем, что это тот же запрос, потому что запрос создаст тот же ключ и ресурс, которые попадают в кеш.
@Jay, мы используем ehcache в приложении. Почему? Это наследие
@jwill AFAIK, вы можете запустить EHCache на отдельном сервере и подключить ваше приложение к нему, используя адреса многоадресной рассылки. Таким образом, вам не нужно беспокоиться о том, что кеш в вашем приложении окажется слишком большим. Пусть этим занимается EHCache.
FWIW, мой опыт работы с rx-java очень ограничен, поэтому я не совсем уверен, насколько это применимо в вашем случае. Это решение, которое я использовал несколько раз со Scala, и я знаю, что в самой Java есть аналогичные конструкции, которые допускают такой же подход.
Решение, которое я использовал в прошлом, которое мне очень помогло, включает использование Futures. Это не уменьшает дублирование полностью, но удаляет дублирование для каждого запрашивающего сервера. Подход включает использование кэша TTL, в котором мы сохранили объект Future, который содержит или будет содержать результат запроса, который мы хотим выполнить дедупликацию. Он хранится под ключом, который может определять уникальность запроса, например, различные параметры, которые могут быть применимы.
Итак, допустим, у вас есть метод, который вы вызываете для получения ответа от службы 2 и возврата его как Future. В качестве примера мы скажем getPage
, у которого есть один параметр, целое число, которое представляет собой страницу, которую вы хотите получить.
Когда начинается запрос и мы собираемся вызвать getPage с номером страницы 2, мы проверяем кеш на наличие ключа типа «getPage: 2». Это не будет содержать ничего для первого запроса, поэтому мы вызываем getPage(2)
, который возвращает Future[SomeResponseObject]
. Мы устанавливаем "getPage: 2" в кэше TTL для объекта Future. Когда поступает другой запрос, который может порождать повторяющийся запрос, происходит такая же проверка кеша, однако объект Future
уже находится в кеше. Мы получаем это будущее и добавляем прослушиватель ответа, который будет вызываться, когда ответ доступен, или в Scala просто .map()
на нем.
У этого есть несколько преимуществ. Если ваш запрос выполняется медленно или очень много дублирующихся запросов даже в короткие сроки, многие запросы к Сервису 1 обслуживаются одним ответом от Сервиса 2.
Во-вторых, как только запрос к Сервису 2 вернулся, если у вас есть окно, в котором ответ все еще действителен, ответ уже доступен немедленно, и запрос вообще не нужен.
Если ваш запрос Service 2 занимает 50 мс, а ваш ответ можно считать действительным в течение 5 секунд, все запросы, поступающие на один и тот же сервер в первые 50 мс, обслуживаются в течение 50 мс при возврате ответа, а с этого момента - для оставшихся 4950 мс. мс уже есть доступ к ответу.
Как я уже упоминал ранее, эффективность здесь зависит от количества запущенных экземпляров службы 1. Количество повторяющихся запросов в любой момент времени линейно зависит от количества запущенных серверов.
Это в основном свободный от блокировок способ добиться этого. Я видел по большей части, потому что необходима некоторая синхронизация самого кэша TTL, чтобы убедиться, что запрос запускается только один раз, но, по моему опыту, это никогда не было проблемой для производительности.
В качестве расширения этого вы можете потенциально использовать что-то вроде redis для кеширования ответов от Service 2, если у него долгое время отклика, и пусть ваш эквивалент getPage
сначала проверит кеш redis для сериализованного ответа (и напишите истекающее значение, если оно там не было). Это позволяет еще больше сократить количество запросов к Сервису 2 за счет кэширования более глобального значения, но наличие второго уровня кэширования действительно добавляет сложности и повышает вероятность возникновения проблем.
как определить, такой же запрос или другой?