在Linux上将正在运行的应用程序中的共享对象热更新到新版本



我们有一台服务器,它必须能够在没有停机的情况下进行更新。

我们通过使应用程序仅是一个瘦加载层来实现这一点,并且所有逻辑都在由应用程序dlopen的共享对象中。让我们将这个库称为libmyservice.so.1.23

当请求传入时,服务器会创建一个线程,并从lib中调用适当的API来为其提供服务

当服务器需要热更新时,它会下载一组新的库,并使用dlopen加载它们——让我们将新库称为libmyservice.so.1.24

在更新期间,存在一个中间时段,其中已经运行的请求仍然由旧库提供服务,而新请求由新库提供服务。当旧请求完成时,旧库将被卸载,所有请求都将使用新库。因此,在更新过程中没有停机时间。

该库被编译为尽可能独立。它依赖于boost、openssl和许多其他我们无法控制的C++。所有这些依赖项都随库一起提供,rpath用于从与lib相同的目录中加载它们。

在实践中,我们遇到了两个问题:

  • 符号冲突:当加载新库时,动态链接器会重用旧库中的符号,这可能会导致奇怪的错误。我们发现可以通过将RTLD_DEEPBIND传递到dlopen来解决这个问题。但我们也有做类似事情的dlmomon。当目标是完全隔离新旧库集时,应该使用这两者中的哪一个?

  • 静态初始化和清理:当使用RTLD_DEEPBIND加载库时,我们遇到了一个错误,即使用dlopen库中的std::cerr等全局对象会导致崩溃。我们仍然不完全理解为什么会发生这种情况。我们相信,当使用RTLD_DEEPBIND时加载一组新的libstdc++符号时,不会发生静态初始化。对我来说,这似乎是动态链接器中的一个错误。静态初始化和清理应该如何在共享对象中工作?

如何在不发生任何冲突的情况下同时加载两个共享库及其所有依赖项?这可能吗?即没有符号冲突,在静态初始化中没有冲突。

该软件已经在Windows上正常工作了,因为DLL似乎不像共享对象那样相互冲突。

编辑:

虽然我喜欢多个流程的想法,但架构已经决定了,如果没有很多批准(我不太可能得到),我就无法更改它。

为了使事情变得复杂(我在最初的问题中不希望这样),实际的体系结构是这样的:服务器应用程序->主库->热更新核心库。";主库";是在热插拔核心库之上作为代理的产品。客户获得此库并将其集成到他们的产品中。在更新过程中,客户的产品下载更新包并调用主库中的函数来触发热插拔。主库不处理线程,但它是实现为线程安全的。

因此,多个流程并不是真正的选择。

我不打算回答这个问题,但这个建议太长了,无法放在评论中。。。TL;DR:小心XY问题:)

我不知道是否可以进行动态库重新加载,但正如您所注意到的,即使可以使其工作,这也是一个麻烦的来源。因此,我会考虑一种不同的、更习惯的方法。

你可以创建一个";超级服务器";可执行文件,其任务仅是侦听传入请求并将其移交给实际服务器。

当一个新的连接进入时,超级服务器fork进入一个新进程(它继承了连接的文件描述符),而exec是可执行的请求处理程序。在最简单的情况下,你甚至不需要写这个;xinetd正是这样做的。Systemd也可以这样做,使用套接字激活。

如果每个请求有一个进程的开销太大,您还可以对超级服务器进行编码,将连接的文件描述符移交给现有的长期运行的服务器进程。然后,实际的服务器可以随心所欲地处理请求,例如使用线程或基于select()的reactor。超级服务器可以通过向其发送SIGTERM信号来关闭服务器;应该对服务器进行编码,通过完成所有运行中的请求然后关闭来处理此问题。

最后,如果你真的无法忍受任何停机时间,那么如果你的服务器只在一台机器上运行,而机器出现故障(或者需要重新启动以进行内核更新),这些技巧都不会拯救你。您需要多个服务器形式的冗余。在这一点上,您还不如通过逐个拆除这些服务器来升级它们,而实际上不需要这些热交换。

最新更新