You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ignite 2.13.x并发更新缓存键报并发修改异常问题咨询

问题场景

业务中使用Ignite 2.13.x版本维护集群缓存,部分缓存键值存在并发更新操作,同时有并行的查询读取请求,运行时抛出如下报错:

java.sql.SQLException: Failed to update some keys because they had been modified concurrently [keys=[ROWKEY [idHash=628930863, hash=1637498765, rowkey=6, id=765436]]]
at org.apache.ignite.internal.jdbc.thin.JdbcThinConnection.sendRequest(JdbcThinConnection.java:1009)
at org.apache.ignite.internal.jdbc.thin.JdbcThinStatement.execute0(JdbcThinStatement.java:234)
at org.apache.ignite.internal.jdbc.thin.JdbcThinPreparedStatement.executeWithArguments(JdbcThinPreparedStatement.java:252)
at org.apache.ignite.internal.jdbc.thin.JdbcThinPreparedStatement.executeUpdate(JdbcThinPreparedStatement.java:96)
at sun.reflect.GeneratedMethodAccessor56.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:118)
at com.sun.proxy.$Proxy16.executeUpdate(Unknown Source)
服务端配置
<bean class="org.apache.ignite.configuration.IgniteConfiguration"
    id="igniteConfiguration">
    
    <property name="cacheKeyConfiguration">
        <list>
            <bean class="org.apache.ignite.cache.CacheKeyConfiguration">
                <property name="typeName" value="ROWKEY" />
                <property name="affinityKeyFieldName" value="rowkey" />
            </bean>
        </list>
    </property>

    <property name="clientMode" value="false" />
    <property name="gridName" value="6d-ignite-grid"></property>
    <property name="cacheConfiguration">
        <list>
            <bean class="org.apache.ignite.configuration.CacheConfiguration">
                <property name="name" value="persons"></property>
                <property name="cacheMode" value="PARTITIONED" />
                <property name="backups" value="0" />
                <property name="onheapCacheEnabled" value="false" />
                <property name="statisticsEnabled" value="true" />
                <property name="memoryPolicyName" value="Default_Region" />
                <property name="copyOnRead" value="false" />
                <property name="writeSynchronizationMode" value="FULL_SYNC" />
                <property name="affinity">
                    <bean
                        class="org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction">
                        <property name="excludeNeighbors" value="true" />
                        <property name="partitions" value="10" />
                        <property name="affinityBackupFilter">
                            <bean
                                class="org.apache.ignite.cache.affinity.rendezvous.ClusterNodeAttributeAffinityBackupFilter">
                                <constructor-arg>
                                    <array value-type="java.lang.String">
                                        <value>AVAILABILITY_ZONE</value>
                                    </array>
                                </constructor-arg>
                            </bean>
                        </property>
                    </bean>
                </property>

                <property name="queryEntities">
                    <list>
                        <bean class="org.apache.ignite.cache.QueryEntity">
                            <property name="keyType" value="ROWKEY"></property>
                            <property name="valueType" value="PERSONS"></property>
                            <property name="fields">
                                <map>
                                    <entry key="rowkey" value="java.lang.Long"></entry>
                                    <entry key="id" value="java.lang.Long"></entry>
                                    <entry key="name" value="java.lang.String"></entry>
                                    <entry key="birthday" value="java.lang.String"></entry>
                                    <entry key="gender" value="java.lang.String"></entry>
                                    <entry key="amount" value="java.lang.Long"></entry>
                                    <entry key="salary" value="java.lang.Long"></entry>
                                    <entry key="orgid" value="java.lang.String"></entry>
                                </map>
                            </property>
                            <property name="keyFields">
                                <set>
                                    <value>rowkey</value>
                                    <value>id</value>
                                </set>
                            </property>
                        </bean>
                    </list>
                </property>
            </bean>
        </list>
    </property>
    <property name="includeEventTypes">
        <list>
            <util:constant
                static-field="org.apache.ignite.events.EventType.EVT_TASK_STARTED" />
            <util:constant
                static-field="org.apache.ignite.events.EventType.EVT_TASK_FINISHED" />
            <util:constant
                static-field="org.apache.ignite.events.EventType.EVT_TASK_FAILED" />
            <util:constant
                static-field="org.apache.ignite.events.EventType.EVT_TASK_TIMEDOUT" />
            <util:constant
                static-field="org.apache.ignite.events.EventType.EVT_TASK_SESSION_ATTR_SET" />
            <util:constant
                static-field="org.apache.ignite.events.EventType.EVT_TASK_REDUCED" />
            <util:constant
                static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_PUT" />
            <util:constant
                static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_READ" />
            <util:constant
                static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_REMOVED" />
        </list>
    </property>
    <property name="peerClassLoadingEnabled" value="true"></property>

    <property name="discoverySpi">
        <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
            <property name="ipFinder">
                <bean
                    class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder">
                    <property name="addresses">
                        <list>
                            <value>127.0.0.1:47500..47509</value>
                        </list>
                    </property>
                </bean>
            </property>
        </bean>
    </property>

    <property name="memoryConfiguration">
        <bean class="org.apache.ignite.configuration.MemoryConfiguration">
            <property name="defaultMemoryPolicyName" value="Default_Region" />
            <property name="pageSize" value="4096" />
            <property name="systemCacheInitialSize" value="#{40 * 1024 * 1024}" />
            <property name="systemCacheMaxSize" value="#{40 * 1024 * 1024}" />
            <property name="memoryPolicies">
                <list>
                    <bean
                        class="org.apache.ignite.configuration.MemoryPolicyConfiguration">
                        <property name="name" value="Default_Region" />
                        <property name="initialSize" value="#{20 * 1024 * 1024}" />
                        <property name="maxSize" value="#{20 * 1024 * 1024}" />
                        <property name="pageEvictionMode" value="RANDOM_2_LRU" />
                        <property name="evictionThreshold" value="0.6" />
                        <property name="metricsEnabled" value="true" />
                    </bean>
                </list>
            </property>
        </bean>
    </property>
