今天看了“Database Sharding at Netlog, with MySQL and PHP”一文,和去年我们讨论扩展的思路很类似(不过这种分布式扩展,计算,存储的思路都很类似),但是这片文章的作者是在日益爆炸式增长的用户数据下实践的分享,因此这里将文中的一些思想记录下来分享一下。
Netlog拥有4000万活跃用户,每个月有超过5000万的独立用户访问网站,每个月有5亿多的PV。数据量应该算是比较大的。作者是Jurriaan Persyn,他从一个开发者角度而非DBA或者SA角度来谈Netlog是如何通过数据切分来提高网站性能,横向扩展数据层的。原文在:http://www.jurriaanpersyn.com/archives/2009/02/12/database-sharding-at-netlog-with-mysql-and-php/
首先,还是先谈到关于数据库在数据日益庞大的情况下一个演变过程。
第一阶段:读写同在一台数据库服务器
第二阶段:读写分离(可以解决读写比例均衡或者读居多的情况,但是带入了数据复制同步的问题)
第三阶段:部分数据独立部署结合读写分离。(部分数据根据其业务独立性情况,可以将所有的数据独立存储到数据库服务器,分担数据读写压力,前提是要求数据具有较高的业务独立性)
第四阶段:数据分拆结合读写分离(三阶段的增强)
第五阶段:问题出现,分拆也无法解决数据爆炸性增长,同时读写处于同等比例。
解决问题两种方式:DB Scale up ,DB Scale out。前者投入以及后期扩展有限,因此需要进行数据切分。
上图就是将photo的数据切分到了10台数据库服务器上。
切分数据的两个关键点:
1. 如何根据存储的数据内容判断数据的存储归属,也就是什么是内容的分区主键。
2. 采用什么算法可以根据不同的主键将内容存储到不同的分区中。
分区主键的选择还是要根据自身的业务场景来决定,Netblog选择的是用户ID。
采用什么方式将分区主键映射到对应的分区可以通过以下四种方式:
1. 根据数据表来切分。(前提就是数据独立性较强,和前面提到的三阶段类似)
2. 基于内容区间范围的分区。(就好比前1000个用户的信息存储在A服务器,1000-2000存储在B服务器)
3. 采用Hash算法结合虚拟节点的方式。(这类在memcached等等分布式场景中最常见,其实也是一个难点),缺点就是在于动态增加存储节点会导致数据部分或者全部失效。
4. 目录式的分区。最简单也是最直接的方式,key和分区的对应关系被保存,通过查找目录可以得到分区信息。适合扩展,就是增加查询损耗。
如何将数据分布的尽量均匀,如何平衡各个服务器之间的负载,如何在新增存储机器和删除存储机器的时候不影响原有数据,同时能够将数据均摊,都是算法的关键。在分布式系统中DHT(Distribute Hash Table)被很多人研究,并且有很多的论文是关于它的。
数据的横向切分给应用带来的问题:
1. 跨区的数据查询变得很困难。(对于复杂的关联性数据查询无法在一个请求中完成)
2. 数据一致性和引用完整性较难保证。(多物理存储的情况下很难保证兼顾效率、可用性、一致性)
3. 数据分区之间的负载均衡问题。(数据本身的不均衡性,访问和读写的不均衡性都会给数据分区的负载均衡带来困难)
4. 网络配置的复杂性。(需要保证服务器之间的大数据量频繁的交互和同步)
5. 数据备份策略将会变得十分复杂。
解决这些问题当前已经有的一些开源项目:
1. MySql Cluster,解决读写分离问题已经十分成熟。
2. MySql Partitioning,可以将一个大表拆分为很多小表,提高访问速度,但是限制与这些小表必须在同一台服务器上。
3. HSCALE和Spock Proxy都是建立与MySql Proxy基础上的开源项目,MySql Proxy采用LUA脚本来进行数据分区。
4. HiveDB是MySql分区框架的java实现。
5. 另外还有HyperTable,HBase,BigTable等等。
Netblog几个需求:
1. 需要灵活的可扩展性。对于存储增加减少需要能够动态的及时实施,因为数据量增长很快,如果策略会导致数据失效或者部署需要重新启动,则就不能满足需求。
2. 不想引入全新的数据层和与原有系统不匹配的抽象层,因为并不是所有数据都需要切分,仅仅在需要的情况下通过API的方式来透明切分数据。
3. 分区的主键需要可配置。
4. 需要封装API,对开发人员透明数据切分的工作。
Netblog Sharding的实现
上图就是Netblog的Sharding的结构图,主要分成了三部分:Shard,Sharddb,Sharddbhost。Shard就是一个表,里面存放了部分用户数据。Sharddb是一个表的组合就像一个虚拟的DB。Sharddbhost是具体的存储分区。Shard,Sharddb可以根据负载的情况被移动到不同的host中去。
对于Shard的管理,Netblog采用的是目录查询的管理方式。目录信息也存储在MySql中,同时会通过互备,Memcache,集群来确保安全性和高效性。
Shard Table API采用了多一层的映射模式来适合各种不同属性的查询情况。数据和记录在数据库中存储除了UserID以外还有对应的ItemID,ItemID的作用就是定义了具体获取数据的字段信息,例如关联照片表时,ItemID就是PhotoId,关联视频表时,ItemID就是videoID。
一个获取用户id为26博客信息的范例:
1.Where is user 26?
User 26 is on shard 5.
2.On shard 5; Give me all the $blogIDs ($itemIDs) of user 26.
That user's $blogIDs are: array(10,12,30);
3.On shard 5; Give me all details about the items array(10,12,30) of user 26.
Those items are: array(array('title' => "foo", 'message' => "bar"), array('title' => "milk", 'message' => "cow"));
对于Shard的管理Netblog采取的措施主要有这些:
1. 服务器之间的负载均衡根据用户数,数据库文件大小,读写次数,cpu load等等作为参数来监控和维护。根据最后的结果来迁移数据和分流数据。
2. 移动数据时会监控用户是否在操作数据,防止不一致性。
3. 对于数据库的可用性,采用集群,master-master,master-slave复制等手段。
最后通过三种技术来解决三个问题:
1. Memcached解决shard多次查询的效率问题。
根据上面的范例可以看到,一次查询现在被分割成为了三部分:shard查询,item查询,最终结果查询。通过memcached可以缓存三部分内容,由前到后数据的稳定性以及命中率逐渐降低,同时通过结合有效期(内容存储时效)和修改更新机制(add,update,delete触发缓存更新),可以极大地解决效率问题。甚至通过缓存足够信息减少大量的数据库交互。
2. 并行计算处理。
由于数据的分拆,有时候需要得到对于多Shard数据处理的结果汇总,因此就会将一个请求分拆为多个请求,分别交由多个服务器处理,最后将结果汇总。(类似于Map-reduce)
3. 采用Sphinx全文搜索引擎解决多数据分区数据汇总查询,例如察看网站用户的最新更新情况或者最热门日至。这个采用单独系统部署,通过建立全局信息索引,来查询数据情况。
以上是技术上的全部内容,作者在最后的几个观点十分值得学习,同时也不仅仅限于数据切分,任何框架设计都可以参考。
“Don't do it, if you don't need to!" (37signals.com)
"Shard early and often!" (startuplessonslearned.blogspot.com)
看起来矛盾的两句话,却是说出了对于数据切分的一些考虑。
首先在没有必要的情况下就不要考虑数据切分,切分带来的复杂性直接影响可用性,可维护性和一致性。在能够采用Scale up的情况下,可以选择Scale up降低框架复杂度。
另一方面,如果发现了业务增长情况出现必须要扩展的趋势,那么就要尽早着手去实施和规划扩展的工作,并且在切分和扩展过程中要不断地去优化和重构。
后话:
其实任何架构设计首要就是简单直接,不过度设计,不滥竽充数。其实就是要平衡好:可用性、高效性、一致性、可扩展性这四者之间的关系。良性循环、应时应事作出取舍和折中。用的好要比学会用更重要,更关键。