Infinispan复制缓存集群节点宕机引发数据不一致问题咨询
当前缓存配置
<cache-container name="qcachename" statistics-enabled="true"> <transport/> <replicated-cache name="cachename" statistics-enabled="true"> <expiration lifespan="60000"/> </replicated-cache> </cache-container>
问题原因分析
场景1:节点宕机瞬间部分消息无法检索
你的集群使用默认的异步复制模式(未显式配置<sync/>时,Infinispan复制缓存默认采用异步复制)。异步复制逻辑为:写入操作在本地节点完成后立即向应用返回成功,后台异步将数据复制到其他节点。当某节点宕机瞬间,活跃节点上刚完成本地写入但尚未完成异步复制的数据,会随宕机节点丢失;若写入操作在宕机节点完成但未复制到其他节点,其他活跃节点也无法检索到这部分数据,最终引发数据不一致。
场景2:节点恢复期间存删操作引发不一致
节点断电恢复后,在部署应用、加入集群的过程中尚未完成集群同步。此时另外两个节点执行的"存入+立即删除"操作,因第三节点未就绪,异步复制的操作日志无法同步到该节点。当第三节点最终加入集群时,状态转移过程中无法正确追赶上这两个操作的顺序,导致该节点缓存中残留本应被删除的数据,或出现数据状态与其他节点不匹配的情况。
解决方案
1. 切换为同步复制模式
将复制模式改为同步,确保写入操作必须得到指定数量节点的确认后才返回应用,避免异步复制带来的数据丢失风险。修改缓存配置:
<replicated-cache name="cachename" statistics-enabled="true"> <sync repl-count="2"/> <!-- 3节点集群中,要求至少2个节点确认写入 --> <expiration lifespan="60000"/> </replicated-cache>
repl-count="2":配置写入操作需要得到2个节点(本地+1个其他节点)的确认,符合3节点集群的多数派原则,既保证数据一致性,又避免单节点故障导致写入失败。
2. 配置节点启动时的状态转移
开启节点加入集群时的状态转移,确保新启动的节点能从集群中同步最新的缓存状态,避免因恢复期间的操作未同步导致的不一致:
<replicated-cache name="cachename" statistics-enabled="true"> <sync repl-count="2"/> <state-transfer enabled="true" timeout="30000"/> <!-- 启用状态转移,超时30秒 --> <expiration lifespan="60000"/> </replicated-cache>
state-transfer enabled="true":节点加入集群时,自动从其他节点同步当前缓存的完整状态,确保数据一致。
3. 启用缓存持久化(可选)
如果需要进一步提升数据可靠性,可配置缓存持久化到磁盘,即使节点完全宕机,恢复时也能从本地磁盘加载数据,减少对集群同步的依赖:
<replicated-cache name="cachename" statistics-enabled="true"> <sync repl-count="2"/> <state-transfer enabled="true" timeout="30000"/> <persistence passivation="false"> <file-store path="${jboss.server.data.dir}/infinispan/cachename"/> </persistence> <expiration lifespan="60000"/> </replicated-cache>
复制异常捕获配置
在同步复制模式下,当复制操作无法得到指定数量节点的确认时,Infinispan会自动抛出org.infinispan.util.concurrent.TimeoutException或org.infinispan.remoting.RemoteException,应用可直接捕获这些异常并进行重试、告警等处理。
此外,还可通过以下方式增强异常监控:
- 利用已配置的
statistics-enabled="true",通过JMX监控复制失败的统计指标(如replFailedWrites)。 - 注册缓存监听器,监听复制相关异常事件:
@Listener public class ReplicationErrorListener { @CacheEntryCreated public void onEntryCreated(CacheEntryCreatedEvent<?, ?> event) { if (event.isOriginLocal() && !event.isCommandSuccessful()) { // 处理本地写入但复制失败的情况 } } }
将监听器注册到缓存:
cache.addListener(new ReplicationErrorListener());
内容的提问来源于stack exchange,提问作者maryam maleki

