在春季会话中的某个时候,通过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,也不是一致的。重新运行可能不会显示此问题。
答案 0 :(得分:1)
在与另一个Pivotal(GemFire)客户合作解决类似问题(也使用 Spring Session 和Pivotal GemFire(SSDG)在高度并发的Web应用程序/环境中管理HTTP会话状态)之后,我们发现了潜在的问题,并最终在Pivotal GemFire中发现了BUGS!
简而言之,由于 种族条件 ,这些错误导致 丢失的更新 并发(多用户)Web环境,其中多个HTTP请求可能正在负载下访问和修改同一HTTP会话。而且,并发(用户)越大,负载(同一个HTTP会话的HTTP请求数)越大,这个问题就越明显。
事实上,我已经写了几个集成测试来说明这个问题。
首先,我写了Load Integration Test(MultiThreadedClientProxyRegionSessionIntegrationTests
)。此类产生180个线程(用户)对同一基础Session
执行10,000个并发请求。 Session
对象虽然不完全相同,但是是根据SSDG的GemFireSession
对象表示来建模的。
第二,我写了另一个Integration Test(TwoThreadsClientProxyRegionSessionIntegrationTests
),可以可靠地重复出现此问题。
这两个测试类都是用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缓存客户端应用程序:
PROXY
状态的客户端Session
区域(默认)copy-on-read
设置为 true 。而且,您必须通过适当设置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
(即将发布),因为我做了一些重要的,最近的改进,例如:
在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%的数据丢失!!
但是,当应用上述配置时,所有数据都正确计算了。
希望这会有所帮助!