For the complete documentation index, see llms.txt. This page is also available as Markdown.

FlightAware 与 FreeBSD

作者:Sean Kelly

FlightAware 与 FreeBSD

它是最棒的——大多数情况下。

如果你还没听说过我们,FlightAware 是全球最大的航班追踪数据公司,为超过 10,000 家航空运营商和服务公司提供全球航班追踪解决方案。我们通过网站和移动应用向公众提供免费的航班追踪服务,但同时也面向全球航空运营商提供其他商业产品和服务。我们之所以能实现这一切,靠的是聚合来自自有地面站、覆盖 55 个以上国家的政府数据源,以及其他卫星数据提供商的数据。然后,我们接收所有这些数据,对其解读,把它们转换为对正在发生情况的更全局视图。可以想见,这需要相当数量的处理与存储能力。

在 FlightAware,我们的业务至今都建立在 FreeBSD 之上,我们是 FreeBSD 的坚定拥趸。我们有四名员工是 FreeBSD 项目的非活跃成员,包括我们的 CEO 和 CTO。直到最近,FreeBSD 几乎完全支撑着我们的网站和服务,仅有少数例外。但这种情况开始改变,我愿意解释一下原因。不过在此之前,先回顾我们与 FreeBSD 的渊源,以及我们如何使用它。

我们的 CEO 曾经运营 ftp2.freebsd.org,并向 Ports 树贡献过内容。他从 2.0-ALPHA 时代就开始使用 FreeBSD,至今仍持续在工作和个人生活中使用。我们的 CTO 资历更深,可以追溯到 386BSD 的起点,当时他维护补丁包,后来为 T1 驱动贡献了回环检测。再往前追溯,我们的一位开发者大量参与过 2BSD 和 4.1BSD 的开发,后来又协助了《FreeBSD Handbook》的编写。最后,我从 4.0 发布时开始使用 FreeBSD,贡献过若干补丁,以及软件看门狗子系统的最初实现。

除 FreeBSD 外,我们大量使用 Tcl 编程语言。这包括我们的网站,它用 Tcl 编写,由 Apache 的 mod_rivet 扩展提供服务。我们 Tcl 包的若干 Port 位于 Ports 树中,包括 speedtables、casstcl、yajl-tcl、tcllauncher、tclreadline 和 tclbsd。这些 Port 由 tcltk@ 和 Pietro Cerruti 维护。Pietro 虽不是 FlightAware 员工,但他慷慨地同步更新 Port 以配合我们的软件发布。我们之所以使用 FreeBSD,部分原因包括网络栈的稳定性与健壮性、社区的凝聚力、从内核到用户态对系统的整体把控,以及 ZFS。这一切结合在一起,为我们的服务器和服务创造了一个最优且可信赖的开源环境。

我们如何使用 FreeBSD

像许多其他公司一样,我们在 FreeBSD 上运行 Web 服务器、Varnish 服务器、邮件服务器和 DNS 服务器。虽然这些并不算特别不寻常,但我们在 FreeBSD 平台上做的其他一些事情可能更让读者感兴趣:包括定制的 FreeBSD 安装器、高事务量的 RDBMS、几种使用共享内存的不同内存数据库,当然,所有这些都利用了出色的 ZFS 文件系统。

安装:一台服务器诞生了

虽然 bsdinstall 相比 sysinstall 已是巨大进步,但我们仍需要一种自动化程度更高的方案。我们几乎所有服务器都从相同的基础安装开始,因此反复向 bsdinstall 录入相同信息毫无必要。取而代之,我们开发了一套集成 bsdinstall 与 bsdconfig 的脚本。这些脚本在 mfsBSD PXE 环境中运行,执行以下操作:

  • 销毁任何现有的硬件 RAID 配置与分区

  • 清除磁盘上任何已有的 ZFS 标签

  • 在所有内置磁盘上创建新的 zpool

  • 创建所有支持启动环境的相应 ZFS 文件系统

  • 拉取 FreeBSD 安装文件并解包

  • 创建一个用户

  • 配置所有网络参数

完成后,机器重启,我们会看到一个菜单,可从中选择要安装的组件。每个组件由 shell 脚本内的一个 shell 函数管理,便于管理与修改。需要新组件时,只需创建一个新函数。所有这些都是逐步演进而来的。理想情况下,我们希望有一种安装器能 PXE 启动,找到与 MAC 地址匹配的配置文件,并据此进行完全无人值守安装。如果这一能力内置在 FreeBSD 中就更好了——这样 FreeBSD 就会有一套标准化的零接触部署系统,每家用户都不必自行造轮子。

