Атрибуты сеанса периодически пропадают - Spring Session + реализация Pivotal GemFire - PullRequest
0 голосов
/ 25 сентября 2018

Когда-то в Spring Session возникла странная проблема с интеграцией Pivotal GemFire.

У нас есть несколько HTTP-запросов, которые в конечном итоге устанавливают / получают атрибуты сеанса в различном порядке в зависимости от нескольких условий.

В определенный момент времени ...

    (T) session.getAttribute(sessionKeyN); // (T) is template object

... получает null.Мы перекрестно убедились, что между двумя вызовами session.getAttribute(..) не вызывается session.setAttribute(..), из которых один пропускает объект.

Мы включили ведение журнала трассировки в клиенте GemFire.Там мы видим несоответствие в читаемых / записываемых хэш-картах.Совместное использование журналов:

>2018-09-24T02:46:15:342-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.DataSerializer: **Writing HashMap with 8 elements**: {LOGIN_DATE_TIME=09-24-2018 02:46:08 AM, TERMINAL=terminal.Terminal@5aa33970, org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME=testuser, LOG_TRANSACTION_ID=20180924_451_5_4,20180912045104000005, USER=user.User@70b4b11b, TRANSACTION_HEADER=TransactionHeader@4144221c, XXXX_LOGIN=true, SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@65bb8fc1: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@65bb8fc1: Principal: testuser; Credentials: [PROTECTED]; Authenticated: true; Details: null;}
>2018-09-24T02:46:15:342-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.internal.**InternalDataSerializer: basicWriteObject**: {LOGIN_DATE_TIME=09-24-2018 02:46:08 AM, TERMINAL=terminal.Terminal@5aa33970, org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME=testuser, LOG_TRANSACTION_ID=20180924_451_5_4,20180912045104000005, USER=user.User@70b4b11b, TRANSACTION_HEADER=TransactionHeader@4144221c, XXXX_LOGIN=true, SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@65bb8fc1: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@65bb8fc1: Principal: testuser; Credentials: [PROTECTED]; Authenticated: true; Details: null;}
>2018-09-24T02:46:15:342-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.DataSerializer: Writing STRING_BYTES of len=8
>2018-09-24T02:46:15:342-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.DataSerializer: Writing String "testuser"
>2018-09-24T02:46:15:342-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.DataSerializer: Writing STRING_BYTES of len=36
>2018-09-24T02:46:15:342-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.DataSerializer: Writing String "5c4948d9-7438-4dff-badc-fdc0f9997781"
>2018-09-24T02:46:15:342-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.internal.InternalDataSerializer: basicWriteObject: { @type = org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository$GemFireSession, id = 5c4948d9-7438-4dff-badc-fdc0f9997781, creationTime = 2018-09-24T09:44:23.180Z, lastAccessedTime = 2018-09-24T09:46:14.909Z, maxInactiveInterval = PT30M, principalName = testuser }
>2018-09-24T02:46:15:342-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.internal.InternalDataSerializer: DataSerializer Serializing an instance of org.apache.geode.cache.Operation
>2018-09-24T02:46:15:342-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.internal.InternalDataSerializer: basicWriteObject: UPDATE
>2018-09-24T02:46:15:342-0700 [20180924_451_5_4,20180912045104000005] DEBUG org.apache.geode.cache.client.internal.PutOp: PutOpImpl constructing message with operation=UPDATE
>2018-09-24T02:46:15:144-0700 [20180924_451_5_4,20180912045104000005] DEBUG org.apache.geode.internal.cache.LocalRegion: invoking listeners: [org.springframework.session.data.gemfire.GemFireOperationsSessionRepository@4471a4f]
>2018-09-24T02:46:15:144-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.internal.cache.LocalRegion: dispatchListenerEvent event=EntryEventImpl[op=LOCAL_LOAD_CREATE;region=/XXXX2wl;key=5c4948d9-7438-4dff-badc-fdc0f9997781;oldValue=null;newValue={ @type = org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository$GemFireSession, id = 5c4948d9-7438-4dff-badc-fdc0f9997781, creationTime = 2018-09-24T09:44:23.180Z, lastAccessedTime = 2018-09-24T09:46:15.079Z, maxInactiveInterval = PT30M, principalName = testuser };callbackArg=null;originRemote=false;originMember=tstplXXXX0004(ClientConfigXXXX2Application:28299:loner):35884:0c27e20a:ClientConfigXXXX2Application;callbacksInvoked;version={v20; rv161; mbr=10.5.230.71(server_devplgemf0066:123628)<v23>:1024; time=1537782375131; remote};isFromServer]
>2018-09-24T02:46:15:144-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.internal.cache.versions.VersionTag: deserializing class org.apache.geode.internal.cache.versions.VMVersionTag with flags 0x4
>2018-09-24T02:46:15:144-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.internal.InternalDataSerializer: basicReadObject: header=1
>2018-09-24T02:46:15:144-0700 [20180924_451_5_4,20180912045104000005] TRACE org.apache.geode.DataSerializer: **Read HashMap with 9 elements**: {LOGIN_DATE_TIME=09-24-2018 02:46:08 AM, TERMINAL=terminal.Terminal@5a2aa051, **CUSTOMER_SEARCH_RESPONSE=CustomerInfo@600fa25f**, org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME=testuser, LOG_TRANSACTION_ID=20180924_451_5_4,20180912045104000005, USER=user.User@7178708f, TRANSACTION_HEADER=TransactionHeader@30215dcd, XXXX_LOGIN=true, SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@65bb8fc1: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@65bb8fc1: Principal: testuser; Credentials: [PROTECTED]; Authenticated: true; Details: null;}

