blog-post

Manticore Buddy: challenges and solutions

嘿!🤗 我们希望大家已经查看了我们的 Buddy 简介 ,并对它的工作原理有了良好的理解。我们想分享我们开发它的旅程和经历以及我们面临的挑战。

Manticore Software,我们遇到了两个主要挑战:

  • 扩展 Manticore Search,添加非性能关键特性,而不修改 C++ 代码。
  • 使为增强功能和新特性实现作出贡献变得更容易。

我们下定决心要找到解决方案。所以,让我们深入探讨我们开发 Buddy 的旅程以及我们面临的问题。准备好了吗?让我们开始吧!

旅程的开始

我们的旅程从仔细检查这个问题开始。尽管 Manticore Search 是一款出色的搜索数据库产品,但由于代码库是用 C++ 编写的,我们在按预期速度发布新特性时遇到了困难。编写 C++ 代码需要与数据结构、字节进行低级别的交互,对于机器如何工作的深入了解、编译、调试,以及寻找最佳的编写方法,这都需要大量的时间,但可以使程序执行更快。这大大延迟了开发过程。虽然 C++ 是性能的不错选择,但开发过程需要时间。我们希望快速推进,发布更多特性,并做到这一点持续稳定。

我们想到为我们的主要 searchd 进程创建一个辅助程序,它可以处理来自 Manticore Search 的失败查询并将结果返回给原始客户端。我们很快决定使用哪种语言,最终选定了 PHP,原因有几个:

  • 核心团队中的大多数人对它很熟悉,因此这将花费更少的时间来让它发挥作用。
  • PHP 非常快速,特别是在最新版本(8+)下,即使我们不要求 Buddy 执行性能表现。它比 PythonJavaScript 更快,因此非常适合我们的需求。
  • PHP 不仅快速,而且简单,减少了参与未来生态系统所需的专业知识水平。

这就是我们选择 PHP 并开始实现基础代码以了解我们后续所需内容的原因。

我们仍然使用 C++ 开发速度关键的功能。C++ 非常适合需要高速的任务。对于不需要太快速度的任务,Buddy 是最佳选择。

这就是 Buddy 的创建过程。为了在 Manticore Search 的 C++ 端实现这种可能性,我们实现了一个独立的循环,并通过 CURL 扩展在 searchd 守护进程与 Buddy PHP 进程之间进行通信。我们开发了内部协议,使用简单的 JSON,以将查询路由到 Buddy;它处理这些查询并向我们发送适当的响应以传回原始客户端。

实现通信协议

当开始一个新项目时,保持灵活性和避免过度规划是很重要的。在我们的案例中,我们首先使用 PHPsockets 扩展进行了基本的通信实现。虽然运行良好,但扩展性不足。我们的目标是将 Manticore SearchBuddy 连接起来,而这个简单的实现使我们能够验证这个想法。

我们没有 reinventing the wheel,而是研究了使系统更具可扩展性和非阻塞性的选项。我们最初考虑了 OpenSwoole,但由于许可问题,我们无法使用它。随后我们找到了 ReactPHP,它具有合适的许可。因此,我们决定选择 ReactPHP

ReactPHP 是一个纯 PHP 框架,可以让我们以异步模式运行 TCP 服务器。

这个选择对我们很有帮助,因为它允许我们同时处理多个请求并轻松扩展系统。

接下来,我们重写了我们简单的 Buddy MVP,并创建了一个核心结构,以便将来能够轻松添加新的 SQL 命令处理。过程如下:

  • Manticore Search 从用户那里接收 SQL 查询并尝试处理。
  • 如果 Manticore Search 能处理该查询,它会将响应返回给客户端,而不涉及 Buddy
  • 如果 Manticore Search 无法处理该查询,它会将包含所有输入查询信息和任何错误的特殊结构发送给 Buddy
  • Buddy 解析该结构并检查是否有处理程序。如果没有,它会返回 Manticore Search 将发送给客户端的相同错误。
  • 如果一切正常,我们有实现来处理查询,我们将过程分为两个步骤:准备请求所需的数据和使用处理逻辑处理它。请求是一个简单的结构,表示一个具有预定义变量和从输入 SQL 查询解析的输入参数的类。如果有任何问题,它可能会失败并返回错误给客户端。
  • 然后处理程序接收请求,做必要的工作,并将最终结果返回给 HTTP 请求。

