0

我有一个用例,我配置了多个具有不同属性的缓存管理器,以及用单独的缓存名称注释的不同方法。缓存的方法是从 http 客户端异步检索数据,并缓存响应。在上述用例中,来自两个缓存方法的数据在返回结果之前被合并。有时,结果仅包含来自缓存方法之一的数据,并且在刷新时问题得到解决。我无法理解在什么情况下会出现问题?

@Configuraions
public class CacheConfig{

    public static final String CACHE1 = "cache1";
    public static final String CACHE2 = "cache2";

    @Value("${cache.caffeineSpec:expireAfterWrite=43200s,maximumSize=1000,recordStats}")
    private String cacheSpec1;

    @Value("${cache.caffeineSpec: expireAfterWrite=3600s,maximumSize=2000,recordStats}")
    private String cacheSpec2;

    @Bean("cacheManager1")
    @Primary
    public CacheManager brokerDetailscacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager(CACHE1);
        cacheManager.setCaffeine(Caffeine.from(cacheSpec1));
        return cacheManager;
    }

    @Bean("cacheManager2")
    public CacheManager brokerTierCodeMapCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager(CACHE2, BROKER_TIER_CACHE);
        cacheManager.setCaffeine(Caffeine.from(cacheSpec2));
        return cacheManager;
    }
}

正在使用的型号

public class Person { 

 private String firstname;
 private String lastname;
 private List<Address> adresses;
}

private class Address { 

    private String street;
    private String City 
    private String zip;
}

private class PersonInfo {
    private String firstname;
    private String lastname;
    private Address address;
}

缓存的方法类是:

@Service
@RequiredArgsConstructor
public class PersonCache {

    private final DataClient dataClient;

    @Cacheable(cacheNames = CacheConfig.CACHE1, cacheManager = "cacheManager1" ,sync = true)
    public Map<String, Person> getPersonDetails(String firstname) {
        Map<String, Person> personMap = new HashMap()<>;
//Key is first name, grouping all results by firstname
        try {
            personMap = dataClient.getPersonDetails(firstname)
                                    .toCompletableFuture()
                                    .get(3, TimeUnit.SECONDS);

        }catch(Exception e) {
            log.error("Error fetching response from api". e);
        }
    }

    @Cacheable(cacheNames = CacheConfig.CACHE2, cacheManager = "cacheManager2" ,sync = true)
    public Map<String, Person> getPersonDetails(String firstname) {
        List<PersonInfo> personMap = new ArrayList();
        try {
            personMap = dataClient.getPersonInfoDetails(firstname)
                                    .toCompletableFuture()
                                    .get(3, TimeUnit.SECONDS);

        }catch(Exception e) {
            log.error("Error fetching response from api". e);
        }

        return transformPersonInfoToPerson(personMap);
    }
}

调用方法:

@Service
@RequiredArgsConstructor
public class PersonService {

    private final PersonCache personCache;

    public List<Person> getPersonDetails(String firstName) {
        Map<String, Person> personResponse1 = personCache.getPersonDetails(firstName);

        //.. after fetching for the first result set, check for a flag and call the below cache to append the data

        Map<String, Person> personResponse2 = personCache.getPersonInfoDetails(firstName);

        personResponse1.putAll(personResponse2);
        // This when returned at times does not contain any response from personResponse1 and only contains the personResponse2 data
        return personResponse1.values();
    } 

}

异步 API 调用是否可能导致某种未命中,并且第二个缓存的结果集被添加到结果中并返回?(调用方法也是从控制器类异步调用的)

无论端点被触发的次数如何,我应该如何处理以获得一致的响应?

4

1 回答 1

0

缓存键和值一旦进入缓存,就应该被视为不可变的。这是因为它们可用于多个线程,因此之后更改条目可能变得不可预测。当以不安全的方式完成时,该行为鲜为人知。

在您的代码中,缓存值作为 HashMap 返回personResponse1。然后对其进行修改以包含 的条目personResponse2。充其量这会将所有内容存储在 response1 中以供下次调用使用,但也可能导致损坏,因为多个线程不同步地写入其中。当损坏时,可能无法再次找到某些条目,例如在调整大小时,它们没有正确地重新散列到正确的表位置或不再位于 bin 的链接列表中。另一种可能性是,由于值的可变视图返回给未显示的客户端代码,因此该代码可能在处理它时删除了条目。实际行为变得不可预测,这就是为什么它在刷新后短时间内看起来是正确的,因为缓存会丢弃损坏的结果。

一个好的做法是存储一个不可变的Map.copy或用Collections.unmodifiableMap. 然后不允许任何突变,您会立即发现这一点。当使用缓存的响应时,将它们合并到一个新的地图中。很可能您的代码未缓存,因此响应没有被存储和共享,但是在此处添加缓存改变了这一点,因此您现在需要注意可变共享状态出现的问题。

于 2020-05-15T05:16:08.030 回答