Атрибут CUSTOMER_SEARCH_RESPONSE отсутствует, хотя session.setAttribute(..) для него не было вызвано.

Это не WRT для одного атрибута и также не соответствует.Повторное выполнение может не показать эту проблему.

1 Ответ

0 голосов
/ 21 декабря 2018

После работы с другим клиентом Pivotal (GemFire) над аналогичной проблемой (также используя Spring Session и Pivotal GemFire ​​(SSDG) для управления состоянием сеанса HTTP в высокопараллельном веб-приложении / среде), мы обнаружили,основные проблемы и, в конечном итоге, обнаруженные ошибки в Pivotal GemFire!

В двух словах, эти ошибки приводят к потерянным обновлениям из-за RaceУсловия , которые раздражены в высококонкурентной (многопользовательской) веб-среде, где несколько HTTP-запросов могут получить доступ и изменить один и тот же сеанс HTTP под нагрузкой.И чем больше параллелизм (пользователи) и чем больше нагрузка (количество HTTP-запросов к одному и тому же сеансу HTTP), тем более очевидной становится эта проблема.

На самом деле я написал несколько интеграционных тестов, иллюстрирующих этопроблема.

Сначала я написал тест интеграции нагрузки (MultiThreadedClientProxyRegionSessionIntegrationTests).Этот класс порождает 180 потоков (пользователей), выполняющих 10 000 одновременных запросов к одному базовому Session.Session объект, хотя и не совсем тот же, смоделирован после представления GemFireSession объекта SSDG.

Во-вторых, я написал еще один интеграционный тест (TwoThreadsClientProxyRegionSessionIntegrationTests), который надежно и многократновоспроизводит проблему.

Оба этих тестовых класса были написаны исключительно с использованием GemFire ​​API , тем самым иллюстрируя, что проблема связана с Pivotal GemFire, а не с SSDG.

Я написаланалогичный тест с использованием Spring Session Data GemFire ​​ как в моем примере , так и сейчас, , включенном в набор тестов SSDG (наряду со многими другими MultiThread /Интеграционные тесты на основе параллелизма ) сами по себе гарантируют, что Spring Session (для Pivotal GemFire) больше никогда не столкнется с этой проблемой, и если это произойдет, я узнаю об этом раньше, чем позже.

Короче говоря, 2 основных ошибки Pivotal GemFire:

Обходной путь выглядит следующим образом:

Во-первых, вы должны настроить клиентское приложение Spring Session, GemFire ​​для кэширования следующим образом:

  1. Клиент PROXY Регион для управления состоянием Session (по умолчанию)
  2. Установить copy-on-read установить на true .
  3. И вы должны использовать GemFire ​​DataSerialization, установив sessionSerializerBeanName соответствующим образом:

    @ SpringBootApplication @ClientCacheApplication (copyOnRead = верно, subscriptionEnabled = истина) @EnableGemFireHttpSession (clientRegionShortcut = ClientRegionShortcut.PROXY, sessionSerializerBeanName = GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME) класс MySpringBootSpringSessionDataGemFireApplication {...}

См здесь , например.

Вам также потребуется обновить до Spring Session для Pivotal GemFire ​​ 2.1.2.RELEASE (будет выпущен в ближайшее время),так как я сделал несколько важных недавних улучшений, таких как:

  1. Проблема № 12 - Предотвращение SessionRepository.save (Session) для незапятнанных сессий.
  2. Проблема № 9 - добавлена ​​поддержка конфигурации на стороне сервера для GemFire ​​/ Geode DataSerialization, когда SSDG не используется для настройки Spring Session на серверах.
  3. Проблема № 17. Рассмотрим поддержку настраиваемой проверки объекта домена приложения IsDirty.

Использование GemFire ​​DataSerialization с Deltas не помешает, но значительноуменьшает вероятность потерянных обновлений и других состояний гонки, которые по своей природе наследуются в веб-среде, в частности, поскольку контейнер сервлетов (например, Tomcat) является многопоточным, обрабатывая каждый HTTP-запрос в отдельном потоке.

Несмотря на то, что SSDG прилагает огромные усилия для обеспечения того, чтобы представление сеанса HTTP (т. Е. GemFireSession) было поточно-ориентированным, вы также должны убедиться, что любой объект, который вы помещаете в сеанс HTTP, также является поточно-ориентированным, поскольку он может и, скорее всего,будет доступен более чем одному потоку в веб-приложении с высокой степенью одновременности, особенно 1, где более одного HTTP-запроса могут одновременно обращаться к одному и тому же сеансу HTTP (по идентификатору сеанса).

В любом случае, пища дляподумал.

Когда указанная выше конфигурация используется, все работает как положено, а если нет, потерянные обновления могут и будут возникать из-за ошибок GemFire!

Фактически, мой нагрузочный тест показал, чтоиз 10000 обновлений сеанса, где добавлено ~ 9800 атрибутов сеанса, только ~ 1100 успешно завершены, что составляет колоссальную потерю данных ~ 89% !!!

Однако, когда применяется вышеуказанная конфигурация, все данныеучитывается правильно.

Надеюсь, это поможет!

...