突然,仅靠垃圾收集系统是不够的

这是我一年前必须调试的有关神秘服务器故障的简短故事(日期为2018年12月5日,大约每篇)。服务器工作了一段时间后,然后开始崩溃。此后,尽管文件系统报告在约20 GB的磁盘上仅占用了几GB的字节,但是尝试运行服务器上几乎所有程序的尝试均失败,并显示错误“设备上没有空间”。

原来,问题是由日志系统引起的。这是一个Ruby应用程序,它接收日志文件,将数据发送到远程服务器并删除旧文件。错误是未明确关闭打开的日志文件。相反,该应用程序允许Ruby的自动垃圾收集器清理File对象。问题在于File对象不会占用大量内存,因此从理论上讲,日志记录系统可以在需要进行垃圾收集之前将数百万条日志保持打开状态。

* Nix文件系统将文件名和文件中的数据分开。磁盘上的数据可以有多个指向它们的文件名(即硬链接),并且仅在删除最后一个链接时才删除数据。打开的文件描述符被视为链接,因此,如果在读取程序时删除文件,文件名将从目录中消失,但是文件数据将保持活动状态,直到程序关闭它为止。这就是记录器发生的事情。 du(“磁盘使用情况”)命令使用目录列表搜索文件,因此对于数千个仍在打开的日志文件,它看不到千兆字节的文件数据。仅在运行lsof(“列出打开的文件”)之后才发现这些文件。

当然,在其他类似情况下也会发生类似错误。几个月前,我不得不遇到一个Java应用程序,几天后由于网络连接泄漏而崩溃了。

我曾经用C编写大多数代码,然后用C ++编写。在那些日子里,我认为手动资源管理就足够了。那有多复杂?每个malloc()需要free()函数,每个open()需要close()。只是。除了不是所有程序都简单以外,随着时间的推移,手动资源管理已成为束缚。然后有一天,我发现了链接计数和垃圾收集。我以为它解决了我所有的问题,并且完全不再关心资源管理。同样,对于普通程序,这是正常的,但并非所有程序都简单。

您不能指望垃圾回收,因为它只能解决内存管理问题,而复杂的程序所需要处理的不仅仅是内存。有一个流行的模因可以解决这一问题,因为内存是资源问题的95%。您甚至可以说所有资源都是问题的0%,直到用尽其中之一。然后,此资源将成为您100%的问题。

但是这种想法仍然将资源视为特例。一个更深层次的问题是,随着程序变得越来越复杂,一切都趋于成为一种资源。例如,使用日历程序。复杂的日历程序允许多个用户管理多个共享日历,并具有可以在多个日历之间共享的事件。数据的任何部分最终都会影响程序的几个部分,并且应该是相关且正确的。因此,对于所有动态数据,您需要一个所有者,而不仅仅是内存管理。随着新功能的添加,程序的越来越多的部分将需要更新。如果您理智,则一次只能更新该程序一个部分的数据,因此,更新数据的权利和责任本身就是一种有限的资源。使用不可变结构对变异数据进行建模不会导致这些问题的消失,而只会将它们转化为另一个范式。

计划所有权和资源寿命是复杂软件设计的必然部分。如果使用一些常见的模式,这会更容易。模式之一是可互换资源。一个示例是不可变字符串“ foo”,在语义上与任何其他不可变“ foo”相同。这种类型的资源不需要预定的寿命或拥有。实际上,为了使系统尽可能简单,最好不要具有预定的生存期或所有权(hi Rust,大约每人)。另一种模式是不可互换的资源,但具有确定的寿命。这包括网络连接以及更抽象的概念,例如控制部分数据的权利。最合理的事情是在编码时明确确保这些东西的寿命。

请注意,自动垃圾回收对于实现第一种模式确实非常有用,但对于第二种模式则不是,而手动资源管理技术(例如RAII)对于实现第二种模式非常有用,但是对于第一种模式来说却很糟糕。这两种方法在复杂的程序中成为互补。

Source: https://habr.com/ru/post/undefined/


All Articles