Hướng dẫn dùng distributed mysql trong PHP

Phân chia dữ liệu (Sharding) là một giải pháp chia nhỏ một database lớn thành nhiều database nhỏ. Ta có thể phân tách từng bảng hoặc cả một database ra nhiều phần nhỏ đặt ở nhiều máy chủ (server) khác nhau.

Điều này sẽ giúp cho hệ thống database của chúng ta đạt được các tính chất như: khả năng bảo trì (manageability), hiệu suất (performance), tính sẵn sàng (availability), và cân bằng tải (load balancing) của ứng dụng.

Giải pháp này cũng giúp giảm chi phí và khả năng mở rộng (scalability) để scale up database bằng cách dùng nhiều server nhỏ gộp lại hơn là nâng cấp một server lớn.

Hướng dẫn dùng distributed mysql trong PHP

Các cách thức Sharding dữ liệu

Có 3 cách thức phân chia (Sharding) dữ liệu như sau:

  • Horizontal sharding: Là cách chia dữ liệu của cùng một bảng (table) ra nhiều database khác nhau. Ví dụ ta có bảng dữ liệu thông tin về người dùng, ta sẽ dựa trên location của người dùng để quyết định nó nằm ở database nào, ví dụ người dùng ở Sài Gòn thì sẽ chứ ở DB_SG, thông tin người dùng ở Biên Hòa sẽ nằm ở DB_BH hay thông tin người dùng ở Vĩnh Long sẽ nằm ở DB_VL. Giải pháp này có một vấn đề là ta phải chọn nơi dữ liệu Sharding rất cẩn thận để không gây mất cân bằng (unbalanced) giữa các database dẫn tới rất có thể có một vài Server sẽ thành điểm nóng (hot spot), ví dụ người dùng ở Sài Gòn chắc chắn là đông hơn rất nhiều lần người dùng ở Biên Hòa hay Vĩnh Long.
  • Vertical sharing: Là cách chỉa dữ liệu dựa trên tính năng (feature) của hệ thống. Ví dụ ta thiết kế một hệ thống chia sẻ ảnh giống Instagram, ta sẽ lưu thông tin của User vào DB_Users, lưu thông tin ảnh họ up lên trên một DB khác là DB_Photos và thông tin danh sách những người họ follow ở một database thứ 3 là DB_Follow. Cách làm này rất là rõ ràng dễ để triển khai và không làm ảnh hưởng lớn đến ứng dụng, nhưng khi hệ thống lớn dần lên thì dữ liệu cũng lớn dần theo, do đó ta lại phải thực hiện sharding tiếp những database trên từng feature (bởi vì 1 DB không thể xử lý 10 tỷ bức ảnh của 140 triệu user được).
  • Directory Based sharding: Cách này sẽ yêu cầu ta phải thiết kế một “lookup service” có tác dụng quyết định ánh xạ (mapping) dữ liệu sẽ nằm ở database nào. Mỗi khi có request ghi hoặc đọc sẽ thông qua lookup service để mapping vị trí sẽ đọc và ghi. Khi business mở rộng số lượng server có thể tăng lên mà không ảnh hưởng hay đòi hỏi ứng dụng phải thay đổi theo.
Hướng dẫn dùng distributed mysql trong PHP

Những tiêu chí để phân vùng dữ liệu

