关于分布式数据库

时间:2022-09-22 00:19:11
对于单机的数据库系统如果不能满足我们对性能的需要,那就需要多机来解决这个问题。有一台中心服务器负责对外,而其他的内部服务器则作为单机数据库使用。这样中心服务器需要维持对各个数据服务器的链接,并且要处理相关问题。在花期杯搜索引擎的制作中,出于系统扩展性的考虑,数据库系统需要一个这种系统,所以在此讨论一下可行性。

      直接在物理数据层处理这个问题的话需要处理的问题太多。典型的例子就是事务处理,如果一个事务更新多个子数据库,那么我们的中心服务器还需要实现自己的事务处理。这个方案看起来不那么好处理。而将问题向上提升,将问题提交到数据抽象层,问题就好办多了,有了一个可行的解决方案。通过数据的抽象,封装事务——每个业务对象只存在于一个数据库中,并且不会批量更改业务对象(但是可以批量访问)。这样的要求就可以实现一个分布式的系统。这个要求看似很苛刻,但实际上很多业务都是这种情况。

      在这种方案中,每个业务对象需要一个可排序的键。在各个子服务器之间按照这个键进行数据的分配和查找。比如这个键是一个英文字符串,那么就可以按照字母顺序,将A开头的数据放在一台服务器上,而B开头的方在另一台服务器上。这实际上是重新服务器的一个分组策略(Grouping)。所谓分组就是使数据按照一定的方法归类,每一类放在一起。查找的时候按照分类很容易就可以找到相应的数据,并且可以提高数据查询的并行性——A开头和B开头的数据可以同时进行,而不必等到一个完成再查找另外一个。这样的话,对于单个业务对象的操作将被Grouping分配给特定的子服务器。那批量访问呢?比如一个查找操作。广播是一个解决方案,就是给所有的子服务器发送一个查找命令。然后对返回的结果进行相应的处理,并最终返回给用户。这样,命令中的排序部分将产生问题——子服务器的数据到达中心服务器之后怎么排序?难道要分析SQL语句?可行。这样,由于结果已经分好组,并且已经被子服务器排序,中心服务器所要做的就是使用分组选择排序使用O(m*n)时间再排序(m=子服务器的数据条数,n=子服务器数)。

负载平衡

      如果一台子服务器满了怎么办?我们的分布式系统应该可以将数据转移到其他负载不是很高的服务器上。这和数据库中广泛应用的 B 树很相似 —— 如果一个节点满了就调整子树使得节点不满,并将数据转移到其他子树上,都满了的话扩展树的深度 —— 也就是添加新的子服务器。

这是最简单的负载均衡,服务器1的数据满了,移动一部分数据到其后的服务器2,并将服务器2的原有数据后移。但这样就需要改变原先的Grouping机制,因为服务器2上也有A开头的数据了。所以需要引进锚点Archor)。每个服务器有其两端数据的键,作为锚点。中心服务器获取这个锚点来判断数据到底可能在那个服务器中,然后向指定的服务器发出数据请求。这样上面的操作出了数据的移动之外就只需要改变中心服务器的锚点设置,而这项工作完全可以自动完成——数据移动的时候显然知道各个锚点的值。

      但还有一个问题就是如果服务器2也满了,那该怎么办?那就向服务器3移动数据。这是一个非常简单可行的方案。只是在数据非常不平衡的时候数据移动将非常耗时间。而如果我们去查找有空间的服务器的话,必将导致碎片,这个问题并非不能处理,但随着碎片的增多,将使得Grouping的速度优势完全丧失——一个条目一个锚点,那速度将会很慢。所以直接向后边的服务器移动数据是一个很可行的方案。而且,即使数据很不平衡,我们也可以通过Grouping的机制使得在填充数据的时候就不把服务器1填满,而是直接向服务器2移动数据这样就可以使所有的服务器有一个相同的负载。而这时正好是这个系统的查找效率最高的时候。这个机制实际上就是树的平衡机制。

      另外一个问题就是在移动数据的过程中如何保证数据的一致性?也就是如何使得移动期间的数据查找不会有缺损的数据,也不会有冗余的数据?实际上使用锚点后,只要锚点不改变,上层软件的操作就不会有任何改变,也就是说数据的移动变成了锚点的更新。这个时间很短完全可以同过原子操作来完成。之后删除原数据库中的数据。

      这个方案实际上是将数据库的内部处理提到更高的层面上,把一个服务器上的数据库作为一个数据节点来处理。

      由于各个子服务器的性能可能不一样,所以各个子服务器所能存储的数据条目的数目也会不一样,通过统一的条目控制负载不是一个好的方案。应该通过一个负载系数来处理。每个服务器的配置当众说明本服务器可以存储多少数据,然后中心服务器在必要时获取各个子服务器的负载系数,并将自动数据使得各个子服务器的负载系数相同。这样的好处首先就是如果新添加了一台子服务器,那么需要做的只是在中心服务器的子服务器列表中添加新的服务器就可以了,整个系统会迅速通过负载均衡机制将数据添加到新添加的数据库中。算然这个过程可能会比较慢,但完全自动,可以在系统空闲的时候后台执行。如果要去除一台子服务器需要做的也只是将该子服务器的负载调成0,这样负载系数永远高于其他的服务器,以使得其中的条目被分配到别的子服务器中,等到空了之后,再在中心服务器的子服务器列表中讲这个子服务器删除。整个过程系统不用停机,不用人为干预数据调整。

      而相比于一台服务器多个磁盘阵列,这种方式将并行操作,理论峰值性能应该高于单服务器多磁盘的方案。

