优化分布式数据库聚合作业的网络带宽

时间:2013-06-02 04:36:46

标签: database algorithm caching optimization distributed-computing

我有一个分布式/联合数据库,结构如下:

  1. 数据库分布在三个地理位置("节点")
  2. 在每个节点聚集多个数据库
  3. 关系数据库是PostgreSQL,MySQL,Oracle和MS SQL Server的混合体;非关系数据库是MongoDB或Cassandra
  4. 每个节点内和整个节点联合中的松散耦合是通过RabbitMQ实现的,每个节点都运行一个RabbitMQ代理
  5. 我正在为跨越节点联合的作业(即对于非节点本地的作业)实现只读节点间聚合作业系统。这些工作只执行" get"查询 - 它们不会修改数据库。 (如果作业的结果打算进入一个或多个数据库,那么这是通过一个单独的作业完成的,该作业不是我试图优化的节点间作业系统的一部分。)我的目标是最小化这些作业所需的网络带宽(首先是最小化节点间/ WAN带宽,然后最小化节点内/ LAN带宽);我假设每个WAN链路的统一成本,以及每个LAN链路的另一个统一成本。这些工作对时间不是特别敏感。我在节点内执行一些CPU负载平衡,但不在节​​点之间执行。

    通过WAN / LAN为聚合作业传输的数据量相对于群集或特定数据库本地的数据库写入量较小,因此将数据库完全分发是不切实际的。联邦。

    我用来最小化网络带宽的基本算法是:

    1. 如果作业运行在遍布联合的一组数据上,则管理器节点会向包含相关数据库查询的其他每个节点发送一条消息。
    2. 每个节点运行一组查询,用gzip压缩它们,缓存它们,并将它们的压缩大小发送到管理器节点。
    3. 管理器移动到包含多个数据的节点(具体地说,移动到群集中具有最多数据且具有空闲核心的机器);它从其他两个节点和集群中的其他机器请求其余数据,然后运行该作业。
    4. 如果可能,作业使用分而治之的方法来最小化所需的数据共址量。例如,如果作业需要计算联合中所有销售数字的总和,则每个节点在本地计算其销售总额,然后在管理器节点聚合(而不是将所有未处理的销售数据复制到管理器节点) 。但是,有时(例如,当在两个位于不同节点的表之间执行连接时)需要数据协同定位。

      我做的第一件事就是聚合作业,并在十分钟时间内运行聚合作业(机器都运行NTP,所以我可以合理地确定"每十分钟一次&#34 ;在每个节点上表示相同的事情)。目标是两个作业能够共享相同的数据,这降低了传输数据的总体成本。

      1. 鉴于两个查询同一个表的作业,我生成每个作业的结果集,然后我取两个结果集的交集。
      2. 如果两个作业都安排在同一节点上运行,则网络传输成本将计算为两个结果集之和减去两个结果集的交集。
      3. 两个结果集存储到PostgreSQL临时表(在关系数据的情况下),或者存储在选定用于运行作业的节点上的临时Cassandra columnfamilies / MongoDB集合(在nosql数据的情况下);然后,针对组合的结果集执行原始查询,并且传递的数据是针对各个作业的。 (此步骤仅在组合结果集上执行;单个结果集数据只是简单地传递到其作业,而不首先存储在临时表/列族/集合中。)
      4. 这导致网络带宽的改善,但我想知道是否有一个框架/库/算法可以改善这一点。我考虑的一个选项是在确定网络带宽时将结果集缓存在节点上并考虑这些缓存的结果集(即除了当前的预先安排的共址作业集之外,尝试在作业之间重用结果集,以便例如在一个10分钟的纪元中运行的作业可以使用前一个10分钟结果集中的缓存结果集),但除非作业使用完全相同的结果集(即除非他们使用相同的where子句),否则我不知道将填充结果集中的空白的通用算法(例如,如果结果集使用了子句&#34;其中N&gt; 3&#34;并且另一个作业需要带有子句&#34的结果集;其中N> 0&#34;然后我可以使用什么算法来确定我需要将原始结果集和结果集与第&#34条结合起来;其中N> 0且N <= 3&#34 ;) - 我可以尝试编写自己的算法来做到这一点,但结果将是一个无用的混乱。我还需要确定缓存数据何时过时 - 最简单的方法是将缓存数据的时间戳与源表上最后修改的时间戳进行比较,如果时间戳有,则替换所有数据已更改,但理想情况下,我希望能够仅更新已按行或每个块时间戳更改的值。

1 个答案:

答案 0 :(得分:4)

我已经开始实施我的问题解决方案。

为了简化节点内缓存并简化CPU负载平衡,我在每个数据库集群(&#34; Cassandra节点&#34;)上使用Cassandra数据库来运行聚合作业(以前我手动聚合本地数据库结果集 - 我使用单个Cassandra数据库来处理关系,Cassandra和MongoDB数据(缺点是某些关系查询在Cassandra上运行较慢,但这是为了弥补事实上,单个统一聚合数据库比单独的关系和非关系聚合数据库更容易维护。我也不再在10分钟的时期聚合作业,因为缓存使得这种算法变得不必要。

节点中的每台机器都引用一个名为Cassandra_Cache_ [MachineID]的Cassandra列,该列用于存储它已发送到Cassandra节点的key_ids和column_id。 Cassandra_Cache列家族由Table列,Primary_Key列,Column_ID列,Last_Modified_Timestamp列,Last_Used_Timestamp列以及由Table | Primary_Key | Column_ID组成的复合键组成。 Last_Modified_Timestamp列表示来自源数据库的数据的last_modified时间戳,Last_Used_Timestamp列表示聚合作业上次使用/读取数据的时间戳。当Cassandra节点从机器请求数据时,机器计算结果集,然后获取结果集和Cassandra_Cache中的表|键列的设置差异,并且具有与其Cassandra_Cache中的行相同的Last_Modified_Timestamp(如果时间戳不匹配,然后缓存的数据陈旧,并与新的Last_Modified_Timestamp一起更新。然后,本地计算机将设置差异发送到Cassandra节点,并使用设置差异更新其Cassandra_Cache,并更新用于组成结果集的每个缓存数据上的Last_Used_Timestamp。 (为每个表| key |列维护单独的时间戳的一个更简单的替代方法是维护每个表|键的时间戳,但这不太精确,并且table | key | column时间戳不会过于复杂。)保持Last_Used_Timestamps in Cassandra_Caches之间的同步只要求本地计算机和远程节点发送与每个作业关联的Last_Used_Timestamp,因为作业中的所有数据都使用相同的Last_Used_Timestamp。

Cassandra节点使用从节点内接收的新数据以及从其他节点接收的数据更新其结果集。 Cassandra节点还维护一个列系列,该列存储每个机器的Cassandra_Cache中的相同数据(Last_Modified_Timestamp除外,只需要在本地机器上确定数据何时失效),以及指示源ID如果数据来自节点内或来自另一个节点 - id区分不同节点,但不区分本地节点内的不同机器。 (另一种选择是使用统一的Cassandra_Cache而不是每台机器使用一个Cassandra_Cache加上另一个Cassandra_Cache用于节点,但我认为增加的复杂性不值得节省空间。)

每个Cassandra节点还维护一个Federated_Cassandra_Cache,它包含从本地节点发送到其他两个节点之一的{Database,Table,Primary_Key,Column_ID,Last_Used_Timestamp}元组。

当作业通过管道时,每个Cassandra节点都使用本地结果集更新其节点内缓存,并且还完成可以在本地执行的子作业(例如,在作业中对多个节点之间的数据进行求和,每个节点对其节点内数据求和,以便最小化需要在节点间联合中共同定位的数据量) - 如果子作业仅使用节点内数据,则可以在本地执行子作业。然后,管理器节点确定在哪个节点上执行剩余的作业:每个Cassandra节点可以通过获取其结果集的集合差异和已根据缓存的结果集的子集来本地计算将其结果集发送到另一个节点的成本到它的Federated_Cassandra_Cache,管理器节点最小化成本等式[&#34;从NodeX&#34运输结果集的成本; +&#34;从NodeY&#34;]传输结果集的成本。例如,它花费Node1 {3,5}将其结果集传输到{Node2,Node3},它花费Node2 {2,2}将其结果集传输到{Node1,Node3},并且它花费Node3 {4,3}将结果集传输到{Node1,Node2},因此作业在Node1上运行,成本为&#34; 6&#34;。

我为每个Cassandra节点使用LRU驱逐策略;我最初使用的是最早的驱逐策略,因为它实现起来更简单,并且需要更少的写入Last_Used_Timestamp列(每次数据更新一次而不是每次读取数据一次),但LRU策略的实现结果并不过分复杂和Last_Used_Timestamp写入没有造成瓶颈。当Cassandra节点达到20%的可用空间时,它会驱逐数据直到达到30%的可用空间,因此每次驱逐大约是可用总空间的10%。该节点维护两个时间戳:最后被驱逐的节点内数据的时间戳,以及最后被驱逐的节点间/联合数据的时间戳;由于节点间通信的延迟相对于节点内通信的延迟增加,驱逐策略的目标是使75%的缓存数据为节点间数据,25%的缓存数据为节点内数据通过将每次驱逐的25%作为节点间数据并且每次驱逐的75%是节点内数据,可以快速近似。驱逐如下:

while(evicted_local_data_size < 7.5% of total space available) {
    evict local data with Last_Modified_Timestamp < 
        (last_evicted_local_timestamp += 1 hour)
    update evicted_local_data_size with evicted data
}

while(evicted_federated_data_size < 2.5% of total space available) {
    evict federated data with Last_Modified_Timestamp < 
        (last_evicted_federated_timestamp += 1 hour)
    update evicted_federated_data_size with evicted data
}

在从节点内的机器和其他节点收到驱逐确认之前,不会永久删除被驱逐的数据。

然后,Cassandra节点向其节点内的计算机发送通知,指示新的last_evicted_local_timestamp是什么。本地计算机更新其Cassandra_Caches以反映新的时间戳,并在完成后向Cassandra节点发送通知;当Cassandra节点收到来自所有本地计算机的通知后,它会永久删除被驱逐的本地数据。 Cassandra节点还使用新的last_evicted_federated_timestamp向远程节点发送通知;其他节点更新其Federated_Cassandra_Caches以反映新的时间戳,并且Cassandra节点在从每个节点接收通知时永久删除被驱逐的联合数据(Cassandra节点跟踪一个数据来自哪个节点,因此在收到驱逐后来自NodeX的确认,节点可以在从NodeY接收驱逐确认之前永久删除被驱逐的NodeX数据。在所有计算机/节点都发送了通知之前,如果Cassandra节点从尚未驱逐其旧数据的计算机/节点接收结果集,则会在其查询中使用缓存的逐出数据。例如,Cassandra节点具有已驱逐的本地Table | Primary_Key | Column_ID数据,同时本地机器(未处理驱逐请求)未在其结果集中包含Table | Primary_Key | Column_ID数据,因为它认为Cassandra节点已经在其缓存中拥有了数据; Cassandra节点从本地机器接收结果集,并且因为本地机器没有确认驱逐请求,所以Cassandra节点在其自己的结果集中包含缓存的驱逐数据。