这个系统易于维护,简单,并且可以轻松扩展新的功能。然而,这种方法存在一个问题。如果在 Buddy 中有重量级任务或需要等待的东西,它可能会减慢并发请求的速度。这是因为,尽管异步不是并行的,但 PHP 仍然是阻塞代码,且 ReactPHP 使用 fibers 模拟异步方式。我们将在下一节中对此问题进行更详细的讨论。

PHP 中的异步问题和并发扩展

为了处理重负载,Manticore Search 团队需要一种比 ReactPHP 能处理更多请求的解决方案。虽然 ReactPHP 适用于实现异步 HTTP 服务器和处理一些并发,但它的可扩展性不足。经过快速搜索,我们选择使用 parallel 扩展而不是 pthreads,因为它的可维护性和可靠性。

但什么是 parallel?这涉及并行化,创建独立的 Runtimes,表示并行运行的线程。这些线程可以通过 Parallel 提供的通道进行通信,允许我们将数据从主 ReactPHP 循环进程发送到另一个 Runtime 中的并行和分离的线程,而无需重新发明轮子。

这种方法是我们处理高并发水平并保持响应时间低的银弹。为了实现这一点,我们在 Handler 级别实现了一个 Task 组件,以在并行线程中运行任务。这样,主进程不会被阻塞,允许我们轻松处理许多并发请求。

最初,我们在每个请求上创建一个 Runtime,并在执行结束时销毁它。然而,这种方法在接收多个请求时导致了性能下降。为了处理更多请求,我们在首次启动时准备了 Runtimes,并以循环方式使用它们。这样,我们可以限制创建的最大线程数,而不超过核心总数,这也会影响性能。

虽然我们轻松解决了性能和并发问题,但面临另一个挑战:部署。并非所有操作系统都支持最新的 PHP 8 版本,某些不常见的扩展也未包含在默认安装中。但我们找到了解决方案,你也可以。

向 manticore-executor 打个招呼

我们进行了研究,以寻找一种便捷的方式将新工具交付给客户,并发现了一种绝佳的方法—— 将 <strong>PHP</strong> 和 Buddy 编译成一个单一的静态二进制文件 。这涉及到将 PHP 注入到其源代码中,并创建一个可以运行的二进制文件。然而,我们遇到了一个障碍,因为我们想混合不同的许可证——PHP 3.01GPL 2.0——这是不可行的。因此,我们选择了预构建 PHP,静态链接,并将其命名为 manticore-executor。

不幸的是,这个过程并不简单。我们尝试在 Ubuntu 上构建,但遇到了一个问题——我们需要 OpenSSL 来建立与外部域的安全连接。然而,在使用动态 GCC 时,我们无法静态链接 OpenSSL。

我们为什么使用 GCC?这是编译 PHP 及其扩展所必需的。问题是,我们需要一个静态构建的 GCC 进行静态链接,这并不简单,并且需要大量工作。因此,我们寻找替代方案。

幸运的是,我们发现了 MUSLAlpine,这使我们能够轻松构建一个完全静态版本的 PHP,以及所有必需的扩展和库!此外,它适用于任何 Linux 发行版。

Alpine Linux 是编译 C 程序的绝佳选择,因为它体积小、轻量,适合资源有限的系统,如嵌入式设备或容器。此外,Alpine Linux 是安全的。它采用加固内核和少量软件包,限制攻击面,使其更不容易受到安全威胁。这对 C 程序尤其重要,因为它们可能容易受到安全漏洞的影响。

