现在的位置: 首页 > 综合 > 正文

高性能MySQL.读书笔记(四)可扩展的MySQL

2013年07月02日 ⁄ 综合 ⁄ 共 4347字 ⁄ 字号 评论关闭

向上扩展

向上扩展(有时也称为垂直扩展)意味着购买更多强悍的硬件,对很多应用来说这是唯一需要做的事情。这种策略有很多好处。例如,单台服务器比多台服务器更加容易维护和开发,能显著节约开销。在单台服务器上备份和恢复应用同样很简单,因为无须关心一致性或者哪个数据集是权威的。向上扩展比处外扩展更简单。

 

尽管可以在非常强大的服务器上运行MySQL,但和大多数据库一样,在增加硬件资源的情况下MySQL也无法很好地扩展(非常奇怪!)。为了更好地在大型服务器上MySQL,一定要尽量选择最新的版本。建议使用MySQL5.5或更新的版本,或者PerconaServer 5.1及后续版本。即使如此,当前合理的“收益递减点”的机器配置大约是256G RAM,32核CPU以及一个PCIeflash驱动器。实际上在更大的系统上,可以通过运行多个小的MySQL实例来替代单个大实例,这样可以获得更好的性能。

 

向上扩展的策略能够顶一段时间,实际很多应用是不会达到天花板的。但是如果应用变得非常庞大,向上扩展就没办法了。

 

向外扩展

向外扩展有时也称为横向扩展或者水平扩展,策略划分为三个部分:复制、拆分,以及数据分片(sharding)。

 

最简单的扩展方法是通过复制将数据分发到多个服务器上,然后将备库用于读查询,即读写分离。

 

另外一个比较常见的向外扩展方法是将工作负载分布到多个“节点”。许多大型的MySQL应用不能自动分布负载,就算有也没有做到完全的自动化。

 

一个节点可能就是一台服务器,如果设计冗余,那么一个节点通常是下面的某一种:主主复制,一主多备复制,一主多备DRBD复制,共享存储。大多数情况下,一个节点内的所有服务器应该拥有相同的数据。我们倾向于把主主复制架构作为两台服务器的主动被动节点。

 

按功能拆分

按功能拆分,或者说按业务拆分,意味着不同的节点执行不同的任务。将独立的服务节节点分配给不同的应用,这样每个节点只包含特定应用的数据,即按库划分。

 

另一个可能的按功能划分方法是对单个服务器的数据进行划分,产确保划分的表集合之间不会执行关联操作,即按表划分。

 

但是功能划分不能无限地进行扩展,因为如果一个功能区哉被捆绑到单个MySQL节点,就只能进行垂直扩展。如果进行了太多的功能划分,以后就很难采用更具扩展性的设计了。

 

数据分片

在目前用于扩展大型MySQL应用的方案中,数据分片是最通用且最成功的方法。它把数据分割成一小片,或者说一块,然后存储到不同的节点中。

 

数据分片和某些类型的按功能划分联合使用时非常有用。大多数分片系统也有一些“全局”的数据不会被分片(例如城市列表或者登录数据)。全局数据一般存储在单个节点上,并且通常保存在类似memcached这的缓存里。

实际上,大多数应用只会对需要的数据做分片,通常是那些将会增长得非常庞大的数据。

 

如果事先知道应用会扩大到很大的规模,并且清楚按功能划分的局限性,就可以跳过中间步骤,直接从单个节点升级为分片数据存储。事实上,这种前瞻性可以帮你避免由于粗糙分片方案带来的挑战。

采用分片的应用常用一个数据库访问抽象层,用以降低应用和分片数据存储之间通信的复杂度,但无法完全隐藏分片。因为相比数据存储,应用通常更了解跟查询相关的一些信息。

 

分片?还是不分片?

这是个问题,对吧?答案很简单:如非必要,尽量不分片。首先看是否能通过性能调优或者更好的应用或数据库设计来推迟分片。如果能足够长时间地推迟分片,也许可以直接购买更大的服务器,升级MySQL到性能更优的版本,然后继续使用单台服务器,也可以增加或减少复制。

 

简单的说,对单台服务器而言,数据大小或负载变得太大时,分片将是不避免的,如果不分片,而是尽可能地优化应用,系统能扩展到什么程度呢?答案可能会让你很惊讶。有些非常受欢迎的应用,你可能以为从一开始就分片了,但实际上直到已经值数十亿美元并且流量极其巨大也还没有采用分片的设计。分片不是城里唯一的游戏,在没有必要的情况下采用分片的架构来构建应用会步履维艰。

 

 

分片要解决如下几个问题,关于分片的详细做法我们以后详细讨论。

选择分区键

多个分区键

跨分片查询

在节点上部署分片

重新均衡分片数据

 

生成全局唯一ID

当希望把一个现有系统转换为分片数据存储时,经常会多台机器上生成全局唯一ID。单一数据存储时通常可以使用AUTO_INCREMENT来获取唯一ID。但涉及多台服务器时就不凑效了。以下几种方法可以解决这个问题:

使用auto_increment_increment和auto_increment_offset

这种方法比较简单,是普遍的方法。

全局节点中创建表

在一个全局数据库节点创建一个包含AUTO_INCREMENT列的表,应用可以通过这个表来生成唯一数字。

使用memcached

在memcached的API中有一个incr()函数,可以自动增长一个数字并返回结果。

批量分配数字

应用可以从一个全局节点中请求一批数字,用完后再申请。

使用复合值

可以使用一个复合值来做唯一ID,例如分片号和自增数的组合。

使用GUID值

可以使用UUID()函数来生成全局唯一键值。注意,这个函数在基于语句的复制时不能正确复制,但可以先获得这个值,再存放到应用的内存中,然后作为数字在查询中使用。GUID的值很大并且不连续,因此不适合作InnoDB表的主键