Bên trên ta đã tìm hiểu và các cách thức để sharding dữ liệu, giờ ta hãy tìm kiểu sâu hơn về các tiêu chí để phân vùng dữ liệu.

  • Phân vùng theo key hoặc hash: Hệ thống sẽ áp dụng các hàm băm (hash function) cho một hoặc nhiều key chính trong dữ liệu (thường là Id) để xác định ra một con số của phân vùng nó đang nằm. Ví dụ: nếu hệ thống có 100 máy chủ database và Id của bản ghi sẽ tự tăng lên mỗi lần một bản ghi mới được chèn. Trong ví dụ này, hàm băm có thể là Id % 100, từ đó ta có thể xác định vị trí của dữ liệu. Cách tiếp cận này cần đảm bảo phân bổ dữ liệu thống nhất giữa các máy chủ. Nhưng cách làm này có một điểm yếu là mỗi khi ta thay đổi số lượng Server tăng hoặc giảm thì hàm băm cũng sẽ phải thay đổi theo và sẽ xuất hiện hiện tượng xáo trộn tập dữ liệu trên mỗi server, và đòi hỏi ta phải phân phối lại dữ liệu và xuất hiện độ trễ dữ liệu. Có một cách giải quyết vấn đề này là sử dụng Consistent Hashing (hàm băm nhất quán).
  • Phân vùng theo danh sách (list): Cách phân vùng sẽ được quyết định gán một danh sách các giá trị ngay từ đầu, từ đó mỗi lần ghi một bản ghi mới ta sẽ tìm ra giá trị của bản ghi nằm ở phân vùng nào và ghi vào đó. Ví dụ ta sẽ quyết định nhóm tất cả các người dùng ở Ai-len, Na Uy, Thụy Điển, Phần Lan và Đan Mạch và một phân vùng có tên là Nordic (Bắc Âu).
  • Phân vùng vòng tròn (round-robin): Cách phân vùng này rất đơn giản là mỗi lần có thao tác ghi ta sẽ ghi vòng tròn quanh các phân vùng có sẵn. Cách làm này đơn giản nhưng rất khó để xác định dữ liệu nào ở đâu lấy ra khi cần.
  • Phân vùng tổng hợp: Là cách tổng hợp các giải pháp trên thành một giải pháp mới. Ví dụ ta có thể áp dụng phân vùng theo Key/Hash và sau đó xác định được key rồi ta sẽ áp dụng tiếp phân vùng theo danh sách để chứa key sau khi hash vào một danh sách cụ thể nào đó. Consistent Hashing có thể được coi là cách phân vùng tổng hợp.

Các vấn đề khi Sharding dữ liệu

Vì việc dữ liệu sẽ bị phân tán đi nhiều Server khác nhau do vậy sẽ phát sinh một vài vấn đề khi sharding dữ liệu như sau:

  • Joins and De-normalization: Bởi vì việc dữ liệu ở các bảng được phân bố và trải rộng đi nhiều Database/Server khác nhau nên việc join bảng dữ liệu là điều rất khó khăn và cũng không đem lại hiệu suất bởi vì việc dữ liệu phải được queries từ nhiều máy chủ khác nhau. Để giải quyết vấn đề này ta có thể thiết kế dữ liệu dạng non-relationship Database hay còn gọi là NoSQL, giống như MongoDB hay Cassandra hai hệ NoSQL rất nổi tiếng và hỗ trợ Sharding vô cùng tốt. Tuy nhiên việc này ta phải chấp nhận rủi ro việc không nhất quán dữ liệu (inconsistency)
  • Referential integrity: Cũng vì lý do trên về việc truy vấn chéo dữ liệu giữa các Database nằm trên các máy chủ khác nhau là bất khả thi, do vậy việc ràng buộc khóa ngoại để bảo đảm sự toàn vẹn dữ liệu cũng là một điều vô cùng khó khăn. Hầu hết RDBMS không hỗ trợ các ràng buộc khóa ngoại trên các cơ sở dữ liệu phân tán trền nhiều server. Do vậy để đạt được điều này ta phải thực hiện điều này trên mã ứng dụng (code), điều này sẽ tăng tính phức tạp của ứng dụng.
  • Rebalancing: Trong suốt quá trình hệ thống vận hành có rất nhiều lý do ta thay đổi các chiến thuật hay cách thức Sharding dữ liệu, như business tăng trưởng yêu cầu thêm Server… Và mỗi khi như vậy ta phải tái cân bằng (rebalancing) dữ liệu trên tất cả các Server, có nghĩa là dữ liệu phải được phân phối lại trên toàn bộ server. Để làm được điều này mà không có độ trễ (downtime) là cực kỳ khó khăn. Mô hình sharding “Directory Based” có khả năng rebalancing tốt nhất nhưng lại tăng độ phức tạp của ứng dụng và tạo thêm một single point of failure (ví dụ “lookup service”).

Tóm lại với Sharding thì mô hình sharding với key/hash với Consistent Hashing hiện tại là giải pháp tối ưu nhất cho việc Rebalancing.

Consistent Hashing

Vấn đề hashing trong distributed system

Như đã nhắc đến rất nhiều lần bên trên thì Distributed Hash Table (DHT — Bảng băm phân tán) là một thành phần cơ bản trong những distributed scalable systems (hệ thống phân tán có khả năng mở rộng). Một Hash Table cần một cặp key-value, và một hàm “hash” để map key với vị trí mà value của nó được lưu trữ.

index = hash_function(key)

Giả sử ta có thiết kế một distributed caching system (Redis cache chẳng hạn). Chúng ta có “n” cache servers, thì hàm băm (hash function) để map key với vị trí của Cache server nó làm ở đâu sẽ là key % n. Nó rất đơn giản và dễ sử dụng, tuy nhiên nó có hai nhược điểm chính là:

  1. Nó rất khó horizontally scalable (phân tán theo chiều ngang). Bởi vì mỗi lần một cache server mới được thêm vào Load Balancing, mọi mapping trước kia giữa key với value trước đây sẽ sai hết. Hiện tượng cache miss sẽ xảy ra tràn lan và rất mất thời gian và khó khăn để cập nhật tất cả các mapping key-value, với một hệ thống lớn với số lượng caching data khổng lồ đây là ác mộng với những người quản lý dữ liệu.
  2. Nó có thể không đáp ứng được việc cân bằng tải (Load Balanced), bởi vì ta không thể chắc chắn rằng việc hash và mapping như vậy những data được lưu trữ ở các server khác nhau có độ truy cập có cân bằng nhau không? Rất có thể những data ít được truy cập được tập trung vào một vài server và vài server còn lại lại chứa những data được truy cập nhiều, dẫn tới tình trang một vài server thì hoạt động hết công suất, một vài server thì lại quá nhàn rỗi ngồi chơi không 😙.

Với những vấn đề như vậy thì Consistent Hashing là một giải pháp rất tốt để cải tiến việc chia server với các caching system.

Consistent Hashing giải quyết vấn đề này như thế nào?

Consistent Hashing là một chiến thuật hiệu quả cho việc phân chia distributed caching systems và DHT. Nó cho phép việc thêm hay xóa các node trên một cụm server (cluster) mà ít gây ra sự xáo trộn dữ liệu, do đó nó các hệ thống caching system sẽ dễ dàng scale-up hay scale down.

Trong Consistent Hashing, khi bảng băm (hash table) thay đổi kích thước (ví dụ thêm một node và cluster), chỉ có “k/n” keys cần re-map với “k” là tổng số keys có trong hệ thống, và “n” là tổng số server. Có nghĩa là khi thay đổi kích thước của cluster thì có duy nhất keys trên một server cần phải re-map. Và khi một node bị xóa khỏi cluster, thì các data của node đó sẽ được di chuyển và chia sẻ với các node khác, và cũng tương tự khi một node được thêm vào cluster thì nó sẽ lấy tự động dữ liệu từ một vài node khác để chia sẻ tài nguyên.

Consistent Hashing hoạt động như thế nào?

Vậy làm thế nào để Consistent Hashing làm được những điều trên thì các Node trên cluster sẽ được lưu trữ dưới dạng vòng tròn bằng cách sử dụng hash function trong Consisten Hashing sẽ hash các key thành một một số nguyên (Integer) nằm trong một khoảng nào đó và các số nguyên đó sẽ được đặt trên một vòng tròn sao cho mỗi điểm trên vòng tròn sẽ tương ứng với một số nguyên trên dãy số nguyên đó.