传统数据库:让它更快

虽然不如我们的定制安装器那般新颖,但 FreeBSD 在 FlightAware 的另一项用途是为 PostgreSQL 数据库集群提供动力。我们选择 FreeBSD,正是看中 ZFS 提供的数据完整性功能。另一项额外好处是 LZ4 压缩,其压缩比约为 2.15 倍,性能损耗微乎其微。集群本身是标准的 PostgreSQL 9.4 部署,使用原生流复制支持。我们使用 pgpool 来负载均衡只读事务,所有写事务则发往主节点。这让我们能将查询分散到更多服务器上,由于大多数事务都是只读的,从而提升了事务处理能力。2015 年 10 月,我们将 PostgreSQL 服务器从 24 块硬盘组成的 ZFS 镜像对替换为 4 块 NVMe SSD 组成的镜像对。在我们解决并最终禁用 TRIM 引发的问题之后,这是一次巨大的性能提升。当执行 zpool create 时,NVMe 子系统会超时,因为 ZFS 试图对整个池执行 TRIM,而 NVMe 等待 SSD 完成的耐心耗尽。在正常运行期间,对 Samsung SSD 执行 TRIM 也会引发问题,曾导致几次服务中断。PostgreSQL 清理 pg_xlog 文件时会因 TRIM 短暂阻塞 I/O。后来我们换成了 Intel SSD,看起来不存在此问题,但我们仍让 TRIM 保持禁用状态。为支持这一切的备份,我们用 Tcl 开发了一套自定义工具集,用于管理 ZFS 快照。该工具在数据库服务器上创建快照,发送到另一台主机暂存,再从那里分发到各个站点。这些工具还管理快照的保留策略,保留一定数量的按小时、按天、按周快照。

如你所见,共享内存对我们是一项重要技术。我们在内部工具以及 PostgreSQL 等第三方项目中都大量使用。我们发现,SHM 的整体性能在过去几年有所提升,希望这种情况能够持续。我们注意到 FreeBSD 9 时有显著改进,FreeBSD 10 时又有小幅提升。

内存数据库:让快的更快

除 PostgreSQL 数据库外,我们还有名为 Birdseye 的内部软件。Birdseye 是用 Tcl 和 C 编写的内存数据库,基于我们开源的 speedtables 项目。Birdseye 使用共享内存存储我们所知的全部飞机约 24 小时的位置数据。单台机器上的多个 Birdseye 进程共享同一共享内存段,从而能同时服务更多客户端。客户端(例如网站)可以连接到这些 Birdseye 服务器,发起各种查询,从而非常快速地判断一架飞机正在做什么、做过什么,甚至在任意地理矩形框内正在发生什么。除 Birdseye 外,我们还有另一种内存数据库 Superbird。Superbird 同样基于 speedtables 项目,用 Tcl 和一些生成的 C 编写。Superbird 会定期读入 PostgreSQL 表中已发生的任何变更,并将其保留在内存中的 speedtables 数据库里。网站利用这一点,通过访问本地内存缓存副本,对繁忙表进行更快的查询,而不必访问远端 PostgreSQL 服务器。换言之,Superbird 是利用更新轮询的数据库缓存层。

痛点:并非一切完美

现在已介绍完 FreeBSD 在我们这里如何工作,若不讨论痛点就有失偏颇。FlightAware 正在迅速成长,我们需要以越来越快的速度部署新系统和服务。此外,数据增长和不断攀升的摄取速率,正迫使我们重新评估如何存储、处理和分析数据集。这时,我们选择 FreeBSD 越来越难以继续维持和说服自己。

安装:一支服务器大军