此外,Alpine Linux 采用 MUSL libc 作为其标准 C 库,这是一种轻量高效的 C 库,生成的代码比其他 C 库更快,更高效。

因此,我们利用它并设置了操作,以利用 Alpine 镜像并在 Docker 中构建。这个方法的美妙之处在于,它还使我们更容易为 ARM 构建,因为 Docker 拥有 buildx 命令,允许我们在准备好的构建架构中利用 QEMU,并在同一台机器上完成 AMD 和 ARM 架构的构建流程!查看我们的构建流程 here

Github Actions 自动化所有支持操作系统的包构建和部署。对于用户来说,安装很简单:只需运行 apt-get install manticore-executoryum install manticore-executor,你就会有一个准备好使用的 PHP 版本,并预装了运行任何 Manticore 提供的 PHP 项目所需的所有软件包。简单!

我们如何交付源代码

在 Manticore Search,我们面临提供由多个源代码文件组成的 PHP 应用程序给用户的挑战。我们有许多文件分散在不同的文件夹中,还有必须通过 Composer 安装的依赖项,使得安装过程复杂。

正如你所记得的,我们开发了一个自定义 PHP 版本,称为 manticore-executor ,可以轻松从库中安装。然而,这仍未解决将整个 PHP 应用程序提供给用户的问题。

我们在 PHAR 中找到了一个解决方案,这使我们能够构建一个单独的文件,可以作为包添加到库中。这简化了安装过程。然而,确保最终的 PHAR 存档中正确包含所有依赖项是棘手的。为了解决这个问题,我们创建并分离了一个外部构建系统,我们还用于我们的 manticore-backup 工具。

为了使包可执行,我们决定使用 Bash 和 Shebang 脚本与我们的 Manticore-Executor 包。这段脚本检查系统临时文件夹中 PHAR 的修改日期,并将 PHAR 数据提取到那里,从而允许多次启动,保持性能并保持对新版本的最新状态。有关我们是如何实现这一点的更多信息,请参考我们的 phar_builder 项目在 GitHub 上。

经验教训

  1. 在对项目的未来成功不确定时,从简单的基本解决方案开始,不要使用软件设计模式。优先验证,然后重构并迭代更新。
  2. PHP中并发处理可能是一个挑战,但使用线程和异步框架可以帮助实现高吞吐量。为了获得最佳性能,建议同时使用两者。为线程预分配运行时可以帮助达到预期性能。
  3. 简化用户的运输过程。减少所需的指令数量。在我们的案例中,一个PHAR归档和一个包含所有扩展的二进制文件解决了这个问题。
  4. 使用最新版本的PHP或其他工具,以保持对最新发展的掌握,并确保您的数据安全。过时的软件可能存在被攻击和安全漏洞的风险。升级可提供更好的性能、最新的功能和高效的编码过程。
  5. 寻找可以解决您问题的包,并检查它们的依赖关系。选择依赖关系最少的包,以避免依赖地狱。使用像构建块一样的小包,而不是创建自定义解决方案。

最终结果

Buddy的开发过程中,我们面临了许多挑战,但我们以兴奋的心情克服了这些挑战。该工具完全用PHP编写,并作为操作系统包发布,使用户安装极其简单,也便于我们维护和自动化构建,这得益于GitHub Actions。尽管仍有改进的空间,使工具变得更简单,但我们的故事展示了如何构建一个易于维护和易于安装的工具,完全依靠PHP的强大功能。

我们希望您喜欢阅读我们关于发布 Manticore 6.0.0 之旅的文章。请继续关注我们的下一篇文章,在文章中我们将探讨Buddy的新插件设计及其易于贡献的生态系统,这对整个社区都能带来益处。现在真是一个令人兴奋的时刻,我们迫不及待想与您分享更多内容。

安装Manticore Search

安装Manticore Search