首頁 >web前端 >js教程 >處理分散式系統中的分割資料:深入探討連線、廣播和查詢最佳化

處理分散式系統中的分割資料:深入探討連線、廣播和查詢最佳化

Patricia Arquette
Patricia Arquette原創
2024-12-23 13:50:181043瀏覽

Handling Sharded Data in Distributed Systems: A Deep Dive into Joins, Broadcasts, and Query Optimization

在現代分散式資料庫中,水平擴展資料的需求導致了分片的廣泛採用。雖然分片有助於管理跨多個節點的大型資料集,但它也帶來了挑戰,特別是在執行連接並確保高效的資料檢索時。在本文中,我們探討了應對這些挑戰的各種概念和技術,特別關注廣播連接分片鍵對齊分散式查詢引擎,例如PrestoBigQuery。此外,我們也示範如何使用 Node.jsExpress.

在實際應用程式中處理這些問題

Node.js 與 Express.js 中的分片範例

以下是如何使用 Node.js 和 Express.js 在 PostgreSQL 中實作分片。

PostgreSQL 分片範例

使用 Citus 或使用 Node.js 進行手動邏輯分片:

邏輯分片範例

  1. 分片設定表:
    使用分片表(shard1 上的 user_data 和 shard2 上的 user_data)。

  2. 建立 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'));

1. 分散式資料庫中的分片

分片是跨多個資料庫執行個體或分片水平分區資料的過程,以提高效能、可擴展性和可用性。當單一資料庫執行個體無法處理大量資料或流量時,通常需要分片。

分片策略

  • 基於範圍的分片:資料根據鍵的範圍分佈在分片上,例如,按 order_date 對訂單進行分區。
  • 基於雜湊的分片:透過分片鍵(例如 user_id)對資料進行雜湊處理,以將資料均勻分佈在各個分片上。
  • 以目錄為基礎的分片:中央目錄追蹤資料駐留在系統中的位置。

但是,當相關表在不同鍵上分片時,或當一個表需要跨多個分片與另一個表進行聯接時,由於需要分散-聚集 操作,性能可能會下降。這就是理解廣播連接和分片鍵對齊變得至關重要的地方。


2. 分片系統中連接的挑戰

當資料駐留在不同的分片中時,在這些分片之間執行連接可能會很複雜。以下是常見挑戰的細分:

1. 分片鍵錯位

在許多系統中,表在不同的鍵上進行分片。例如:

  • users 表格可能會按 user_id 進行分片。
  • 訂單表格可能會按區域分片。

執行連線時(例如,orders.user_id = users.user_id),系統需要從多個分片中取得數據,因為相關記錄可能不在同一個分片中。

2. 分散-聚集連結

在分散-聚集連結中,系統必須:

  • 向所有保存相關資料的分片發送請求。
  • 跨分片的聚合結果。 這會顯著降低效能,尤其是當資料分佈在許多分片上時。

3. 廣播加入

廣播連線 當被連接的一個表格夠小,可以廣播到所有分片時,就會發生。在這種情況下:

  • 小表(例如使用者)在較大的分片表(例如訂單)所在的所有節點上複製。
  • 每個節點都可以將其本地資料與廣播資料連接起來,從而避免跨分片通訊。

3. 使用分散式查詢引擎進行分片資料

分散式查詢引擎,例如 PrestoBigQuery 旨在跨分散式系統高效處理分片資料和聯結查詢。

急板/Trino

Presto 是一個分散式 SQL 查詢引擎,旨在跨異質資料來源(例如關聯式資料庫、NoSQL 資料庫、資料湖)查詢大型資料集。 Presto 跨分散式資料來源執行聯接,並且可以透過最小化節點之間的資料移動來最佳化查詢。

範例用例:使用 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'));

急速將:

  1. 使用分散-聚集取得相關使用者記錄。
  2. 跨節點連接資料。

Google BigQuery

BigQuery 是一個完全託管的無伺服器資料倉儲,擅長執行大規模分析查詢。雖然 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 自動處理分區和分配,最大限度地減少手動分片的需要。


4. 處理 Node.js 應用程式中的分片鍵錯位

在 Node.js 應用程式中處理分片資料時,經常會出現 分片鍵未對齊 以及需要 分散-聚集 連接等問題。以下是如何使用 Node.jsExpress 來應對這些挑戰。

在 Node.js 中處理廣播連線

如果連接需要在所有分片上廣播一個小表(例如用戶),您可以透過獲取一次小表並使用它與分片表中的資料連接來在應用程式層實現連接。

