会话属性间歇性丢失-Spring Session + Pivotal GemFire实施

时间:2018-09-25 11:53:06

标签: spring spring-session gemfire spring-data-gemfire

在春季会话中的某个时候,通过Pivotal GemFire集成解决了一个奇怪的问题。

我们有多个HTTP请求,这些请求最终会根据几种条件以不同的顺序设置/获取会话属性。

在某个给定的时间...

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

...正在检索null。我们已经交叉验证了在两个session.setAttribute(..)调用之间没有调用任何session.getAttribute(..),其中有一个错过了对象。

我们在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;}

即使没有调用session.setAttribute(..),也缺少属性CUSTOMER_SEARCH_RESPONSE。

这不是对一个属性的WRT,也不是一致的。重新运行可能不会显示此问题。

1 个答案:

答案 0 :(得分:1)

在与另一个Pivotal(GemFire)客户合作解决类似问题(也使用 Spring Session 和Pivotal GemFire(SSDG)在高度并发的Web应用程序/环境中管理HTTP会话状态)之后,我们发现了潜在的问题,并最终在Pivotal GemFire中发现了BUGS!

简而言之,由于 种族条件 ,这些错误导致 丢失的更新 并发(多用户)Web环境,其中多个HTTP请求可能正在负载下访问和修改同一HTTP会话。而且,并发(用户)越大,负载(同一个HTTP会话的HTTP请求数)越大,这个问题就越明显。

事实上,我已经写了几个集成测试来说明这个问题。

首先,我写了Load Integration TestMultiThreadedClientProxyRegionSessionIntegrationTests)。此类产生180个线程(用户)对同一基础Session执行10,000个并发请求。 Session对象虽然不完全相同,但是是根据SSDG的GemFireSession对象表示来建模的。

第二,我写了另一个Integration TestTwoThreadsClientProxyRegionSessionIntegrationTests),可以可靠地重复出现此问题。

这两个测试类都是用GemFire API编写的,因此说明了Pivotal GemFire是问题所在,而不是SSDG。

我在example和现在的included in the SSDG test suite(以及许多其他MultiThread/Concurrency based integration tests)中都使用了 Spring Session Data GemFire 编写了类似的测试,本身,确保 Spring Session (对于Pivotal GemFire)将永远不会再遇到此问题,如果确实如此,我会早知道这一问题。

简而言之,两个潜在的Pivotal GemFire错误是:

解决方法如下:

首先,您必须使用以下命令配置您的Spring Session GemFire缓存客户端应用程序:

  1. 用于管理PROXY状态的客户端Session区域(默认)
  2. copy-on-read设置为 true
  3. 而且,您必须通过适当设置sessionSerializerBeanName来使用GemFire DataSerialization:

    @SpringBootApplication @ClientCacheApplication(copyOnRead = true,subscriptionEnabled = true) @EnableGemFireHttpSession(   clientRegionShortcut = ClientRegionShortcut.PROXY,   sessionSerializerBeanName = GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME ) 类MySpringBootSpringSessionDataGemFireApplication { ... }

例如,参见here

您还需要升级到 Pivotal GemFire春季会议 2.1.2.RELEASE(即将发布),因为我做了一些重要的,最近的改进,例如:

  1. Issue #12 - Prevent SessionRepository.save(Session) on non-dirty Sessions.
  2. Issue #9 - Add server-side configuration support for GemFire/Geode DataSerialization when SSDG is not used to configure Spring Session on the servers.
  3. Issue #17 - Consider support for customizable IsDirty application domain object checking.

在Deltas中使用GemFire DataSerialization不会阻止,但会大大减少在Web环境中固有丢失继承更新和其他竞争条件的可能性,尤其是因为Servlet容器(例如Tomcat)是多线程的,因此会处理一个单独的线程。

尽管SSDG尽力确保HTTP会话表示形式(即GemFireSession)是线程安全的,但您还必须确保放入HTTP会话中的任何对象也是线程安全的,因为它可以并且在高度并发的Web应用程序中,最有可能会被1个以上的线程访问,尤其是1个线程中,一个以上的HTTP请求可以一次访问同一HTTP会话(按会话ID)。

反正值得深思。

使用上述配置时,一切都会按预期工作,否则,由于GemFire BUGS造成的更新丢失并可能会发生!

事实上,我的负载测试表明,在10,000个Session更新中,其中添加了〜9800个Session属性,只有〜1100个成功更新,这意味着〜89%的数据丢失!!

但是,当应用上述配置时,所有数据都正确计算了。

希望这会有所帮助!