FreeBSD 的一大优点是,安装到系统上之后基本可以放心不管。你显然会想安装更新等等,但系统总体上极为稳定,几乎不需要持续维护和照料。结果就是我们倾向于把 FreeBSD 系统视为独特的个体,而非完全可抛弃的资源大军。我们会增删软件,但很少需要重新干净安装。问题在于,这种方式扩展性不佳。结果,我们开始把操作系统安装视为一种可商品化的事物。如前所述,我们已经构建了自己的 FreeBSD 安装器,主要满足我们应对这种变化的需要。它还不够好,我们想加入更多功能,比如让它能通过 TFTP 获取配置文件以实现完全无人值守安装。也许可以有一个基础配置文件,再加上基于 MAC 地址或 SMBIOS 系统序列号的覆盖文件。但与其由我们为自己的工具实现这一点,不如让这些功能直接进入 FreeBSD 安装器更为理想。FreeBSD 安装器应通过文档化的脚本功能支持完全无人值守安装。Linux 世界早已凭借 Red Hat kickstart 系统或 Debian 预配置文件征服了这一领域。安装器可由 PXE 启动,再用 DHCP 选项指引它从何处下载配置数据。这将让一次性部署或重新部署数百台服务器变得轻而易举。

容器与隔离:当 Jail 不够用时

我已经分享了关于 FreeBSD 安装过程可以如何改进的设想,接下来解释一下我们想用那些无人值守安装的服务器大军做什么。我们正迅速告别在标准系统安装上部署服务的做法。像许多云规模公司一样,我们正在采用容器化方案,让我们能在一台硬件上轻松部署大量服务,而不会引入传统方式带来的软件和库依赖问题。我们希望每一次基础安装都完全相同,仅作为承载应用容器的平台。这简化了安装和管理。作为通往这一目标的一条潜在路径,我们评估了 Jail,发现它们大体可用。但我们没有朝这个方向推进,因为我们遇到了几个问题。一个突出问题是缺少 per-jail 共享内存。FreeBSD Jail 的共享内存彼此之间以及与基本系统之间并未隔离。这种缺乏隔离意味着 PostgreSQL 和 Birdseye 等服务无法在相邻 Jail 中安全部署,除非特别小心地避免冲突或被利用。例如,在相邻 Jail 中运行 PostgreSQL 是有风险的,因为 pgsql 用户具有相同的 UID,每个 Jail 因此都能访问另一个 Jail 的 IPC 资源。尽管如此,将 RCTL 加入 GENERIC 似乎已带来很多改进,但我们也希望在这方面有更多投入。VIMAGE 也应稳定并加入。此外,我们希望对共享内存等内容引入更多命名空间,以实现真正的隔离。这是 Linux 领先的领域之一,由此催生了 Docker 和 Kubernetes 等技术的繁荣。容器机制本身固然重要,如何管理它同样重要。目前,FreeBSD 有许多处于不同支持和开发状态的 Jail 管理方式:ezjail、iocage、CBSD、jail(8)/etc/jail.conf 以及许多自研工具和衍生品。虽然选择多通常是好事,但这种多样化的生态也使自动化和编排工具难以一致地支持 Jail。如果作为基本系统能将所有这些多样特性收归一处——一套 API 和命令一统天下——那将是再好不过。

64 位 Java:比之前好一倍

如前所述,我们的数据集在增长,摄取的数据量也在增长。这迫使我们转向 Cassandra、Kafka 和 Spark 等更新的技术,用于存储、移动和理解这一切。这些技术有一个共同点:它们都运行在 Java 运行时环境上。FreeBSD 上的 Java 选项不多。你可以构建并使用 OpenJDK 这一开源 Java 实现,它能编译为原生 64 位实现。也可以通过 FreeBSD Linux 模拟层使用 Oracle 的 32 位 Linux JRE。然而,直到最近才支持 64 位 Linux 系统调用,我们也尚未在 FreeBSD 上成功运行 64 位 Oracle JRE。最后,FreeBSD 基金会曾提供 Oracle JRE 的 FreeBSD 原生二进制,但已不再提供。你大概会纳闷我们的问题是什么,为什么不直接用 OpenJDK。答案是我们在它上面遇到过难以定位的问题。在 Kafka 上,我们使用 OpenJDK 时看到了周期性的消息损坏,而使用 Oracle JRE 时并未出现。更不用说 Oracle JRE 是 Java 的权威实现,这在生产环境中至关重要。之后,我们选择使用 Oracle JRE,至少等我们回头重新测试并协助修复 OpenJDK 时再考虑。我们要 64 位,选择了 Oracle 的 JRE 作为前进方向。这意味着我们不得不转向 Linux 来运行 Kafka、Cassandra 和 Spark 环境。随着我们持续构建这些服务,这会导致我们拥有越来越多的 Linux。