Tóm lại các bước để Consistent Hashing băm dữ liệu như sau:

  1. Tiến hành hash các danh sách node trong cluster server thành một số nguyên trong một khoảng số được định nghĩa trước. Khoảng số này tùy vào người thiết kế hệ thống cân nhắc dựa trên số lượng Server tối đa mà hệ thống sẵn sàng scale up.
  2. Sau khi đã có danh sách mapping giữa các node, ta sẽ tiến hành mapping key của data tới các node bằng cách.
  • Hash giá trị của key thành một số nguyên (integer).
  • Di chuyển nó liên tục trong vòng tròn số nguyên trong khoảng đã tạo ở trên theo chiều kim đồng hồ cho tới khi giá trị integer đó bằng với giá trị hash key của một Node đầu tiên nó gặp. Tiến hành ghi dữ liệu của dữ liệu đó vào Node vừa gặp.
Hướng dẫn dùng distributed mysql trong PHP

Do đó khi tiến hành thêm một Node vào Cluster Server thì dữ liệu Node gần nó nhất theo chiều kim đồng hồ sẽ được chia sẻ với Node mới thêm vào.

Hướng dẫn dùng distributed mysql trong PHP

Tương tự như việc xóa một Node trên Cluster, khi cache miss sẩy ra, dữ liệu sẽ được di chuyển, lưu và lấy từ Node kế cận nhất với Node đã xóa theo chiều kim đồng hồ.

Hướng dẫn dùng distributed mysql trong PHP

Như vậy, Consistent Hashing đã giải quyết được vấn đề xáo trộn data cache khi scale hệ thống theo chiều ngang, đảm bảo sự xáo trộn cache chỉ xảy ra với một server.

Vấn đề về phân phối đều data trên từng Node.
Bên trên chúng ta cũng đã đề cập về vấn đề Load Balancing, với dữ liệu thật thì khả năng nhiều data key đều được hash vào một dãy giá trị lưu trên một Node nào đó, khiến Node đó trở thành điểm nóng (hot spot) so với các server còn lại. Dẫn đến việc lệch cân bằng tải và rủi ro nếu node hot spot gặp sự cố, gần như toàn bộ cache sẽ bị mất, dẫn tới cache miss hàng loạt.

Để giải quyết vấn đề này, chúng ta sẽ thực hiện hash và thêm nhiều các Node ảo (virtual node) vào vòng tròn. Và thay vì việc chỉ mapping mỗi Node vào một điểm trên vòng tròn, mỗi node ảo tượng trưng cho một dãy giá trị mà một Node thật đảm nhiệm. Có nghĩa là mỗi Node thật sẽ lưu trữ dữ liệu được ánh xạ trên nhiều Virtual Node. Điều này sẽ làm cho việc phân phối key sẽ cân bằng hơn, nó giống với việc tạo ra nhiều Node con hơn cho hash function trong Consistent Hashing băm “nhuyễn” hơn 😃

Ví dụ ta có 3 Node trên Cluster, ta sẽ tạo thêm 3 virtual Node tương ứng, lúc này vòng tròn sẽ chia thành 6 điểm như trên hình vẽ. Mỗi Virtual Node sẽ ánh xạ tới các Node thật, như trong hình là Virutal Node mầu đỏ 🔴 sẽ ảnh xạ tới Real Node mầu đỏ 🔴, Virutal Node mầu xanh nước biển 🔵 sẽ ánh xạ tới Real Node mầu xanh nước biển 🔵 , tương tự với Virtual Nod mầu xanh nhạt. Do vậy với Node 🔴 nó sẽ chứa dữ liệu của khoảng 1 và 4 Node 🔵 sẽ chứa dữ liệu của 2 và 5, tương tự Node còn lại sẽ chứa dữ liệu của 0 và 6.

Hướng dẫn dùng distributed mysql trong PHP

Consistent Hashing được ứng dụng rất rộng rãi nổi bật nhất là DynamoDB và Cassandra đã ứng dụng nó vào việc replicate dữ liệu giữa các Server Node của nó.

