Alluxio异常排查一
了解 Alluxio 的朋友应该都清楚,加速是 Alluxio 特点中异常重要的一个特性。因此我们整个工程中都非常依赖和信任 Alluxio 的 MEM 所带来的性能提升。正是这种天真无知,让我经历了将近一个月的痛苦折磨。
事情发生在二个月之前。我们的软件,在那个月出现了两个比较极端的客户。保险行业的客户 A 和 税务部门的客户 B。保修行业客户 A 数据量一般,但是业务频繁。税务部门客户 B 访问不多,但是数据量巨大。 这两个客户都在那个月中,接连出现了问题。产品刚刚遇到极端客户,就出现问题,这个对于我们来说不是一件好事情。我们分别派出了两个研发到两个客户现场去处理问题。我在后方协调他们,并给予他们足够的支持。 简单描述一下问题。
- 保险客户 A,数据导入正常,每1小时就有一次数据的增量导入。问题是分析计算线程莫名卡死。
- 税务客户 B,数据量巨大,而且有超大表的关联计算。在数据导入部分就出现异常。
分析问题
我首先处理的是税务客户 B的问题。客户现场每一次的失败,都会伴随着 Netty 读写超时的异常。通过异常定位代码
RPCResponse response = listener.get(NettyClient.TIMEOUT_MS, TimeUnit.MILLISECONDS);
其中NettyClient.TIMEOUT_MS是一个配置参数。由于这个客户已经更改过多个与分配资源有关的配置,因此我首先调大了这个值,将默认30秒,调大为成了5分钟。然后,那次导数重要成功了。可是,我知道这个不是长久的做法,因为这个更改我并不知道其中根本原因。因为数据量巨大,于是我开始着手写测试工具,开始准备还原问题。果不其然,没过几天再次增加数据量后出现问题了。
我让前方人员暂时先再次调大超时时间,先确保客户能正常使用。我在公司这边又增加一个同事和我一同来定位问题。
好在我这边问题还原也还算顺利,可以稳定还原问题。
但是还原后我做的一件时间,并不是急着去定位问题。而是尝试了多次,把一次还原的时间从3小时的耗时降到了5分钟。这一步对于后续定位问题非常重要。
当时还有一个棘手的问题。系统中的 Alluxio 的 worker 无法 dump 线程(后来发现是由于系统用户权限导致)。这一点当时给我定位问题造成了很大的麻烦。当时已经没有时间和心思去处理什么dump 的事情了。我只能一步步增加日志,然后编译执行还原。在代码中打了很多类似下面的时间日志
if (LOG.isDebugEnabled()) {
LOG.debug("read_ end validate session {},block {},time:{}", sessionId, blockId, (System.currentTimeMillis() - time));
LOG.debug("read_ start readBlockRemote session {},block {},time:{}", sessionId, blockId, (System.currentTimeMillis() - time));
}
最后重要让我一步步的逼近,最终发现代码卡住在
long lockId = mLockManager.lockBlock(sessionId, blockId, BlockLockType.WRITE);
这个和我们前期讨论也是一致的,估计是某个资源地方有很强的竞争。 仔细分析了代码,这里上下文的逻辑上这样的
- 线程需要写入 Block A 数据,此时MEM 满了
- 线程需要换出 Block B来空出一部分空间。
- 就在线程给 Block B 上锁的时候,其它线程取优先给 Block B 上锁
- 导致此线程等待。因此如果等待的线程太多,那么必然有一个线程等待时间过长,从而超时。
从逻辑上也可以看出,线程最终会可以运行的。也解释了为什么调大超时时间可以成功。
解决问题
到这里我们特别凌乱,这是结构问题啊,超时是必然的。好在这个地方有 retry 逻辑存在,而换入换出空间本身就是准许失败的。经过仔细的分析和讨论,我们决定更改这部分的逻辑。增加以下的代码
public long tryLockBlock(long sessionId, long blockId, BlockLockType blockLockType) {
Lock lock = getBlockRWLock(sessionId, blockId, blockLockType);
if (!lock.tryLock()) {
return INVALID_LOCK_ID;
}
return storeLock(sessionId, blockId, lock);
}
将换入换出的lockBlock改为tryLockBlock,上锁失败后通过重试机制来重新找一个 block 换出。我在本地快速跑了测试,的确可以通过。毕竟是个重要的更改,为了安全起见,我更改了测试,压了远超客户数据的测试。反复跑了一夜才放心给客户换上。最终确定的确有效,超时时间恢复到默认,数据量多次增加也没有任何问题。