系统调优:我的服务器没那么弱

另一个值得更多关注的领域是 FreeBSD 安装的出厂调优。许多默认值似乎是为支持最低公分母而调优的,最好能实现一种在安装时可选的配置档机制。让我用具体例子说明。现在,我们编译 Tcl 解释器时会附加一个 CFLAGS 值 -DFD_SETSIZE=4096,以覆盖 <sys/select.h> 中默认的 1024。我们知道 select() 并不理想,也悬赏征求 Tcl 的 kqueue 支持,但 1024 对于现代网络应用来说似乎也太小了。希望这个值能调高,这样 FreeBSD 系统就能开箱即用,处理它原本可以毫不费力支持的所有并发连接,而无需用户进一步调整。默认的日志轮转大小也值得审视。根据全新 FreeBSD 安装上的 /etc/newsyslog.conf,大多数日志文件应在增长到 100KB 时轮转。我不知道你怎么想,但我的 /var/log/messages/var/log/auth.log 没多久就会达到这一阈值。在你能买到 10TB 硬盘和 1TB SSD 的时代,这显然是配置档系统的候选对象,提供嵌入式、服务器、桌面等不同选项,让目标轮转大小更合理。即使 100KB 对于基于 SD 卡的系统也嫌小。既然在抱怨日志,顺便也提几个建议。能否为 /etc/syslog.conf 提供一个 include 指令?newsyslog.conf 已经有了一个,syslog 看起来也该有。对 syslog.conf 中的 ! 程序说明符做一些改进也不错。眼下,为了把所有来自 postgres 的消息记录到 /var/log/postgres.log,我得这么写:

如果有一种程序说明符能匹配除其它已定义程序说明符之外的一切,那就太好了。这样你就可以有一条 catch-all 规则,而不必逐一列出排除项。这也能绕过我遇到的另一个问题:syslog.conf 的最大行长太短,无法一一列出。最后,另一个可改进的领域是整体网络栈调优。如果你在谷歌上搜索,会找到许多页面告诉你该设置哪些 sysctl 和可调参数来最大化网络栈和 10GigE 网卡吞吐。如果在安装时能有一档配置选择,把系统调优为适合 Varnish、nginx、Apache 等不同场景,以及 GigE、10GigE 或 40GigE 网络,那就太好了。眼下,要把它配置好并完全理解它,多少算是一门暗黑技艺。

调试:因为东西会坏

如果 zpool 命令崩溃了你怎么办?我会启动调试器,希望通过加载 core 文件看到它在哪里崩溃。可惜,安装 FreeBSD release 版本时基本系统不带调试符号。这是为什么?太大了吗,就像那些 100KB 的日志文件一样?如果基础二进制没有被 strip,或者至少有一个可选系统组件包含所有基础二进制和库的符号文件,那就太好了。同样,Ports 树默认构建软件时不带符号。多数 Port 有 DEBUG 选项,启用后软件会带符号构建,但通常会禁用所有编译器优化。这对生产环境并不理想。我希望看到一个通用选项,可以构建带优化同时也带调试符号的 Port,最好默认如此。我知道用 -O-O2 构建的程序调试会更难,但总比没有强。最后,所有东西都应默认带 DTRACE。DTRACE 是一款出色的工具,FlightAware 不止一次用它来理解性能问题或系统故障。PostgreSQL 和 Tcl 都方便地具备全面的 DTRACE 支持。然而,Ports 默认不启用它。在我们看来,每个 Port 都应默认带 DTRACE 构建。日常不一定用得上,但与调试符号一样,问题真正出现时它无价。

收尾

希望我已经讲清楚,FlightAware 确实偏爱并倡导 FreeBSD。话虽如此,总有改进空间,我在这里着手解释了 FreeBSD 哪些方面可以改进,以更好地服务我们以及想必其他用户。是时候开始研究如何更轻松地增加 FreeBSD 在野安装数量,并让它更容易被视为一种商品化平台了。


Sean Kelly 自 2012 年起加入 FlightAware 团队,担任 IT 运营总监。在该职位上,Sean 设计并监督支撑 FlightAware 的全球基础设施以及公司两处办公点所有 IT 系统的增长与维护。Sean 还是 FreeBSD 项目的前提交者,至少从 4.0 时代起就开始使用 FreeBSD。

最后更新于