Tạo Index cho cơ sở dữ liệu

Có lẽ thuật ngữ “đánh index” đã quá quen với những ai làm việc với Database, đó là cách rất phổ biến để tăng tốc độ query của dữ liệu, khi dữ liệu Database ngày càng tăng và trở nên chậm dần đều theo thời gian.

Mục tiêu của việc tạo Index là để tăng tốc độ trả về dữ liệu của một hoặc nhiều trường (rows) trên một bảng (table) cụ thể nào đó bằng cách tạo Index trên một hoặc nhiều cột (columns) của một database table.

Để hiểu rõ hơn thế nào là Index ta hãy đến thử một nhà sách hay thư viện, thường các cuốn sách sẽ được phân chia theo các danh mục về nội dung như: sách nấu ăn, sách tiểu thuyết nước ngoài, sách tâm lý, sách lịch sử …

Nếu ta muốn tìm kiếm một loại sách theo nội dung mong muốn thì chỉ việc tới các kệ sách với nội dung tương ứng, nó sẽ nhanh hơn là tìm kiếm toàn bộ nhà sách.

Hoặc ví dụ khác về các phần mục lục trong muốn cuốn sách, nếu ta muốn tìm nhanh đến “chương” ta đang cần tìm kiếm hoặc đọc dở chỉ cần tra mục lục rồi tìm tới đúng trang chứa nội dung.

Index trong Database cũng giống như vậy, ví dụ ta có một table là Books chứa 4 columns là “BookTitle”, “Writer”, “Subject”, và “DateOfPublication”, thường thì khách hàng sẽ thường xuyên tìm kiếm sách theo hai tiêu chí là tên sách và tác giả, do đó ta sẽ tạo Index cho hai column là “BookTitle” và “Writer”.

Database sẽ tạo ra một data structure riêng biệt chứa hai giá trị của toàn bộ nội dung (content) các cột đánh index và một con trỏ (pointer) để trỏ tới dữ liệu thật sự đang nằm ở Database.

Như vậy, sử dụng index yêu cầu cần disk space để chứa cấu trúc của nó và Index cũng không làm thay đổi cấu trúc của table. Do vậy mỗi làm tìm kiếm dữ liệu thì Database sẽ tìm kiếm ở Index sau đó dựa vào con trỏ của Index để trả về dữ liệu thật.

Nhưng tại sao tìm kiếm trên Index lại nhanh hơn tìm kiếm trên Database, bởi vì Index luôn luôn sắp xếp dữ liệu để tối ưu nhất cho các thuật toán thực hiện việc tìm kiếm, còn dữ liệu Database thật thì luôn sắp xếp lộn xộn không có thứ tự nên không thuận tiện cho việc tìm kiếm. Mỗi Database sẽ có cách sắp xếp Index và thuật toán tìm kiếm Index khác nhau.

Hướng dẫn dùng distributed mysql trong PHP

Tuy nhiên Index cũng không phải là một magic keyword, việc đánh Index cần thật cẩn trọng,

  • Việc tạo Index sẽ tốn disk space, do đó chỉ nên đánh những cột dữ liệu có dung lượng nhỏ, và sẽ không có ý nghĩa gì nếu đánh Index cột contents kiểu CLOB chứa nội dung của một article vì lúc đó dữ liệu của Index sẽ to bằng nguyên cái table gốc.
  • Index thì ta cũng phải cần tạo ra nó, với một dữ liệu lớn sẵn rồi mà lúc này ta mới đánh Index thì việc tạo ra nó là một công việc rất tốn thời gian và tài nguyên hệ thống. Cho nên tốt nhất hãy lường trước ta tạo Index ngay từ khi dữ liệu còn nhỏ.
  • Việc dữ liệu được thêm mới sửa xóa (CUD) thường xuyên trên Table gốc thì Index cũng sẽ phải thêm mới sửa và sắp xếp lại, với một Table có dữ liệu lớn thì việc này cũng rất mất thời gian và nó sẽ làm chậm đi quá trình update hay create dữ liệu từ table gốc.

Do đó chỉ những Index thực sự cần thiết mới nên thêm vào và nên thường xuyên xem xét lại và xóa những Index không thực sự cần thiết. Và mục tiêu chính của Index đó là tăng khả năng đọc (read) của dữ liệu, do đó những Table dạng thường xuyên ghi nhưng ít khi được đọc thì tốt nhất không nên tạo Index, vì nó sẽ giảm hiệu xuất của việc ghi dữ liệu.

Tham khảo thêm về Index trong SQL Server ở bài viết sau:

Index trong SQL Server | Comdy

Tìm hiểu về index trong SQL Server để có một chiến lược tạo index tốt nhằm tối ưu hóa các truy vấn của bạn.

Hướng dẫn dùng distributed mysql trong PHP
Comdy

Hướng dẫn dùng distributed mysql trong PHP

Định lý CAP trong hệ thống phân tán

Định lý CAP nói rằng một hệ thống phân tán (distributed system) không thể thỏa mãn cả ba yếu tố CAP đó là:

  • Consistency: tính nhất quán, tất cả các node phải có dữ liệu đồng nhất với nhau.
  • Availability: tính sẵn sàng hoạt động của các node. Hệ thống có thể vẫn hoạt động được khi một số node bị chết hoặc không sẵn sàng.
  • Partition Fault Tolerance: trạng thái hoạt động của hệ thống khi đường kết nối (mạng) giữa các node bị đứt, hay còn gọi là khả năng chịu lỗi của hệ thống. Hệ thống vẫn phải hoạt động bình thường cho dù các kết nối của các node trong hệ thống bị đứt gãy.
Hướng dẫn dùng distributed mysql trong PHP

Chúng ta không thể thiết kế một hệ thống bao gồm cả ba yếu tố CAP, bởi vì đảm bảo tính C (consistency) tất cả các cập nhật dữ liệu phải được thực hiện trên các node cùng một thời điểm.

Nhưng nếu đường kết nối (mạng) giữa các node không được đảm bảo dẫn đến việc các node sẽ không được update dữ liệu cùng một thời điểm, điều này dẫn tới việc một vài node dữ liệu sẽ bị out-of-date do chưa được cập nhật dữ liệu từ đó vi phạm tính C (consistency).

Và để đảm bảo đối phó với điều này ta sẽ ngừng phục vụ nhữg node bị out-of-date đó cho tới khi nó được update dữ liệu đầy đủ, nhưng việc này lại vi phạm tính A (availability) của hệ thống.

Thông thường, người ta thường đánh đổi yếu tố C để lấy hai yếu tố A và P. Khi đó họ sẽ thay thế consistency thành eventually consistency (tính nhất quán sau cùng), làm như thế hệ thống sẽ có hiệu năng tốt hơn.

Distributed Transaction trong Distributed System

Distributed Transaction là vấn đề phải đối mặt rất nhiều trong xây dựng các hệ thống phân tán (Distributed System). Nó ảnh hưởng rất lớn tới độ ổn định của hệ thống. Nhưng đây lại là một vấn đề mà có thể vì điều kiện nên rất nhiều người không để ý khi bắt đầu xây dựng hệ thống.

Có hai nhóm giải pháp để giải quyết vấn đề Distributed Transaction trong hệ thống phân tán (Distributed System):

  • Two Phase Commit: đảm bảo tất cả các tính chất của một transaction.
  • Đảm bảo tính nhất quán sau cùng (Eventually Consistency): đảm bảo một số tính chất quan trọng của một transaction nhưng vẫn đảm bảo nghiệp vụ chạy đúng trong phần lớn các trường hợp.

Two Phase Commit

