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.
Có 3 cách thức phân chia (Sharding) dữ liệu như sau:
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.
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).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:
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.
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à:
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 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.
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:
key
của data tới các node bằng cách.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.
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ồ.
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.
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ó.
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.
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,
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.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:
Đị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à:
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 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 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:
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:
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à:
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):
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.
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.
Caching là cách giúp sử dụng tài nguyên hiệu quả hơn từ đó giảm chi phí của hệ thống phân tán (Distributed System).
Load Balancing là gì? Load Balancing đóng vai trò như thế nào trong hệ thống phân tán (Distributed System).
Hệ thống phân tán (Distributed System) là gì? Vì sao các hệ thống của doanh nghiệp lớn lại sử dụng Distributed System?
Series bài viết trình bày chi tiết các thành phần trong Distributed System và cách triển khai.