很多小型网站的开发人员一开始将注意力放在产品需求设计上,这本无可厚非。但如果忽视整体性能、可扩展性等方面的考虑,眼看着访问量一天天往上爬,可突然发现有一天网站因为访问量过大而崩溃了,到时候哭都来不及。

我在后端设计中曾经提到,对于高并发高访问的Web应用来说,数据库存取瓶颈一直是个令人头疼的问题。特别当你的程序架构还是建立在单数据库模式,而一个数据池连接数峰值已经达到500的时候,那你的程序运行离崩溃的边缘也不远了。在Web网站的规模从小到大不断扩展的过程中,数据库的架构也需要动态扩展,每一次扩展性能上都可以得到数量级的提升。

1. Web应用和数据库部署在同一台服务器上

Web应用和Database在同一台服务器

Web应用和Database在同一台服务器

在用户量、数据量、并发访问量都比较小的情况下可以采取这种模式。不过随着访问量增加,应用程序和数据库都抢用有限的系统资源,很快会遇到性能问题。

2. Web应用和数据库部署在各自独立的服务器上

WEB应用和数据库部署在各自独立的服务器上

WEB应用和数据库部署在各自独立的服务器上

服务分离,Web应用和数据库分开部署,服务器各司其职,在访问量增加时可以根据情况分别升级应用服务器和数据库服务器。这是一般小规模网站的典型部署方式,在将应用程序进行性能优化并且使用数据库对象缓存策略的情况下,可以承载较大的访问量,比如2000用户,200个并发,百万级别的数据量。

3. 数据库服务器采用集群方式部署

一个数据库多个实例

一个数据库多个实例

比如Oracle的一个数据库多个实例的情况。数据库物理介质为一个磁盘阵列,多个数据库实例以虚拟IP方式向外部应用服务器提供数据库连接服务。这种部署方式基本上可以满足绝大多数的Web应用需求。

4. 数据库的主从复制模式

数据库的主从复制

数据库的主从复制

在数据库访问中存在众多的查询操作,在多数情况下一个显著特点就是读操作远大于写操作,而且查询条件相对复杂,数据库的大部分性能实际消耗在查询上。假如能将数据库的读写操作分离,对于系统性能来讲会有一个很大的提高空间。

几乎所有的主流数据库都支持复制,以Mysql为例,只需要开启主服务器上的二进制日志以及在主服务器和从服务器上分别进行简单的配置和授权。主从复制是依据主服务器的日志文件进行的,主服务器日志中记录的操作会定时在从服务器上重放,从而实现复制,所以主服务器必须记录所有对于主数据库的更新操作。主从复制用于自动备份,但在这里我们的目的是读写分离。为保证数据的一致性,我们要求所有对于数据库的更新操作都是针对主数据库的,但是读操作可以针对从数据库来进行。

主从复制数据是异步完成的,这就导致主从数据库中的数据有一定的延迟,在读写分离的设计中必须要考虑这一点。以博客为例,博主发表了一篇文章,他需要马上看到自己的文章,但是对于其它读者来讲是可以允许延迟一段时间的。所以其他访问量更大的外部用户就可以读从数据库。

5. 数据库垂直分割

主从部署方式适用于读操作比写操作更加密集的前提,如果写操作占了主数据库CPU消耗的50%以上,我们再增加从服务器就没有意义了,因为所有的从服务器的写操作也将占到CPU消耗的50%以上,从服务器所能为查询操作提供的资源非常有限。数据库需要重新架构,我们需要采用数据库垂直分区技术。

最简单的垂直分区方式是将原来的数据库中独立的业务进行分拆,被分拆出来的部分与其它部分不需要进行Join连接查询操作。比如Web站点的Blog和论坛,是相对独立的,他们之间数据的关联性不是很强,这时可以将原来的的数据库拆分为一个Blog库,一个论坛库,以及剩余的表所组成的库。这三个库再各自进行主从数据库方式部署,这样整个数据库的压力就分担啦。

查询扩展性也是采用数据库分区最主要的原因之一,将一个大的数据库分成多个小的数据库可以提高查询的性能。事实上,当前十分火热的NOSQL技术就是采用空间换时间,使用key-value型的存储结构,即使有很多数据冗余,但由于避免了Join操作从而提升了性能,NOSQL在高并发高负载的网站广泛应用。

6. 数据库水平分割

在数据库的垂直分区之后,如果仍无法应对大量的写操作,这时我们需要的是水平分区。

水平分区意味着我们将同一个数据库表中的记录通过特定的算法进行分离,分别保存在不同的数据库表中,从而可以部署在不同的数据库服务器上。很多的大规模的站点基本上都是主从复制+垂直分区+水平分区这样的架构。水平分区并不依赖什么特定的技术,完全是逻辑层面的规划。对于那些频繁访问导致站点接近崩溃的热点数据,我们必须分区。

在对数据水平分区的时候,我们要找一个索引字段,比如USER_ID,它必须和所有的记录都存在关系,是分区数据库中的核心表的主键,在其它表中作为外键,并且在使用主键的时候,该主键不能是自增长的,必须是业务主键才可以。具体的分区方式有:

  • 余数分区:将User_ID%10后的值为依据存入到不同的分区数据库中,该算法简单高效,但是在分区数据库个数有变动的时候,整个系统的数据需要重新分布。
  • 范围分区:将User_ID的范围进行分区,比如1-100000范围为一个分区数据库,100001-200000范围为一个分区数据库,该算法在分区数据库个数有变动的时候,系统非常有利于扩展,但容易导致不同分区之间的压力不同,例如老用户所在的分区数据库的压力很大,但是新用户的分区数据库的压力偏小。
  • 映射关系分区:将对分区索引字段的每个可能的结果创建一个分区映射关系,这个映射关系非常庞大,需要将它们写入数据库中。比如当应用程序需要知道User_id为10的用户的BLOG内容在那个分区时,它必须查询数据库获取答案,当然,我们可以使用缓存来提高性能。这种方式详细保存了每一个记录的分区对应关系,所以各个分区有非常强的可伸缩性,可以灵活的控制,并且将数据库从一个分区迁移到另一个分区也很简单,也可以使各个分区通过灵活的动态调节来保持压力的分布平衡。

参考链接:http://blog.csdn.net/zhangzhaokun/archive/2009/10/22/4711693.aspx