共计 3356 个字符,预计需要花费 9 分钟才能阅读完成。
背景
最近在浏览查问优化器的论文,发现 System R 中对于 Join 操作的定义个别分为了两种,即嵌套循环、排序 - 合并联接。在原文中,更偏向应用排序 - 合并联接逻辑。
思考到我的畛域是在解决分库分表或者其余的分区模式,这让我开始不由得联想咱们怎么在分布式场景利用这个 Join 逻辑,对于两个不同库外面的不同表咱们是没有方法间接进行 Join 操作的。查阅材料后发现原来早有定义,即分布式联接算法。
分布式联接算法
跨界点解决数据即分布式联接算法,常见的有四种模型:Shuffle Join(洗牌联接)、Broadcast Join(播送联接)、MapReduce Join(MapReduce 联接)、Sort-Merge Join(排序 - 合并联接)。
接下来将进行逐个理解与剖析,以便后续开发的利用。
Shuffle Join(洗牌联接)
先上原理解释:
Shuffle Join 的核心思想是将来自不同节点的数据从新散发(洗牌),使得能够联接的数据行最终位于同一个节点上。通常,对于要联接的两个表,会对联接键利用雷同的哈希函数,哈希函数的后果决定了数据行应该被发送到哪个节点。这样,所有具备雷同哈希值的行都会被送到同一个节点,而后在该节点上执行联接操作。
可能解释完还是有点含糊,举个例子,有两张表,别离以 id 字段进行分库操作,且哈希算法雷同(为了简略,这里只介绍分库场景,分库分表同理。算法有很多种,这里举例是 hash 算法),那么这两张表的分片或者能够在同一个物理库中,这样咱们不须要做大表维度的解决,咱们能够间接下推 Join 操作到对应的物理库操作即可。
在 ShardingSphere 中,这种场景相似于绑定表的定义,如果两张表的算法雷同,能够间接配置绑定表的关系,进行雷同算法的连贯查问,防止简单的笛卡尔积。
这样做的益处是能够尽量下推到数据库操作,在中间件层面咱们能够做并行处理,适宜大规模的数据操作。
然而,这很现实,有多少表会采纳雷同算法解决呢。
Broadcast Join(播送联接)
先上原理解释:
当一个表的大小绝对较小时,能够将这个小表的全副数据播送到所有蕴含另一个表数据的节点上。每个节点上都有小表的残缺正本,因而能够独立地与本地的大表数据进行联接操作,而不须要跨节点通信。
举个例子,有一张十分小的表 A,还有一张依照 ID 分片的表 B,咱们能够在每一个物理库中复制一份表 A,这样咱们的 Join 操作就能够间接下推到每一个数据库操作了。
这种状况比 Shuffle Join 甚至还有性能高效,这种相似于 ShardingSphere 中的播送表的定义,其存在相似于字典表,在每一个数据库都同时存在一份,每次写入会同步到多个节点。
这种操作的益处不言而喻,不仅反对并行操作而且性能极佳。
然而毛病也不言而喻,如果小表不够小数据冗余不说,播送可能会耗费大量的网络带宽和资源。
MapReduce Join(MapReduce 联接)
先上原理解释:
MapReduce 是一种编程模型,用于解决和生成大数据集,其中的联接操作能够分为两个阶段:Map 阶段和 Reduce 阶段。Map 阶段:每个节点读取其数据分片,并对须要联接的键值对利用一个映射函数,生成两头键值对。Reduce 阶段:两头键值对会依据键进行排序(在某些实现中排序产生在 Shuffle 阶段)和分组,而后发送到 Reduce 节点。在 Reduce 节点上,具备雷同键的所有值都会汇集在一起,这时就能够执行联接操作。
MapReduce Join 不间接利用于传统数据库逻辑,而是实用于 Hadoop 这样的分布式解决零碎中。然而为了不便了解,还是用 SQL 语言来剖析,例如一条 SQL:
SELECT orders.order_id, orders.date, customers.name
FROM orders
JOIN customers ON orders.customer_id = customers.customer_id;
会被转换为两个 SQL:
SELECT customer_id, order_id, date FROM orders;
SELECT customer_id, name FROM customers;
这个过程就是 Map 阶段,即读取 orders
和customers
表的数据,并为每条记录输入键值对,键是customer_id
,值是记录的其余部分。
下一个阶段可有可无,即 Shuffle 阶段。如果不在这里排序可能会在 Map 阶段执行 SQL 时候排序 / 分组或者在接下来的 Reduce 阶段进行额定排序 / 分组。在这个阶段次要将收集到的数据依照 customer\_id 排序分组,以确保雷同的 customer\_id 的数据达到 Reduce 阶段。
Reduce 阶段将每个对应的 customer_id 进行联接操作,输入并返回最初的后果。
这种操作广泛利用于两个算法齐全不雷同的表单,也是一种规范的解决模型,在这个过程中,咱们以一张逻辑表的维度进行操作。这种算法可能会耗费大量内存,甚至导致内存溢出,并且在解决大数据量时会相当耗时,因而不适宜须要低提早的场景。
额定补充
内存溢出场景广泛在如下场景:
1. 大键值对数量:如果 Map 阶段产生了大量的键值对,这些数据须要在内存中进行缓存以进行排序和传输,这可能会耗费大量内存。
2. 数据歪斜:如果某个键十分常见,而其余键则不那么常见,那么解决这个键的 Reducer 可能会接管到大量的数据,导致内存不足。这种景象称为数据歪斜。
3. 大值列表:在 Reduce 阶段,如果某个键对应的值列表十分长,解决这些值可能会须要很多内存。
4. 不合理的并行度:如果 Reduce 工作的数量设置得不适合(太少或太多),可能会导致单个工作解决不平均,从而导致内存问题。
我能想到的相应解决方案:
•内存到磁盘的溢写:当 Map 工作的输入缓冲区满了,它会将数据溢写到磁盘。这有助于限度内存应用,但会减少 I / O 开销。
•通过设置适合的 Map 和 Reduce 工作数量,能够更无效地分配资源,防止某些工作过载。具体操作能够将 Map 操作的分段比方 1~100,100~200,Reduce 阶段开设较少的并发解决。
•优化数据分布,比方应用范畴分区(range partitioning)或哈希分区(hash partitioning)来缩小数据歪斜。
Sort-Merge Join(排序 - 合并联接)
先上原理解释:
在分布式环境中,Sort-Merge Join 首先在每个节点上对数据进行部分排序,而后将排序后的数据合并起来,最初在合并的数据上执行联接操作。这通常波及到多阶段解决,包含部分排序、数据洗牌(从新散发),以及最终的排序和合并。
举个了解,还是下面的 SQL。
SELECT orders.order_id, orders.date, customers.name
FROM orders
JOIN customers ON orders.customer_id = customers.customer_id;
1. 对 orders
表按 customer_id
进行排序。
2. 对 customers
表按 customer_id
进行排序。
3. 同时遍历两个已排序的表,将具备雷同 customer_id
的行配对。
这个就有点相似于原生的排序 - 合并联接了。也是数据库场景的规范解决方法。
对于曾经排序的数据集或数据分布平均的状况,这种办法十分无效。如果数据未事后排序,这种办法可能会十分慢,因为它要求数据在联接之前进行排序。
当然,这个算法也会造成内存溢出的场景,解决方案如下:
1. 当数据集太大而无奈一次性加载到内存中时,能够应用内部排序算法。内部排序算法会将数据宰割成多个批次,每个批次独自排序,而后将排序后的批次合并。这种办法通常波及到磁盘 I / O 操作,因而会比内存中操作慢。
2. 对于合并步骤,能够应用流式解决技术,一次只解决数据的一小部分,并继续将后果输入到下一个解决步骤或存储系统。这样能够防止一次性加载大量数据到内存中。
3. 当内存不足以解决数据时,能够应用磁盘空间作为长期存储。数据库管理系统通常有机制来解决内存溢出,比方创立磁盘上的临时文件来存储过程中的数据。
4. 在分布式系统中,能够将数据扩散到多个节点上进行解决,这样每个节点只须要解决数据的一部分,从而缩小单个节点上的内存压力。
作者:京东科技 张豪杰
起源:京东云开发者社区 转载请注明起源