Доступ к компонентам с областью запроса в многопоточном веб-приложении

#java #multithreading #spring

#java #многопоточность #spring

Вопрос:

Сценарий: У нас есть управляемое Spring веб-приложение, которое выполняется внутри Websphere. (Spring 3.0.x, БЫЛО 7) Веб-приложение использует Websphere work manager через Spring WorkManagerTaskExecutor (настроенный с размером пула потоков 10) для выполнения операций чтения БД, требующих больших вычислительных ресурсов. Таким образом, в основном поступает запрос на создание, скажем, 10 разных документов. Для генерации документов для сбора / обработки данных необходимы только чтения из БД. Таким образом, мы в основном создаем 10 потоков для обработки 10 документов и в конце собираем 10 документов, возвращенных от 10 рабочих, объединяем их и записываем один большой ответ клиенту. Мы определили, что в то время как 10 потоков собирают / обрабатывают данные, выполняется множество аналогичных вызовов БД. Итак, что мы придумали, так это создать аспект вокруг наиболее часто выполняемых методов БД для кэширования ответа. Аспект настроен как одноэлементный, и кэш, используемый аспектом, автоматически подключается к аспекту с областью, установленной на request-scope, так что каждый запрос имеет свой собственный кэш.

Проблема: Теперь проблема с этим подходом заключается в том, что, когда потоки выполняют свои вызовы db, а аспект прерывается, мы получаем java.lang.IllegalStateException: No thread-bound request found исключение. Я понимаю, что это полностью справедливо, поскольку потоки выполняются вне контекста запроса.

Есть ли способ обойти эту проблему? Возможно ли применить аспект с кэшем с областью запроса к методам, вызываемым этими потоками?

Ответ №1:

Я не думаю, что вы можете сделать это напрямую. Даже если бы вы могли, это было бы немного некрасиво. Однако вы можете сгенерировать уникальный идентификатор запроса (или даже — использовать идентификатор сеанса, но осторожно с несколькими вкладками) и передать его в каждый поток обработки. Затем аспект может использовать этот идентификатор в качестве ключа к кэшу. Сам кеш также будет одноэлементным, но будет Map<String, X> , где String ID, а X — ваш кэшированный результат.

Чтобы упростить обработку, вы можете @Async использовать методы (вместо того, чтобы вручную создавать потоки), и каждому @Async методу может быть передан идентификатор кэша в качестве первого параметра.

(Конечно, ваши асинхронные методы должны возвращать Future<Result> , чтобы вы могли собирать их результаты в потоке запроса)

Комментарии:

1. Я думал об этом подходе, но единственным недостатком, который я нахожу, является то, что он заставит меня передавать уникальный идентификатор запроса вниз по потоку всем вызовам методов (во время выполнения потока), которые я хотел бы кэшировать. Кроме того, не станет ли кэш довольно большим через некоторое время? Что затем заставило бы меня периодически управлять им. Возможно, я что-то здесь упускаю.

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

3. Мне удалось обойти эту проблему. Я начал использовать SimpleAsyncTaskExecutor вместо WorkManagerTaskExecutor . Преимущество заключается в том, что SimpleAsyncTaskExecutor потоки никогда не будут использоваться повторно. Это только половина решения. Другая половина решения заключается в использовании a RequestContextFilter вместо RequestContextListener . RequestContextFilter имеет setThreadContextInheritable() метод, который в основном позволит дочерним потокам наследовать родительский контекст.