事务的处理

      这是一个棘手的问题。主要是要同步多个服务器的事务。在中心服务器上启动一个事务,将使得中心服务器对每一个子服务器启动一个事务,并保存所有的事务ID为一张列表,然后对应一个中心服务器的事务ID。期间的所有的操作中心服务器将追踪每个操作所有需要执行的服务器(不是没个服务器都执行命令,例如更新一条记录)的返回值,如果有一个出错的子服务器操作就返回出错,这样可以通知用户会滚。而回滚过程也是对当前事务所对应的所有的子服务器的事务进行回滚。但因为实际上并不是所有的子服务器都进行了操作,完全可以根据需要,那个子服务器有操作再在该服务器上请求事务,然后操作,直至最后提交或回滚。但这样存在子服务器申请事务失败的问题——中心服务器已经返回成功申请事务,而子服务器却申请失败,无法返回子服务器的事务申请失败的返回值。但中心服务器依然可以返回一个错误值指示操作失败而不是事务申请失败。

      解决了事务处理,这个系统就和单一数据库看起来没有区别了。

由排序想到的

      由于需要排序,可以考虑实现自身的特殊查询语言或SQL解析器,并编写各种数据库SQL方言的适配器,这样分布式系统中各个子服务器可以使用不同的数据库。Group By 子句也需要相同的处理。各个子服务器的结果可能依然有相同的Group By 字段。这样怎么处理?处理一个完全的SQL数据库可就复杂了。将结果集存入本地数据库,然后再在本地执行,是一个可行的方案,但是这个方案的速度有点慢。对于AVG函数,这个结果就将是错误的。计算列也将由于列名重复而出错。

由于处理整个SQL标准很不现实,所以处理一个自己就简单多了。但怎样使得用户的使用不会超出我们的要求呢?直接传递SQL语句肯定是不行了,因为分析的开销也不小。直接使用自定义的查询语句或DOM的方式来限制SQL语句所使用的功能。这是一个可扩展的模型,可以在允许的情况下扩展到整个SQL的支持。这个方案有一个问题就是由于不知道数据的关键字,锚点机制不起作用。

      现在讨论一下在数据抽象层实现。这需要服务器中的对象都有一个键,并且在批量操作的时候比较慢。而在搜索引擎的开发中需要批量查找子字符串,在这种方式下将使得数据库自身的缓冲区失效——需要加载每一个对象,然后用自己的代码查找,每个对象实际上只访问一次就被废弃了。

SQL Server的分布式实现使用的是对等数据库,即每个子服务器都是对外的接口。然后通过查询优化器将SQL语句分解,上面那些讨论到的问题将不被处理的传输到请求服务器,然后完成剩下的操作。但这样限制颇多,系统设计中所拥有的特性将有很多无法实现。

最终的实现方案将问题交给了业务层,而没有在数据层处理。这样锚点机制可以起作用,而由于各个操作有各个子服务器自己执行,不作数据合并,也使得在抽象数据层的方案的缺点得以克服。只是由于层次再次向上提升,所以使用上受到很大限制——要使用新功能必须在业务层实现新的功能的处理。也就使得系统业务相关,不再拥有通用性,但满足了设计要求。 

 
 
 
 
 
 
 
 
 
关于分布式数据库