深入理解Facebook Messages’ 应用服务器

来源:互联网 发布:linux mint vs deepin 编辑:程序博客网 时间:2024/05/16 14:02

转自:http://www.hellophp.cn/archives/493

在众多Facebook 产品中,消息系统无疑是技术挑战最大的一个。因此我们需要建立一个专有的应用服务器来管理这些基础设施。

我们最近讨论了消息系统的后端组件,以及我们如何扩展我们的服务以处理来自Email ,SMS, Facebook Chat ,收件箱的海量消息,本文我们就分享一下应用服务器的设计细节。

应用服务业务逻辑层

应用服务器整合了很多Facebook服务,封装了访问这些服务的复杂性。 他为客户端,封装了一个简单的接口来处理标准的消息操作,包括创建,读取,删除,更新消息和收件箱。

下面介绍一个每个操作的流程。

当创建一个新的消息或者回复一个消息的时候,应用服务器将消息投递到接收方(用户),如果接收方是个email地址,应用服务器还会去HayStack检查一下,是否有附件,然后创建一个html格式,遵从RFC2822标准的消息。

当消息(邮件)通过外部的邮件地址发送到Facebook内部用户系统的时候,服务器会将消息投递到用户的收件箱,根据一些预设的条件,对消息进行必要的处理,决定消息投递到哪个目录,那个话题里面。

当读取消息的时候,服务器会获取多项关于用户Mailbox的统计数据,例如容量,消息数量,话题数量,回复数量; 用户的活跃联系人数量。 服务器还会获取收件箱里面目录的统计信息和属性信息。在读取话题列表的时候,是通过一系列的搜索条件(目录,属性,作者,关键词等),话题列表里面包括话题的基本属性和话题下面的消息。

当删除消息的时候,服务器将消息和话题标识成删除状态,然后一个离线的作业会执行具体的删除消息操作。

当更新消息和话题的时候,服务器变更消息和话题的属性,比如【已读/未读】 ,归档状态,Tag 等等。更新操作还负责处理针对话题的订阅,退订操作。

管理话题组

Facebook 通过聊天室的模型管理群组话题消息。 用户可以加入(订阅),或者离开(退订)某一话题。在回复某个话题,当收件人不是某个用户,而是一个Email地址的时候,应用服务器会创建一个回复处理器,就像聊天室ID一样,当收件人通过回复了这个话题,消息会通过回复处理器,直接投递到那个话题。

为了优化读性能,简化迁移和备份过程,话题消息存储通过一个非规范化的模式来存储,因此每个用户,都有他自己的一份话题元数据,和消息的拷贝。服务器广播订阅和退订事件,在所有的接收者中,同步话题的元数据数据,从而可以用去中心化得方式,来处理订阅和回复等请求。当处理老的系统时候,比如用户的老的收件箱,或者通过Email地址来订阅,应用服务器会做些特别的适配工作,以保证业务流畅。

缓存用户的元数据

当用户访问收件箱的时候,应用服务器加载用户常见的元数据,(我们叫做活动元数据),然后缓存在LRU里面,后续请求可以尽量少查询HBASE,从而提升效率。

我们尽量减少对HBASE          的查询,是因为HBASE不支持join. 为了服务一个读请求,服务器可能要查找多个索引,在不同的     HBASE查询中抓取元数据和消息内容。Hbase针对写,而不是读做的优化,幸运的是,用户行为通常具有良好的时间和空间局部性,因此通过Cache可以解决性能问题。

我们也做了很多努力,通过减少用户内存痕迹,采用更细粒度的架构来提高Cache效率。我们可以缓存5%-10%的用户,命中率在95%左右。对于超热点数据,比如主页的未读消息数,我们将其放到全局的Memcache层。当新的消息到达时,应用服务器负责将缓存中的数据做过期处理。

同步

HBase对于交易的隔离提供了有限的支持。因此对于用户的多个并发更新,为了解决更新冲突,Facebook采用应用服务器来解决这个问题。每个用户,都会被分配到特定的应用服务器,因此针对这个用户的更新操作,都可以在这个应用服务器内,通过代码做到完全的同步隔离。

存储模式

MTA代理,将附件和大的消息到达应用服务器以前,将他们提提取出来,存储在Haystack中。当然元数据,包括搜索索引数据和小消息体,存储在HBASE中,由应用服务器来维护。每个用户的邮箱同其他用户的邮箱,都是相互独立的;用户数据在Hbase中,是不会共享的。每个用户的数据,都会存储在HBase中的一行中,Hbase行的数据结构如下:

元数据实体和索引

元数据实体,包含着收件箱对象的属性,比如文件夹,话题,消息等等。 每个实体存储在他自己的HBASE列中,不像传统的RDBMS,HBASE没有原生的索引支持。我们在应用层面,维护着二级索引。每个单独的列都已key/value对的形式存储。

例如,当我们查询“在其他目录的第二页加载未读话题”,应用服务器首先查询元数据索引,获取符合查询条件的话题,然后抓取特定话题的元数据实体,通过他们的属性来构造响应对象。

就像我们前面提到的,缓存和有效的预加载减少了Hbase的查询次数,提升了系统效率。

动作日志

任何对于用户收件箱的操作(例如创建,删除,标记话题为已读等) 都会按照时间寻出立刻追加到一个列族,这个动作被动作日志。小的消息体也会存储在动作日志里面。

我们通过重放动作日志,可以重建或者恢复用户当前的收件箱的状态。我们用最后动作日志的ID作为元数据实体和索引的版本。当加载一个用户的收件箱的时候,应用服务器比较元数据版本和最后动作日志ID,如果元数据的版本号落在了后面,那么更新收件箱的内容。

在应用端存储动作日志带来了巨大的灵活性:

a)         我们可以通过重放动作日志,产生新的元数据实体和索引,重建的过程既可以通过离线的MapRedue 任务,也可以通过应用服务器在线完成,从而保证我们可以无缝的切换到新的存储架构。

b)         我们可以进行大批量的Hbase异步写入以节省网络带宽和Hbase压缩成本。

c)         他是一个跟其他组件进行持久层数据交换的标准协议。例如,我们可以通过将动作日志写入到Scribe日志上,实现在应用级别进行备份。迁移管道将用户老的收件箱转换成动作日志,然后通过离线的MapReduce生成元数据和索引。

搜索索引

为了支持全文检索,我们维护了一个关键词的倒排索引列表。当新的消息到达时,我们用Apache Lucene 来解析和转换成包含关键词,消息ID,位置的有序列表,然后将列表增量的添加到Hbase的列族上面。每个关键词,有他自己的列。所有的消息,包括聊天的历史,email,sms,都会被实时的索引。

通过Dark Launch进行测试

应用服务器是我们从概念开始设计起来的全新软件,在我们将其用于5亿用户之前,我们需要监控他的性能,可靠性,扩展性。 我们最初开发了一个压力测试机器人,用来发起测试请求,但是我们发现测试结果,会受一些因素的影响,比如消息大小,不同请求类型的分布,不同用户活动程度的分布等待。

为了模拟生产环境下的负载情况,我们做了个Dark launch,通过这个系统我们镜像了来自聊天室和显存的收件箱的流量,将10%的用户流量导入了一个测试的集群. Dark  launches 帮助我们发现了很多性能问题和性能瓶颈。 我们同时也用他来做性能改善的评价标准。随着时间的推移,我们会向所有用户推出我们新的消息系统。