#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
потоки никогда не будут использоваться повторно. Это только половина решения. Другая половина решения заключается в использовании aRequestContextFilter
вместоRequestContextListener
.RequestContextFilter
имеетsetThreadContextInheritable()
метод, который в основном позволит дочерним потокам наследовать родительский контекст.