线上一个数据库死锁问题的分析

问题

有一个业务,需要记录调用次数,于是我不假思索的写出了如下代码

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());这一行造成了死锁,没办法,代码不会骗人,只能自己找问题了

于是我用我缜密的逻辑思维能力,结合实际情况,有了如下推断

  1. removalListener不是单线程执行 -- 单线程就会排队执行更新,不会有锁争抢
  2. removalListener 不是定期执行,如果是定期执行,我的updateCount不会需要10秒
  3. removalListener 是在缓存放入或者获取的时候检查并执行

基于如上判断,要出现问题,就是getEntry()在其他事物中调用了,而且这个事物由于某些原因导致事物时间过长

检查代码后发现,有个接口在事物中调用了getEntry()然后调用了外部服务,外部服务响应时间过长导致了事物时间被延长

这里我想到几个解决方法

  1. updateCount方法放入单线程排队,这样需要写不少代码
  2. 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();
复制代码

问题暂时得到解决.

猜你喜欢

转载自juejin.im/post/5e684de1e51d4526d87c842c