为过载做计划
到目前为止,我在实际工作中所碰到最常见的错误,基本上都是节点内存耗尽。而且通常都和过长的消息队列有关37。解决这类问题的方法有很多,不过只有在深入、全面的理解系统后,才能做出正确的选择。 基本上,我从事的所有项目都可以简化类比成一个非常大的浴室水槽。用户请求和数据从龙头流入。Erlang系统则是水槽和管道,可以把水流出的地方(数据库,外部API或者服务,等等)看作是下水道系统。
当Erlang节点由于队列溢出而死亡时,找到原因所在是至关重要的。是流入槽中的水太多了吗?是下水道堵塞了吗?还是把管道设计得太细了? 要找到膨大的队列并不是件难事,这个信息可以从crash dump中获得。知道队列膨大的原因则要困难得多。根据进程的角色,或者做些运行时的检查,就可以找出一些可能的原因:消息泛滥,进程阻塞无法快速处理消息,等等。
决定如何修复是最困难的部分。由于对水的滥用导致水槽积水时,我们可以换一个大一些的水槽(程序中崩溃的部分,处于边缘)。接着,发现水槽出水口太小,优化它。再接着发现管道太细,优化它。负载一直被推向系统的下游,直到下水道无法承受。此时,我们可能会试着增加水槽或者增加浴室来缓解这个全局性的输入问题38。
不过,事情总会发展到无法在浴室层面解决的地步。发出的日志过多,要保持一致性的数据库成为了瓶颈,或者只是因为公司没有解决问题所需的知识或者人力。
只有到了此时,我们才找到了系统的真正瓶颈所在,前面所做的优化虽然都不错(并且成本也不低),不过可能没啥用。 我们需要更聪明一些,退后一步,在上游解决问题。可以对进入系统的信息做些处理,让它更轻量一些(可以通过压缩,用更好的算法和数据表示,缓存等等)。 即使这样,还是会出现系统负载过大的情况,此时我们不得不在限制系统输入,丢弃输入,或者接受系统在崩溃前会持续降低服务质量之间做出艰难的选择。这些机制隶属于两种大的策略:反压(back-pressure)和减载(load-shedding)。