SELECT o.order_id, u.user_name
FROM orders o
JOIN users u
ON o.user_id = u.user_id;

在 Node.js 中處理分散-聚集查詢

對於涉及分散-聚集連接的查詢(例如,當分片鍵未對齊時),您將需要查詢所有分片並在應用程式層聚合結果。

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';

5. 分片資料查詢最佳化的最佳實務

處理分片資料和執行連線時,請考慮以下最佳實務:

  1. 對齊分片鍵:如果可能,請確保相關表使用相同的分片鍵。這最大限度地減少了跨分片連接的需求並提高了效能。

  2. 反規範化:在頻繁連接的場景中,請考慮對資料進行反規範化。例如,您可以將使用者資訊直接儲存在 posts 表中,從而減少聯結的需要。

  3. 對小表使用廣播連接:如果其中一個表足夠小,則將其廣播到所有節點以避免分散-聚集查詢。

  4. 預先連接數據:對於經常存取的數據,考慮預先連接並將結果儲存在物化視圖或快取中。

  5. 利用分散式查詢引擎:對於複雜的分析查詢,請使用PrestoBigQuery 等自動處理分散式聯接和最佳化的系統。


6. 使用分片資料進行基於遊標的分頁的最佳實踐

在具有此類分片的分散式系統中,基於遊標的分頁需要謹慎處理,特別是因為資料分佈在多個分片中。關鍵是:

  1. 拆分查詢:獨立查詢每個分片以取得相關資料。
  2. 分頁處理:決定如何對分片資料(貼文或使用者)進行分頁,並收集相關結果。
  3. 在應用程式層級加入:從每個分片中取得結果,在記憶體中加入數據,然後套用下一頁的遊標邏輯。

讓我們來看看如何使用Node.jsExpress 來實現這一點,考慮到資料駐留在不同的分片上並且需要在應用程式層級進行提取後連接。

如何處理分頁和與分片表的連接

假設我們有:

  • 貼文按user_id分片的表格。
  • users 按 user_id 分片的表。

我們想要檢索給定用戶的分頁帖子,但由於用戶和帖子位於不同的分片上,因此我們需要拆分查詢、處理分頁,然後在應用程式層級執行聯接。

方法:

  1. 查詢相關分片:

    • 首先,您需要跨分片查詢 posts 表以取得貼文。
    • 取得相關貼文後,使用貼文中的 user_id 查詢使用者表(同樣是跨分片)。
  2. 分頁策略:

    • 貼文分頁:您可以使用created_at、post_id或其他唯一欄位對貼文表進行分頁。
    • 使用者分頁:您可能需要單獨取得使用者資料或使用 user_id 作為遊標來對使用者進行分頁。
  3. 應用程式層級連線:

    • 從相關分片(貼文和使用者)檢索資料後,在應用程式層級加入它們。
  4. 處理遊標:

    • 取得第一頁後,使用最後的created_at或post_id(來自貼文)作為下一個查詢的遊標。

實施範例

1. 跨分片查詢帖子

在這裡,我們將跨不同的貼文分片執行查詢,並透過遊標(例如,created_at 或 post_id)進行篩選。

2.使用Post資料跨分片查詢用戶

一旦我們從第一個查詢中獲得了相關的 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'));

關鍵細節:

  1. 貼文分頁:遊標基於created_at字段或貼文中的另一個唯一字段,用於對結果進行分頁。
  2. 獨立查詢分片:由於貼文和使用者在不同的鍵上分片,因此我們獨立查詢每個分片,在應用程式層級執行聯接之前從所有分片收集資料。
  3. 遊標處理:檢索結果後,我們使用貼文中的last created_at(或post_id)來產生下一頁的遊標。
  4. 在應用程式層級加入:從相關分片取得資料後,我們根據記憶體中的 user_id 將貼文與使用者資料加入。

結論

管理分散式系統中的分片資料提出了獨特的挑戰,特別是在執行高效連接時。了解廣播連接分散-聚集連接等技術並利用分散式查詢引擎可以顯著提高查詢效能。此外,在應用程式層級查詢中,必須考慮分片鍵對齊非規範化和最佳化的查詢策略。透過遵循這些最佳實踐並利用正確的工具,開發人員可以確保他們的應用程式有效處理分片資料並大規模維持效能。

以上是處理分散式系統中的分割資料:深入探討連線、廣播和查詢最佳化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn