FriendFeed如何使用MySQL存储无Schema数据(三)

一致性和原子性

由于我们给数据库做过分片,并且实体的索引可以被存储在和它本身不同的多个分片之上,这样一致性就成了问题。要是进程在完成对所有索引表的写入前就崩溃了,那该如何是好?

构建一套事务协议就成了大多数FriendFeed工程师眼中的有力方案,但我们希望尽可能地保持系统结构简洁。于是,我们决定放宽约束,使得:

主entities表中存储的属性包是规范的。

索引反映的不一定是实际的实体值。

这样一来,要将一个实体写入数据库,我们要进行以下步骤:

1.用InnoDB的ACID属性来保证,将实体写入entities表。

2.把索引值写入所有分片上的索引表中。

当从索引表中读入数据时,我们很清楚索引值不一定是准确的(也就是说,假如第2步的写入操作没有完成,索引所反映的可能还是老的属性数据)。根据前面提到的约束,为保证不返回无效实体,虽然使用索引表来决定哪些实体该被读入,但是我们会重新把查询的过滤条件应用到实体上,而不是完全信赖索引的完整性:

1.基于查询从所有的索引表中读入entity_id。

2.由取得的实体ID值从entities表中取得实体值。

3.根据实际的属性值,(在Python中)过滤掉所有不符合查询条件的实体。

为保证索引不会永久丢失,以及不一致性问题最终得到解决,我在前面提到的“清扫程序”进程会持续不断的扫描实体表,写入丢失的索引,并清除旧索引和无效索引。清扫程序会优先清扫最近更新的实体,使得索引中的不一致性问题在实际情况下很快得到修复(在几秒之内)。

性能

我们在新系统中对主要的索引做了不少优化,其结果令我们颇为满意。下图显示的是过去一个月内FriendFeed页面浏览的等待时间(我们在几天前启动了新的后端系统,从图中的分水岭处很容易就能看出具体是哪天)。

目前,系统的页面等待时间超乎想象地稳定,甚至在每天正午的流量高峰期也不例外。下图显示的是过去24小时内FriendFeed的页面浏览等待时间。

大家可以对比一周前的数据。

到目前为止,系统的运转一切正常。自从新系统部署以后,我们已经对索引做过了好多次变更,并且开始对我们最大的MySQL表进行转换,来使用这套新的方案。这样一来,我们在今后就可以更加从容地更改这些表的结构了。

参考资料

1.FriendFeed:http://friendfeed.com

2.CouchDB:http://couchdb.apache.org

3.CouchDB性能初探:http://tinyurl.com/CouchDBPerf

4.Pickle:http://docs.python.org/library/pickle.html

留下回复