</bean>
问题原因与修复方案

该报错本质是Ignite乐观并发控制校验失败:多个并发事务同时更新同一个键时,后提交的事务检测到键的版本已经被其他事务修改,为避免数据覆盖直接抛出异常,结合当前配置还有几个放大冲突概率的问题:

  • 缓存设置backups=0无副本,节点负载波动、网络延迟都可能触发版本校验误判,生产环境不推荐该配置
  • 主键配置不匹配:QueryEntity中定义rowkey和id为联合键字段,但亲和性路由仅配置了rowkey,会出现同一条逻辑数据路由到不同分区的问题,加剧写入冲突
  • copyOnRead=false配置下,读写操作共享内存引用,高并发场景可能读到未提交的中间版本,触发不必要的版本校验失败

按以下步骤修复即可:

  1. 增加重试容错
    捕获该异常后做指数退避重试,JDBC更新逻辑配置3-5次重试即可覆盖绝大多数偶发并发冲突,参考实现:
    int maxRetry = 5;
    for (int i = 0; i <= maxRetry; i++) {
        try (PreparedStatement ps = conn.prepareStatement(updateSql)) {
            // 填充SQL参数
            ps.executeUpdate();
            break;
        } catch (SQLException e) {
            if (i == maxRetry || !e.getMessage().contains("modified concurrently")) {
                throw e;
            }
            Thread.sleep(10L * (i + 1));
        }
    }
    
  2. 修正配置项
    • 将persons缓存的backups调整为至少1,保证副本冗余,降低节点波动带来的异常
    • 统一主键路由规则:如果确实使用rowkey+id作为联合主键,自定义ROWKEY类实现正确的equals()和hashCode()方法,亲和性字段选择业务上区分度最高的字段即可,避免同一条数据被路由到多个分区
    • 高并发读写场景将copyOnRead设为true,关闭内存引用共享,避免读写互相干扰,该配置带来的性能损耗远低于冲突重试的开销
  3. 按需调整事务隔离级别
    如果业务不需要强一致的串行化隔离,可以在JDBC连接串添加参数TRANSACTION_SERIALIZABLE=READ_COMMITTED,降低乐观锁校验严格度,减少非必要冲突,资金类强一致场景不建议调整该参数。

内容的提问来源于stack exchange,提问作者Ajay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 15:42:09