非死book如何使用Haskell处理垃圾邮件
非死book使用一个名为Sigma的系统打击垃圾邮件、恶意软件及其它恶意行为。该系统的任务是主动发现这些行为,并自动删除检测到的不良内容,避免它们在用户的动态消息中显示。最近,他们完成了为期两年的Sigma重新设计工作,用 Haskell 取代了仅在非死book内部使用的 FXL语言 。现在,基于Haskell的Sigma系统已经应用于生产环境,每秒为100多万请求提供服务。对于像Sigma这样的大型生产系统而言,Haskell不是一个常用的选项。Simon Marlow是一名非死book软件工程师,同时也是Haskell社区的领军人物。近日,他 撰文 解释了他们做出这种选择的原因,并分享了经验。
Sigma是一个规则引擎,就是说它运行一组规则,非死book称之为策略。Sigma会用那些规则评估非死book上的每次交互,以便识别和阻止恶意交互,防止它们影响非死book用户。策略是持续部署的,任何时候,代码库中的源代码都是Sigma中运行的代码。这样,它们可以对新出现的恶意行为作出快速响应。同时,这也要求他们用于编写策略的语言是安全的。
起初,他们使用自己设计的FXL语言编写策略,但随着扩展性需求和复杂性的增加,FXL不再是理想的选择。它缺少一些抽象机制,比如用户定义类型和模块,而且它的实现是以一个解释器为基础,速度不够快。因此,他们希望选用一种现有的语言。下面是他们重点考虑的几个因素:
- 纯函数式强类型语言 :确保策略不会在无意间相互交互,可以单独测试,并且不会导致Sigma系统崩溃;强类型有助于在策略部署到生产环境之前减少许多Bug;
- 自动批处理和并发获取数据 :策略通常需要从非死book的其它系统获取数据,为了提高效率,他们希望系统默认使用并发,即隐式并发;
- 在几分钟内将修改后的代码推送到生产环境 ,即他们可以快速部署或更新策略;
- 性能 :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 活动视频。