关于数据库Sharding方面的资料,可以下载一份文档:http://download.csdn.net/download/xtjsxtj/6506217

 

通过多实例扩展

研究和经验表明MySQL并不能完全发挥现代硬件的性能。当扩展到超过24个CPU核时,MySQL的性能开始趋于平缓,不再上升。当内存超过128G时也同样如此,MySQL甚至不能完全发挥诸如Virdent或Fusion-io卡这样的高端PCIeflash设备的I/O性能。

 

不要在一台性能强悍的服务器上只运行一个服务器实例,你可以让数据分片足够小,以使每台机器上都放置多个分片(这也是我们一直提倡的),每台服务器上运行多个实例,然后划分服务器的硬件资源,将其分配给每个实例。

这样做尽管比较麻烦,但确实有效。这是一种向上扩展和向外扩展的组合方案。

 

我们已经在一台性能强悍的硬件上获得10倍或15倍的合并系数。你需要平衡管理复杂度代价和更优性能的收益,以决定哪种方法是最优的。

这个时候网卡可能会成为瓶颈,可以通过使用多块网卡并进行绑定来解决这个问题。

 

另一个方法是将每个MySQL实例绑定到特定的CPU内核上。

 

向内扩展

处理不断增长的数据和负载最简单的办法是对不再需要的数据进行归档和清理。这种操作可能会带来显著的成效,具体取决于工作负载和数据特性。

 

保持活跃数据独立性

即使不真的把老数据转移到别的服务器,许多应用也能受益于活跃数据和非活跃数据的隔离。这有助于高效利用缓存,并为活跃数据使用不同的硬件或应用架构。下面列举了几种方法:

将表划分为几个部分:

分表是一种比较明智的办法,特别是整张表无法完全加载到内存时。

 

MySQL分区表

MySQL5.1以后提供了对表进行分区的功能,能够帮助把最近的数据留在内存中。

 

基于时间的数据分区

如果应用不断有新数据进来,一般新数据总是比旧数据更加活跃。例如博客流量最大的是七天内发表的文章和评论。更新的大部分是相同的数据集。因此这些数据被完整地保留在内存中,其它数据则完全可以放到别的地方。

象日志,异动,通话记录这样的数据,往往经常查询的都是最近几个月的数据,这样就比较适合按年,按月或按天建表存储数据。

 

直接连接

有些人认为负载均衡是配置在应用和MySQL服务器之间的东西。但这并不是唯一的负载均衡方法。你可以在保持应用和MySQL连接的情况下使用负载均衡。事实上,集中化的负载均衡系统只有在存在一个对等置换的服务器池时才能很好地工作。如果应用需要做一些决策,例如在备库上执行读操作是否安全,就需要直接连到服务器。

 

除了出现的一些逻辑,应用为负载均衡做决策是非常高效的。例如,如果有两个完全相同的备库,你可以使用其中一个来处理特定分片的数据查询,另一个处理其他的查询。

 

 

 

复制上的读写分离

MySQL复制产生了多个数据副本,你可以选择在备库还是主库上执行查询。由于备库复制是异步的,因此主要的难点是如何处理备库上的脏数据。

 

基于查询分离

将所有不能容忍脏数据的读和写查询分配到主库,其它的读查询分配到备库上。

 

基于脏数据分离

让应用检查复制延迟,以确定备库数据是否太理。许多报表类应用都用这个策略:只需要晚上加载的数据复制到备库即可。

 

基于会话的分离

判断用户自己是否修改了数据,需要看到自己的更新。可以在会话层设置一个标记,表明做了更新,就将该用户的查询在一段时间内总是指向主库。这是我们通常推荐的策略,因为它是在简单和有效性之间的一种很好的妥协。

还可以把基于会话的分离和复制延迟监控结合起来,如果用户在10S前更新了数据,而所有备库延迟在5S内,就可以安全地从备库读取数据。

 

基于版本分离

从备库读取对象的版本或时间戳来判断数据是否够新。如果备库数据太旧,可以从主库获取最新数据。

 

基于全局版本/会话分离

当应用执行写操作时,在提交事务后,执行一次SHOW MATERSTATUS操作。然后在缓存中存储主库日志坐标,作为被修改对象以及/或者会话的版本号。当应用连接到血库时,执行SHOWSLAVE STATUS并将血库上的坐标和缓存中的版本号相对比。如果备库更新,就可以安全地读取备库数据。

 

大多数读/写分离解决方案都需要监控复制延迟来决策读查询的分配,不管是通过复制或负载均衡器,或是一个中间系统。如果这么做,需要注意通过SHOWSLAVE STATUS得到的Seconds_behind_maste列的值并不能准确地用于监控延迟。

 

 

总结

正确地扩展MySQL并没有看起来那么美好。从第一天就建立一个Facebook架构,这并不是正确的方式。最好的策略是实现应用所明确需要的,并为可能的快速增长做好预先规划,成功的规划是可以为任何必要的措施筹集资金以满足需求。

 

在MySQL扩展策略方面,典型的应用在增长到非常庞大时,通常先从单个服务器转移到向外扩展的拥有读备库的架构,再到数据分片和按功能分区。我们并不同意那些提倡为每个应用“尽早分片,尽量分片”(shardearly, shard often)的建议。这很复杂且代价昂贵,并且许多应用可能根本不需要。可以花一些时间去看看新的硬件和新版本的MySQL有哪些变化。

 

当存在多个服务器时,可能出现跟一致性或原子性相关的问题。我们看到的最普遍的问题是缺少会话一致性。负载均衡器可以解决这个问题,但它本身也有一些问题。

抱歉!评论已关闭.