Infinispace Cache:不一致的写入并发操作

Posted

技术标签:

【中文标题】Infinispace Cache:不一致的写入并发操作【英文标题】:Infinispace Cache: Inconsistent writes concurrent actions 【发布时间】:2021-07-11 20:19:22 【问题描述】:

我到处寻找解决此问题的方法,但找不到任何提示。

我的应用程序部署在 JBOSS EAP 6.4 上并使用 JDK 1.8 构建。我在独立 xml 中配置了一个本地 infinispan 缓存:

<subsystem xmlns="urn:jboss:domain:infinispan:1.5">
    <cache-container name="test-cache" default-cache="test-data-cache" jndi-name="java:jboss/infinispan/test-cache" statistics-enabled="true">
        <transport lock-timeout="60000"/>
        <local-cache name="test-data-cache" statistics-enabled="true">
            <!-- <transaction locking="PESSIMISTIC"/> -->
            <locking isolation="READ_COMMITTED"/>
            <expiration lifespan="3600000"/>
        </local-cache>
    </cache-container>
</subsystem>

我将数据放入缓存中:

package com.comp.test;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;

import org.apache.commons.collections4.MapUtils;
import org.infinispan.Cache;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class CacheTest 

    @Resource(lookup = "java:jboss/infinispan/test-cache")
    private org.infinispan.manager.CacheContainer container;
    
    public void putData(String k, String v) 
        final Map<String, String> tmap = getDataCache().get(k);
        if (!containsData(k)) 
            final HashMap<String, String> rmap = new HashMap<>();
            rmap.put(k+"_"+v, v); 
            getDataCache().put(k, rmap);
         else 
            getDataCache().get(k).put(k+"_"+v, v);
        
    
    
    public boolean containsData(String k) 
        return getDataCache().containsKey(k);
    

    private Cache<String, Map<String, String>> getDataCache() 
        return container.getCache("test-data-cache");
    

我有无状态 bean,可以同时将数据集合放入缓存中(@Asyncronous 注释)。当所有并发操作结束后我从缓存中检索数据时,缓存总是具有较少数量的值。如果我放 20 个值,缓存中只有 16 / 17 个值。

我试图确定是否可以在将数据加注到该特定键的缓存中之前锁定该键。但我了解到它是由 Infinispan 内部处理的。我在 SO 上发现了另一个类似的问题。但这个问题也没有答案。 Infinispan cache inconsistency for massive concurrent operations

请告诉我如何确保同时放入 infinispan 本地缓存的数据最后是一致的。如果您需要更多信息,请告诉我。

【问题讨论】:

【参考方案1】:

您有两个使用悲观锁定的选项:

    getDataCache().getAdvancedCache().lock(k) getDataCache().getAdvancedCache().withFlags(Flag.FORCE_WRITE_LOCK)

还有第三种选择:改为使用乐观锁定,如果另一个事务修改了密钥,则重试该事务。但这不适用于@TransactionalAttribute,您必须自己致电TransactionManager.commit() 并接听WriteSkewException

【讨论】:

感谢您的提示。我不必手动启动和停止交易。我使用了“getDataCache().getAdvancedCache().lock(k)”并更改了我的缓存配置以使缓存具有事务性,并使用 PESSIMISTIC 作为锁定模式。我将在单独的答案中发布详细信息。但我接受你的回答,因为它提示锁定钥匙。 @user613114 在您发布答案后我才注意到另一件事:您在将 HashMap 插入缓存后对其进行了修改,这不是一个好主意,因为 a) 读者会能够在您修改它时访问同一个实例,并且HashMap 不是线程安全的(读取甚至可以在正确的时间点进入无限循环)和 b)如果您决定使缓存分布式或存储堆外数据,更改将不会正确应用。 感谢您的宝贵意见! 1)“读取甚至可以以正确的时间进入无限循环”->如何?我读的是 infinispan 允许异步读取没有任何锁。 2)我看不到任何使缓存分布式的可能性。如果我实现集群模式,我可能会复制它。在这种情况下,总会有一个节点为同一个键写入数据。其他节点可以读取数据。你觉得这有什么问题吗? @user613114 1) 看起来我的信息已经过时了:***.com/questions/35534906/… 2) 是的:如果您修改了 hashmap 并且不调用 cache.put(k, map),那么其他人将看不到地图更改节点【参考方案2】:

我用@Dan Berindei 提供的提示解决了这个问题。

我将缓存配置更改为:

<subsystem xmlns="urn:jboss:domain:infinispan:1.5">
    <cache-container name="test-cache" default-cache="test-data-cache" jndi-name="java:jboss/infinispan/test-cache" statistics-enabled="true">
        <transport lock-timeout="60000"/>
        <local-cache name="test-data-cache" start="EAGER" batching="false" statistics-enabled="true">
            <locking isolation="SERIALIZABLE" acquire-timeout="5000"/>
            <transaction mode="FULL_XA" locking="PESSIMISTIC"/>
            <expiration lifespan="3600000"/>
        </local-cache>
    </cache-container>
</subsystem>

然后我更新我的代码以在每次调用 put on Cache 时锁定密钥:

package com.comp.test;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;

import org.apache.commons.collections4.MapUtils;
import org.infinispan.Cache;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class CacheTest 

    @Resource(lookup = "java:jboss/infinispan/test-cache")
    private org.infinispan.manager.CacheContainer container;
    
    public void putData(String k, String v) 
        final Map<String, String> tmap = getDataCache().get(k);
        if (!containsData(k)) 
            final HashMap<String, String> rmap = new HashMap<>();
            rmap.put(k+"_"+v, v); 
            getDataCache().put(k, rmap);
         else 
                 AdvancedCache<String, Map<String, String>> cache = getDataCache().getAdvancedCache();
                cache.lock(k);
            getDataCache().get(k).put(k+"_"+v, v);
        
    
    
    public boolean containsData(String k) 
        return getDataCache().containsKey(k);
    

    private Cache<String, Map<String, String>> getDataCache() 
        return container.getCache("test-data-cache");
    

【讨论】:

以上是关于Infinispace Cache:不一致的写入并发操作的主要内容,如果未能解决你的问题,请参考以下文章

Cache写策略(Cache一致性问题与骚操作)

缓存技术和用户层缓存原理

缓存击穿问题与缓存设置顺序原则

反向代理缓存

Cache一致性与DMA

使用Spring Cache实现广告缓存并基于RabbitMQ实现双写一致