spring - 会话属性间歇性丢失 - Spring Session + Pivotal GemFire 实现
问题描述
在 Spring Session 中与 Pivotal GemFire 集成的某个时候面临一个奇怪的问题。
我们有多个 HTTP 请求,它们最终会根据几个条件以不同的顺序设置/获取会话属性。
在某个给定的点...
(T) session.getAttribute(sessionKeyN); // (T) is template object
...正在检索null
。我们已经交叉验证了 nosession.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;}
属性 CUSTOMER_SEARCH_RESPONSE 丢失,即使没有session.setAttribute(..)
调用它。
这不是一个属性的 WRT,也不一致。重新运行可能不会显示此问题。
解决方案
在与另一位 Pivotal (GemFire) 客户合作解决类似问题后(也使用Spring Session和 Pivotal GemFire (SSDG) 在高度并发的 Web 应用程序/环境中管理 HTTP 会话状态),我们发现了潜在问题并最终发现Pivotal GemFire 中的错误!
简而言之,这些错误导致更新丢失,因为竞争条件在高度并发(多用户)Web 环境中被激怒,其中多个 HTTP 请求可能在负载下访问和修改同一个 HTTP 会话。而且,并发(用户)越大,负载越大(对同一 HTTP 会话的 HTTP 请求数),这个问题就越明显。
事实上,我已经写了几个集成测试来说明这个问题。
首先,我编写了一个负载集成测试( MultiThreadedClientProxyRegionSessionIntegrationTests
)。此类产生 180 个线程(用户),对同一底层执行 10,000 个并发请求Session
。该Session
对象虽然不完全相同,但在 SSDG 的GemFireSession
对象表示之后建模。
其次,我编写了另一个集成测试( TwoThreadsClientProxyRegionSessionIntegrationTests
),它可靠且重复地重现了该问题。
这两个测试类都是纯粹用GemFire API编写的,从而说明问题在于关键的 GemFire,而不是 SSDG。
我在我的示例中都使用Spring Session Data GemFire编写了类似的测试,现在,也包含在 SSDG 测试套件中(以及许多其他基于多线程/并发的集成测试)本身,确保Spring Session(用于 Pivotal GemFire)永远不会再次遇到这个问题,如果是这样,我迟早会知道的。
简而言之,两个潜在的 Pivotal GemFire 错误是:
解决方法如下:
首先,您必须配置 Spring Session、GemFire 缓存客户端应用程序:
PROXY
用于管理状态的客户端区域Session
(默认)- 设置
copy-on-read
为true。 而且,您必须使用 GemFire DataSerialization,通过
sessionSerializerBeanName
适当设置:@SpringBootApplication @ClientCacheApplication(copyOnRead = true, subscriptionEnabled = true) @EnableGemFireHttpSession( clientRegionShortcut = ClientRegionShortcut.PROXY, sessionSerializerBeanName = GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME ) 类 MySpringBootSpringSessionDataGemFireApplication { ... }
例如,请参见此处。
您还需要升级到Pivotal GemFire 的 Spring Session 2.1.2.RELEASE
(即将发布),因为我最近做了几个重要的增强,例如:
- 问题 #12 - 在非脏会话上防止 SessionRepository.save(Session)。
- 问题 #9 - 当 SSDG 不用于在服务器上配置 Spring Session 时,为 GemFire/Geode DataSerialization 添加服务器端配置支持。
- 问题 #17 - 考虑支持可自定义的 IsDirty 应用程序域对象检查。
使用带有 Delta 的 GemFire DataSerialization 不会阻止,但会大大降低丢失更新和其他竞争条件在 Web 环境中固有继承的可能性,特别是因为 Servlet 容器(例如 Tomcat)是多线程的,在单独的线程中处理每个 HTTP 请求.
虽然 SSDG 努力确保 HTTP 会话表示(即GemFireSession
)是线程安全的,但您还必须确保放入 HTTP 会话中的任何对象也是线程安全的,因为它可以并且很可能会被超过高并发 Web 应用程序中的 1 个线程,尤其是 1 个线程,其中多个 HTTP 请求一次可以访问同一个 HTTP 会话(按会话 ID)。
总之,值得深思。
使用上述配置时,一切都按预期工作,否则,由于 GemFire BUGS,可能并且将会发生丢失更新!
事实上,我的负载测试显示,在 10,000 个会话更新中,添加了约 9800 个会话属性,只有约 1100 个成功,这是高达 89% 的数据丢失!!!
但是,当应用上述配置时,所有数据都会正确计算。
希望这可以帮助!
推荐阅读
- angular - Angular6中ngFor中的api调用
- java - 从响应式网站程序中选择“选择文件”按钮时不提供相机选项
- python - python内部函数以不同方式对待可变变量和不可变变量
- php - 类admin不存在?Route::group 问题
- python - VSCode 的 Eventhub 触发器设置本地开发
- fortran - 在 Win10 上安装 VS 2015 和 Intel Fortran Compiler 2016 但仍然无法正常工作
- c++ - C++:使用缓冲区中的数据填充结构
- javascript - 当用户选择错误的日期时如何显示验证消息?
- dart - 如何在 Flutter 中重建 AnimatedWidget
- python - 对于二进制分类的信息量最大的特征,我的结果是否相同?