Facebook 使用一个名为 Sigma 的系统打击垃圾邮件、恶意软件及其它恶意行为。该系统的任务是主动发现这些行为,并自动删除检测到的不良内容,避免它们在用户的动态消息中显示。最近,他们完成了为期两年的 Sigma 重新设计工作,用 Haskell 取代了仅在 Facebook 内部使用的 FXL 语言。现在,基于 Haskell 的 Sigma 系统已经应用于生产环境,每秒为 100 多万请求提供服务。对于像 Sigma 这样的大型生产系统而言,Haskell 不是一个常用的选项。Simon Marlow 是一名 Facebook 软件工程师,同时也是 Haskell 社区的领军人物。近日,他撰文解释了他们做出这种选择的原因,并分享了经验。
Sigma 是一个规则引擎,就是说它运行一组规则,Facebook 称之为策略。Sigma 会用那些规则评估 Facebook 上的每次交互,以便识别和阻止恶意交互,防止它们影响 Facebook 用户。策略是持续部署的,任何时候,代码库中的源代码都是 Sigma 中运行的代码。这样,它们可以对新出现的恶意行为作出快速响应。同时,这也要求他们用于编写策略的语言是安全的。
起初,他们使用自己设计的 FXL 语言编写策略,但随着扩展性需求和复杂性的增加,FXL 不再是理想的选择。它缺少一些抽象机制,比如用户定义类型和模块,而且它的实现是以一个解释器为基础,速度不够快。因此,他们希望选用一种现有的语言。下面是他们重点考虑的几个因素:
- 纯函数式强类型语言:确保策略不会在无意间相互交互,可以单独测试,并且不会导致 Sigma 系统崩溃;强类型有助于在策略部署到生产环境之前减少许多 Bug;
- 自动批处理和并发获取数据:策略通常需要从 Facebook 的其它系统获取数据,为了提高效率,他们希望系统默认使用并发,即隐式并发;
- 在几分钟内将修改后的代码推送到生产环境,即他们可以快速部署或更新策略;
- 性能:FXL 性能较差,他们需要使用 C++ 开发一些对性能要求较高的功能,增加了变更发布时间;
- 支持交互式开发:开发人员可以交互地试验和测试策略代码并立即看到结果。
Haskell 非常适合:它是强类型的纯函数式语言,有成熟的优化编译器和交互式环境(GHCi),并且有他们需要的抽象机制。此外,它还有丰富的库和活跃的开发者社区。因此,上述列表中还有两项特性有待处理:
- 自动批处理和并发:在 Haskell 中,现有所有的并发抽象都是显式的,就是说需要用户自己指出什么应该并发。但他们希望有一个编程模型,使系统在可以并发的时候自动并发。为此,他们开发了 Haxl 框架。它可以使多个数据获取操作自动批处理和并发执行。感兴趣的读者可以阅读他们先前发表的博文、论文以及查看其 GitHub 页面。此外,他们还在 GHC 中实现了 Applicative do-notation 。
- 已编译代码的热切换:他们希望任何提交到策略库的新代码尽快在每台机器的 Sigma fleet 上运行。他们希望动态地在运行中的 Sigma 进程中更新已编译的规则。修改正在运行的程序代码非常困难,但他们的情况相对简单:向 Sigma 发起的请求很短暂,所以他们不需要将一个正在运行的请求切换到新代码,而是等待现有的请求结束,然后用新代码为新请求提供服务。目前,他们使用 GHC 内置的运行时链接器加载和卸载代码,而卸载旧版本代码还要用到垃圾收集器。
Haskell 位于 Sigma 中两个 C++ 层之间。上面一层是 C++ thrift 服务器。其实,这里也可以用 Haskell 作为 thrift 服务器,但 C++ thrift 服务器更成熟,性能更好,功能更丰富,并且可以与下面的 Haskell 层无缝集成,因为他们可以从 C++ 调用 Haskell。最底层是与外围服务交互的 C++ 客户端代码。他们使用 Haskell 的外部函数接口(FFI)将 C++ 客户端封装成一个 Haxl 数据源,那样,他们就可以从 Haskell 使用它。
他们对 Sigma 所服务的 25 种常见类型的请求进行了测试。结果表明,对于特定的请求,Haskell 的性能是 FXL 的 3 倍。Haskell 的吞吐量整体上比 FXL 高 20% 到 30%。为了,他们做了大量艰苦的工作,优化 Haskell 代码,确定和解决性能瓶颈,其中包括修改 GHC 的堆管理方式、修复 Haskell JSON 解析框架 aeson 的一个性能缺陷等。
此外,Simon 还介绍了其它几项重要的工作,包括:
- 资源限制:为了确保单个请求不因占用过多资源而导致其它请求的响应速度降低,他们在 GHC 中实现了资源分配限制,设定一个线程在被动终止之前可以获得的内存上限。
- 交互式开发:为了实现交互式开发,他们需要将 GHCi 环境与他们的整个技术栈集成,包括从命令行向其它后台服务发送请求。为此,他们必须让构建系统将代码的所有 C++ 依赖都链接到一个 GHCi 可以加载的共享目录。此外,他们还定制了 GHCi 前端,实现了部分自有命令,简化了工作流程。
- 打包和构建系统:他们发现,托管在 Hackage 上的 Haskell 开源包变化非常快,而且并不是所有包组合都可以很好地工作。因此,使用这些包意味着更多繁琐的工作。于是,他们切换到了 Stackage 。Stackage 提供了已知可用的包的版本组合。
要了解更多信息,可以查看 Protect the Graph 页面,或者观看他们最近的反垃圾邮件@Scale 活动视频。
感谢郭蕾对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。
评论