问题
有一个业务,需要记录调用次数,于是我不假思索的写出了如下代码
public Entry getEntry() {
...
repository.updateCount(id);
return entry;
}
复制代码
由于这个查询方法调用非常频繁,导致频繁更新数据库,我就想我可以现将数据缓存起来,然后定期更新到数据库,从而可以降低数据库写入io,然后我写下了如下代码
private final Cache<Integer, AtomicInteger> countCache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.expireAfterAccess(Duration.ofSeconds(10))
.removalListener((RemovalListener<? super Integer, ? super AtomicInteger>) notification -> {
Integer id = notification.getKey();
AtomicInteger count = notification.getValue();
if (id != null && count != null) {
repository.updateCount(id, count.get());
}
})
.build();
public Entry getEntry() {
...
try {
AtomicInteger count = countCache.get(entryId, AtomicInteger::new);
count.incrementAndGet();
} catch (ExecutionException e) {
e.printStackTrace();
}
return entry;
}
复制代码
非常完美,我先创建了一个缓存,设置10秒后过期,过期的时候会将调用次数更新到数据库,每次在获取数据的时候值需要更新缓存中的值,完美的循环, 直到我收到了线上服务器的报警.
线上报警说在repository.updateCount(id, count.get());
这一行造成了死锁,没办法,代码不会骗人,只能自己找问题了
于是我用我缜密的逻辑思维能力,结合实际情况,有了如下推断
removalListener
不是单线程执行 -- 单线程就会排队执行更新,不会有锁争抢removalListener
不是定期执行,如果是定期执行,我的updateCount
不会需要10秒removalListener
是在缓存放入或者获取的时候检查并执行
基于如上判断,要出现问题,就是getEntry()
在其他事物中调用了,而且这个事物由于某些原因导致事物时间过长
检查代码后发现,有个接口在事物中调用了getEntry()
然后调用了外部服务,外部服务响应时间过长导致了事物时间被延长
这里我想到几个解决方法
updateCount
方法放入单线程排队,这样需要写不少代码removalListener
中启用独立事物,这样就不影响外部事物了
我选择了方案2
缓存构建变成了如下代码
@Autowired
private PlatformTransactionManager transactionManager;
private final Cache<Integer, AtomicInteger> countCache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.expireAfterAccess(Duration.ofSeconds(10))
.removalListener((RemovalListener<? super Integer, ? super AtomicInteger>) notification -> {
Integer id = notification.getKey();
AtomicInteger count = notification.getValue();
if (id != null && count != null) {
final DefaultTransactionDefinition definition = new DefaultTransactionDefinition(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
final TransactionStatus status = transactionManager.getTransaction(definition);
repository.updateCount(id, count.get());
transactionManager.commit(status);
}
})
.build();
复制代码
问题暂时得到解决.