⚠️ 此页面为自动翻译,翻译可能不完美。
blog-post

Manticore Buddy: challenges and solutions

嘿,大家好!🤗 我们希望你们已经查看了我们的 Buddy Intro 并且对它的运作方式有良好的理解。我们想分享开发它的旅程和经验,以及我们遇到的挑战。

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

  • 在不修改 C++ 的情况下,为 Manticore Search 扩展非性能关键功能。
  • 让增强功能和新功能实现的贡献更容易。

我们决心找到解决方案。所以,让我们深入探讨开发 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;它处理这些查询并发送给我们适当的响应,以便传递回原始客户端。

实现通信协议

在启动新项目时,保持灵活性并不过度规划非常重要。在我们的情况下,我们首先使用 PHP 中的 sockets 扩展实现了一个基本的通信。虽然它运行良好,但不具备可扩展性。我们的目标是将 Manticore SearchBuddy 连接起来,这种简单的实现使我们能够验证这个想法。

我们没有重新发明轮子,而是研究了使系统更具可扩展性和非阻塞性的选项。我们最初考虑了 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提供的通道进行通信,允许我们从主ReactPHP循环进程向另一个运行时中并行且分离的线程发送数据,而无需重新发明轮子。

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

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

虽然我们轻松解决了性能和并发问题,但我们面临了另一个挑战:部署。并非所有操作系统都支持最新的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构建,因为Dockerbuildx命令,允许我们使用QEMU在准备就绪的构建模式中,以相同的流程在同台机器上构建AMD和ARM架构!查看我们的构建流程 此处

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 工具。

为了使包可执行,我们决定使用带有我们的Manticore-Executor包的Bash和Shebang脚本。此脚本检查系统临时文件夹中修改的PHAR的日期,并在该位置提取PHAR数据,允许多次启动并保持在新版本安装时的性能和更新。有关我们如何实现这一点的更多信息,你可以参考我们在GitHub上的 phar_builder 项目。

学到的经验教训

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

最终成果

Buddy 的开发过程中,我们面临了许多挑战,但我们以兴奋的心情克服了它们。该工具完全用 PHP 编写,并作为操作系统包发布,使用户安装变得异常简单,也使我们维护和自动化构建变得容易,这得益于 GitHub Actions。虽然仍有改进空间以使工具更加简单,但我们的故事展示了如何利用 PHP 的力量构建一个易于维护和安装的工具。

我们希望您喜欢阅读我们关于 Manticore 6.0.0 发布前的旅程。请务必关注我们的下一篇文章,我们将探讨 Buddy 中新的可插拔设计及其易于贡献的生态系统,这将使整个社区受益。这确实是一个令人兴奋的时刻,我们迫不及待地想与您分享更多内容。

安装Manticore Search

安装Manticore Search