在现代分布式数据库中,水平扩展数据的需求导致了分片的广泛采用。虽然分片有助于管理跨多个节点的大型数据集,但它也带来了挑战,特别是在执行连接并确保高效的数据检索时。在本文中,我们探讨了应对这些挑战的各种概念和技术,特别关注广播连接、分片键对齐和分布式查询引擎,例如Presto 和 BigQuery。此外,我们还演示了如何使用 Node.js 和 Express.
在实际应用程序中处理这些问题以下是如何使用 Node.js 和 Express.js 在 PostgreSQL 中实现分片。
使用 Citus 或使用 Node.js 进行手动逻辑分片:
分片设置表:
使用分片表(shard1 上的 user_data 和 shard2 上的 user_data)。
创建 Express.js API:
基于分片键(例如 user_id)分发查询。
const express = require('express'); const { Pool } = require('pg'); const poolShard1 = new Pool({ connectionString: 'postgresql://localhost/shard1' }); const poolShard2 = new Pool({ connectionString: 'postgresql://localhost/shard2' }); const app = express(); app.use(express.json()); const getShardPool = (userId) => (userId % 2 === 0 ? poolShard1 : poolShard2); app.post('/user', async (req, res) => { const { userId, data } = req.body; const pool = getShardPool(userId); try { await pool.query('INSERT INTO user_data (user_id, data) VALUES (, )', [userId, data]); res.status(200).send('User added successfully'); } catch (err) { console.error(err); res.status(500).send('Error inserting user'); } }); app.get('/user/:userId', async (req, res) => { const userId = parseInt(req.params.userId, 10); const pool = getShardPool(userId); try { const result = await pool.query('SELECT * FROM user_data WHERE user_id = ', [userId]); res.status(200).json(result.rows); } catch (err) { console.error(err); res.status(500).send('Error retrieving user'); } }); app.listen(3000, () => console.log('Server running on port 3000'));
分片是跨多个数据库实例或分片水平分区数据的过程,以提高性能、可扩展性和可用性。当单个数据库实例无法处理大量数据或流量时,通常需要分片。
但是,当相关表在不同键上分片时,或者当一个表需要跨多个分片与另一个表进行联接时,由于需要 分散-聚集 操作,性能可能会下降。这就是理解广播连接和分片键对齐变得至关重要的地方。
当数据驻留在不同的分片中时,在这些分片之间执行连接可能会很复杂。以下是常见挑战的细分:
在许多系统中,表在不同的键上进行分片。例如:
执行连接时(例如,orders.user_id = users.user_id),系统需要从多个分片中获取数据,因为相关记录可能不在同一个分片中。
在分散-聚集连接中,系统必须:
广播连接 当被连接的一个表足够小,可以广播到所有分片时,就会发生。在这种情况下:
分布式查询引擎,例如 Presto 和 BigQuery 旨在跨分布式系统高效处理分片数据和联接查询。
Presto 是一个分布式 SQL 查询引擎,旨在跨异构数据源(例如关系数据库、NoSQL 数据库、数据湖)查询大型数据集。 Presto 跨分布式数据源执行联接,并可以通过最小化节点之间的数据移动来优化查询。
在 orders 按区域分片且 users 按 user_id 分片的场景中,Presto 可以使用其分布式执行模型跨不同分片执行连接。
查询:
const express = require('express'); const { Pool } = require('pg'); const poolShard1 = new Pool({ connectionString: 'postgresql://localhost/shard1' }); const poolShard2 = new Pool({ connectionString: 'postgresql://localhost/shard2' }); const app = express(); app.use(express.json()); const getShardPool = (userId) => (userId % 2 === 0 ? poolShard1 : poolShard2); app.post('/user', async (req, res) => { const { userId, data } = req.body; const pool = getShardPool(userId); try { await pool.query('INSERT INTO user_data (user_id, data) VALUES (, )', [userId, data]); res.status(200).send('User added successfully'); } catch (err) { console.error(err); res.status(500).send('Error inserting user'); } }); app.get('/user/:userId', async (req, res) => { const userId = parseInt(req.params.userId, 10); const pool = getShardPool(userId); try { const result = await pool.query('SELECT * FROM user_data WHERE user_id = ', [userId]); res.status(200).json(result.rows); } catch (err) { console.error(err); res.status(500).send('Error retrieving user'); } }); app.listen(3000, () => console.log('Server running on port 3000'));
急速将:
BigQuery 是一个完全托管的无服务器数据仓库,擅长运行大规模分析查询。虽然 BigQuery 抽象了分片的细节,但它会自动在多个节点之间分区和分布数据以优化查询。它可以轻松处理大型数据集,对于数据按时间或其他维度分区的分析查询特别有效。
const express = require('express'); const { Pool } = require('pg'); const poolShard1 = new Pool({ connectionString: 'postgresql://localhost/shard1' }); const poolShard2 = new Pool({ connectionString: 'postgresql://localhost/shard2' }); const app = express(); app.use(express.json()); const getShardPool = (userId) => (userId % 2 === 0 ? poolShard1 : poolShard2); app.post('/user', async (req, res) => { const { userId, data } = req.body; const pool = getShardPool(userId); try { await pool.query('INSERT INTO user_data (user_id, data) VALUES (, )', [userId, data]); res.status(200).send('User added successfully'); } catch (err) { console.error(err); res.status(500).send('Error inserting user'); } }); app.get('/user/:userId', async (req, res) => { const userId = parseInt(req.params.userId, 10); const pool = getShardPool(userId); try { const result = await pool.query('SELECT * FROM user_data WHERE user_id = ', [userId]); res.status(200).json(result.rows); } catch (err) { console.error(err); res.status(500).send('Error retrieving user'); } }); app.listen(3000, () => console.log('Server running on port 3000'));
BigQuery 自动处理分区和分配,最大限度地减少手动分片的需要。
在 Node.js 应用程序中处理分片数据时,经常会出现 分片键未对齐 以及需要 分散-聚集 连接等问题。以下是如何使用 Node.js 和 Express 应对这些挑战。
如果连接需要在所有分片上广播一个小表(例如用户),您可以通过获取一次小表并使用它与分片表中的数据连接来在应用程序层实现连接。
SELECT o.order_id, u.user_name FROM orders o JOIN users u ON o.user_id = u.user_id;
对于涉及分散-聚集连接的查询(例如,当分片键未对齐时),您将需要查询所有分片并在应用程序层聚合结果。
SELECT o.order_id, u.user_name FROM `project.dataset.orders` o JOIN `project.dataset.users` u ON o.user_id = u.user_id WHERE o.order_date BETWEEN '2024-01-01' AND '2024-12-31';
处理分片数据和执行连接时,请考虑以下最佳实践:
对齐分片键:如果可能,请确保相关表使用相同的分片键。这最大限度地减少了跨分片连接的需求并提高了性能。
反规范化:在频繁连接的场景中,请考虑对数据进行反规范化。例如,您可以将用户信息直接存储在 posts 表中,从而减少联接的需要。
对小表使用广播连接:如果其中一个表足够小,则将其广播到所有节点以避免分散-聚集查询。
预连接数据:对于经常访问的数据,考虑预连接并将结果存储在物化视图或缓存中。
利用分布式查询引擎:对于复杂的分析查询,请使用 Presto 或 BigQuery 等自动处理分布式联接和优化的系统。
在具有此类分片的分布式系统中,基于游标的分页需要谨慎处理,特别是因为数据分布在多个分片中。关键是:
让我们来看看如何使用 Node.js 和 Express 来实现这一点,考虑到数据驻留在不同的分片上并且需要在应用程序级别进行提取后连接。
假设我们有:
我们想要检索给定用户的分页帖子,但由于用户和帖子位于不同的分片上,因此我们需要拆分查询、处理分页,然后在应用程序级别执行联接。
查询相关分片:
分页策略:
应用程序级连接:
处理光标:
在这里,我们将跨不同的帖子分片执行查询,并通过游标(例如,created_at 或 post_id)进行过滤。
一旦我们从第一个查询中获得了相关的 post_id 和 user_id,我们将从相关分片中获取用户数据。
const express = require('express'); const { Pool } = require('pg'); const poolShard1 = new Pool({ connectionString: 'postgresql://localhost/shard1' }); const poolShard2 = new Pool({ connectionString: 'postgresql://localhost/shard2' }); const app = express(); app.use(express.json()); const getShardPool = (userId) => (userId % 2 === 0 ? poolShard1 : poolShard2); app.post('/user', async (req, res) => { const { userId, data } = req.body; const pool = getShardPool(userId); try { await pool.query('INSERT INTO user_data (user_id, data) VALUES (, )', [userId, data]); res.status(200).send('User added successfully'); } catch (err) { console.error(err); res.status(500).send('Error inserting user'); } }); app.get('/user/:userId', async (req, res) => { const userId = parseInt(req.params.userId, 10); const pool = getShardPool(userId); try { const result = await pool.query('SELECT * FROM user_data WHERE user_id = ', [userId]); res.status(200).json(result.rows); } catch (err) { console.error(err); res.status(500).send('Error retrieving user'); } }); app.listen(3000, () => console.log('Server running on port 3000'));
管理分布式系统中的分片数据提出了独特的挑战,特别是在执行高效连接时。了解广播连接、分散-聚集连接等技术并利用分布式查询引擎可以显着提高查询性能。此外,在应用程序级查询中,必须考虑分片键对齐、非规范化和优化的查询策略。通过遵循这些最佳实践并利用正确的工具,开发人员可以确保他们的应用程序有效处理分片数据并大规模维持性能。
以上是处理分布式系统中的分片数据:深入探讨连接、广播和查询优化的详细内容。更多信息请关注PHP中文网其他相关文章!