Alluxio异常排查一

Alluxio异常排查一

了解 Alluxio 的朋友应该都清楚,加速是 Alluxio 特点中异常重要的一个特性。因此我们整个工程中都非常依赖和信任 Alluxio 的 MEM 所带来的性能提升。正是这种天真无知,让我经历了将近一个月的痛苦折磨。

事情发生在二个月之前。我们的软件,在那个月出现了两个比较极端的客户。保险行业的客户 A 和 税务部门的客户 B。保修行业客户 A 数据量一般,但是业务频繁。税务部门客户 B 访问不多,但是数据量巨大。 这两个客户都在那个月中,接连出现了问题。产品刚刚遇到极端客户,就出现问题,这个对于我们来说不是一件好事情。我们分别派出了两个研发到两个客户现场去处理问题。我在后方协调他们,并给予他们足够的支持。 简单描述一下问题。

  1. 保险客户 A,数据导入正常,每1小时就有一次数据的增量导入。问题是分析计算线程莫名卡死。
  2. 税务客户 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 换出。我在本地快速跑了测试,的确可以通过。毕竟是个重要的更改,为了安全起见,我更改了测试,压了远超客户数据的测试。反复跑了一夜才放心给客户换上。最终确定的确有效,超时时间恢复到默认,数据量多次增加也没有任何问题。

最近的文章

Alluxio异常排查二

Alluxio异常排查二接着上篇,保险客户的问题。问题分析负责这个客户的研发,突然有一天找我说,客户卡死的问题需要看一下。好在客户可以 dump 线程状态出来的。从客户那边 dump 了5个线程快照,每个间隔2秒。可是从 dump 的线程状态上,并没有发现有卡死的线程存在,每个线程做的事情在下一个 dump 文件都会变化。无从下手,于是我再次尝试还原问题,可是这次没有成功。尝试多次都以失败告终。但是,有一点可以确定的,肯定还是线程锁定问题,因为从 dump 中看到快照中多数线程在等待锁。因...…

继续阅读
更早的文章

Flink笔记--容错数据流

Flink学习笔记-容错数据流有状态的函数和操作需要存储关于计算的数据,这使得状态成为复杂计算的关键。在 Flink 中的每一种函数和操作都可以成为有状态的。为了达到很好的容错,Flink 的容错机制持续的记录分布式的数据流的快照。这些快照是非常轻量化的,因此高频的记录快照并不会影响性能。当程序由于机器,网络甚至是软件异常而失败的时候,Flink 会停止数据流。系统重启操作同时将他们恢复到最近的快照点。输入流也会被设置到记录快照点那个时间点。系统可以确保任何一条新于快照点的记录都不会记录在...…

继续阅读