Two phase commit là giải pháp duy nhất đảm bảo các tính chất ACID của distributed transaction. Như tên gọi, quá trình thực thi giao dịch sẽ chia làm hai giai đoạn:

  • Giai đoạn một, Request Commit Phase: giai đoạn này từ client sẽ gửi lệnh ghi tới các resources. Đồng thời ghi client sẽ ghi log Undo và Redo.
  • Giai đoạn hai, Commint Phase: sau khi nhận được từ response ghi thành công từ tất cả các resources, thì từ client gửi lệnh Commit tới tất cả resources.
  • Nếu có bất cứ request nào thực thi bị lỗi thì sẽ tiến hành gửi lệnh undo tới tất cả các resources Qua đây, các bạn thấy có hai điểm chú ý:
  • Các resources phải hỗ trợ cơ chế lock trong quá trình chờ lệnh commit từ client
  • Client phải có khả năng phối hợp quá trình xử lý ở các resources khác nhau. Nó còn gọi là các cordinator. Hiện các RDBMS như MySQL, Oracle, SQL Server đều hỗ trợ thực hiện Distributed Transaction – XA qua giao thức two phase commit. Với Microsoft thì có công nghệ MSDTC – Microsoft Distributed Transaction Cordinator. Các framework web service theo chuẩn SOAP cũng hỗ trợ giao thức này. Các bạn có thể tìm kiếm thêm với các từ khóa liên quan ở trên, hoặc đọc thêm tại đây: https://en.wikipedia.org/wiki/Two-phase_commit_protocol Với phương pháp này, tất cả các tính chất ACID của giao dịch được đảm bảo. Nhưng nó sẽ đòi hỏi các resources phải bị lock trong quá trình xử lý. Điều này sẽ có tác động tới quá trình thiết kế hệ thống khi bạn phải cân bằng giữa ba tính chất CAP của hệ thống phân tán.

Đảm bảo tính nhất quán sau cùng (Eventually Consistency)

Như đã trình bày ở nguyên lý CAP, nếu đảm bảo khả năng Consistency thì một trong hai tính chất Availability (khả năng hoạt động của hệ thống khi một trong các node bị ngừng hoạt động) và Partion Fail Tolerance (khả năng hoạt động của hệ thống khi đường mạng giữa các node bị đứt) khó đảm bảo.

Do vậy để có hệ thống hoạt động ổn định cao, và hiệu năng lớn thì người ta thường hi sinh tính chất consistency. Như vậy khi thiết kế để giải quyết distributed transaction thì sẽ phải thiết kế sao cho việc không đảm bảo consistency không ảnh hưởng tới tính chính xác của nghiệp vụ.

Điều đó có nghĩa là sẽ có khoảng thời gian trạng thái giữa các node trong hệ thống không nhất quán.

Phương pháp lưu log kết quả giao dịch theo Transaction Id

Đây là phương pháp cơ bản được sử dụng rộng rãi. Theo đó khi thực hiện một giao dịch, client sẽ gửi đi một Transaction Id kèm theo. Server sẽ lưu log giao dịch, kết quả thực thi theo Transaction Id đó.

Nếu quá trình trả lại kết quả bị lỗi, client thực hiện lại giao dịch với cùng Transaction Id, thì server sẽ trả lại kết quả tương ứng với Transaction Id đã lưu log đó. Khi cần rollback thì cũng dựa trên Transaction Id và log để thực hiện các xử lý tương ứng.

Nhưng với cách thiết kế này thì cần lưu ý hai điểm:

  • Resources thực sự không bị lock, nên trong quá trình chờ kết quả trả về thì có thể resouces đã bị sửa đổi rồi. Do đó khi thiết kế cần lưu tâm, quản lý chặt chẽ các nghiệp vụ có giao dịch liên quan tới resouces tương ứng. Nếu tồn tại các giao dịch có độ tranh chấp cao, đòi hỏi tính chính xác lớn thì cách thiết kế này không đảm bảo. Trong trường hợp đó buộc phải cài đặt Two Phase Commit.
  • Việc rollback không đơn giản, nó phụ thuộc vào tính chất nghiệp vụ. Vì nhiều khi trong quá trình client bắt đầu rollback thì thực sự dữ liệu đã bị thay đổi bởi nghiệp vụ khác rồi. Do đó việc rollback lại có thể làm sai hoàn toàn nghiệp vụ, rất khó để truy vết lại. Vì vậy cần hết sức cẩn thận khi thực hiện rollback. Ưu điểm lớn nhất của phương pháp này chính là nó đảm bảo tính chất AP của hệ thống.

Sử dụng cặp queue Request và Response

Phương pháp lưu log trên đảm bảo tính nhất quán sau cùng (eventually consistency), nhưng nó chưa thật sự tốt trong trường hợp xử lý các sự cố: đường mạng bị đứt, rồi client server lúc sống lúc chết.

Ví dụ khi client gửi một request tới server xử lý, client gửi xong thì đường truyền bị đứt. Lúc này client nên làm gỉ? Gửi lại để nhận kết quả, hay gửi lệnh rollback cho server? Client không rõ là server có nhận được hay không, đã xử lý hay chưa xử lý, đã xử lý đúng hay sai?

Trong trường hợp đòi hỏi phải có tính chặt chẽ cao hơn thì có thể thiết kế hệ thống để giải quyết distributed transaction bằng hai queue là:

Queue Request: dùng để lưu các request gửi đi.

Queue Response: dùng để lưu các kết quả xử lý xong. Khi client cần thực hiện một request nào đó thì gửi một request vào queue request, Server sẽ nhận request từ queue request và xử lý, sau đó gửi kết quả vào queue response, Client sẽ lắng nghe queue response để nhận kết quả tương ứng. Bằng cách làm như vậy thì cả client và server không cần tồn tại tại cùng một thời điểm. Client gửi xong, client có thể chết, Server nhận xử lý xong và trả kết quả rồi chết… Kết quả quá trình xử lý vẫn được đảm bảo do lưu trữ trong queue. Ngoài các vấn đề của việc thiết kế eventually consistency thì có hai điểm cần lưu ý khi thiết kế dạng này là:

  • Do queue response có chứa rất nhiều kết quả của các giao dịch khác nhau được server trả về, nên client phải có cách lọc lấy chỉ response tương ứng với request gửi đi. Như RabbitMQ thì phải tạo queue tạm tương ứng với request gửi đi, khi server nhận được request sẽ nhận được tên queue mà mình sẽ trả lại kết quả. Với một số loại message queue khác Windows Service Bus thì nó có hỗ trợ fillter message theo Session Id. Khi đó chỉ cần filter response kết quả tương ứng với Transaction Id gửi đi là được.
  • Do các message queue chứa các request và response nên nó phải có độ ổn định cao, bằng không nếu nó có vấn đề dẫn tới mất dữ liệu request và response thì sẽ rất nguy hiểm. Không thể biết là có giao dịch nào đã xử lý, giao dịch nào chưa, kết quả các giao dịch ra sao… Việc phụ hồi hệ thống trong trường hợp sự cố sẽ rất đau đầu. Trên đây là một số giải pháp để giải quyết vấn đề distributed transaction. Không có giải pháp nào là tuyệt đối, cần phải hiểu bản chất của transaction, tính chất của các nghiệp vụ mà có sự lựa chọn cho phù hợp. Các giải pháp này đều đã được áp dụng từ lâu trong các hệ thống lớn trên thế giới, cũng như trong hệ thống mình xây dựng.

Kết Luận

Bài viết này đã giới thiệu với bạn 3 cách Sharding dữ liệu trong hệ thống phân tán (distributed system):

  • Horizontal sharding.
  • Vertical sharding.
  • Directory Based sharding

Các tiêu chi để sharding dữ liệu và những vấn đề cần lưu ý khi sharding dữ liệu.

Index, distributed transaction và định lý CAP trong hệ thống phân tán.

Nếu Comdy hữu ích và giúp bạn tiết kiệm thời gian

Bạn có thể vui lòng tắt trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi duy trì hoạt động của trang web.

Software Architecture • Distributed System • Distributed Transaction • Sharding • Index