Trong cơ sở dữ liệu phân tán các tập tin dữ liệu được lưu trữ độc lập trên các nút mạng máy tính và phải có liên quan đến nhau về mặt logic, và hơn thế nữa còn đòi hỏi chúng phải được tr
Trang 1HỆ CƠ SỞ DỮ LIỆU PHÂN TÁN
HÀ NỘI - 2012
Trang 2MỞ ĐẦU 1
Chương 1: Tổng quan về Riak 2
Chương 2: Phân tích ưu nhược điểm của Riak 21
Chương 3: Một số ứng dụng có thể dùng Riak 26
Chương 4: Tổng kết 28
Tài liệu tham khảo 29
Trang 3Hình 1: Vòng tròn cụm 3
Hình 2: Tổ chức dữ liệu trong Bitcask 4
Hình 3: Cấu trúc 1 entry trong Bitcask 5
Hình 4: Cấu trúc file dữ liệu trong Bitcask 5
Hình 5: Cấu trúc keydir trong Bitcask 6
Hình 6: Tiến trình hợp nhất dữ liệu trong Bitcask 7
Hình 7: Cơ chế tạo bản sao dữ liệu 8
Hình 8: Vector Clock Pruning 17
Trang 4MỞ ĐẦU
Hệ cơ sở dữ liệu phân tán được xây dựng dựa trên hai công nghệ cơ bản là cơ sở dữ liệu và mạng máy tính Hệ cơ sở dữ liệu phân tán được mô tả như là tập hợp nhiều cơ sở
dữ liệu có liên quan logic đến nhau và được phân bố trên mạng máy tính
Trong cơ sở dữ liệu phân tán các tập tin dữ liệu được lưu trữ độc lập trên các nút mạng máy tính và phải có liên quan đến nhau về mặt logic, và hơn thế nữa còn đòi hỏi chúng phải được truy xuất đến qua một giao diện chung, thống nhất
Riak là một hệ cơ sở dữ liệu phân tán mã nguồn mở, được tạo ra với nhiều tính năng mạnh Riak là sự lựa chọn tốt cho nhiều ứng dụng hiện đại của công nghệ thông tin ngày nay như các mạng xã hội, hệ thống lưu trữ file phân tán,…
Bài viết này giới thiệu tổng quan về mô hình thiết kế hệ thống Riak và phân tích một
số ưu nhược điểm của nó, cũng như nêu lên một số hướng ứng dụng có thể sử dụng Riak.Bài viết được chia làm bốn phần chính, dưới đây là tóm tắt nội dung của từng phần:
Chương 1: Trình bày tổng quan về Riak.
Chương 2: Phân tích ưu nhược điểm của Riak.
Chương 3: Một số ứng dụng có thể dùng Riak.
Chương 4: Tổng kết.
Trang 5Chương 1: Tổng quan về Riak
Riak là một hệ cơ sở dữ liệu phân tán Riak tổ chức dữ liệu trong các bucket, các khóa (key) và các giá trị (value) Mỗi bucket là nơi chứa và là không gian khóa cho dữ liệu Khái niệm bucket tương đương với khái niệm bảng trong cơ sở dữ liệu quan hệ Các giá trị (hay các đối tượng) được phân biệt bởi khóa, và mỗi cặp (khóa/giá trị) được lưu trong 1 bucket Riak được thiết kế dựa theo thuyết CAP của Eric Brewer Thuyết CAP
định nghĩa các hệ thống phân tán với 3 tính chất sau: Consistency (tính nhất quán),
Availability (tính sẵn sàng), và Partition tolerance (khả năng chịu lỗi) Thuyết này khẳng
định rằng chỉ có 2 tính chất được thỏa mãn tại 1 thời điểm bất kỳ Riak lựa chọn tập trung vào 2 tính chất A và P trong CAP Lựa chọn này có vẻ như sẽ khiến Riak không đảm bảo tính nhất quán của dữ liệu, tuy nhiên khoảng thời gian xảy ra sự không nhất quán chỉ đo bằng millisecond cũng là đủ tốt cho rất nhiều ứng dụng
Riak là hệ thống có tính sẵn sàng, tính scalability và khả năng chịu lỗi cao Sau đây chúng ta sẽ cùng tìm hiểu Riak được thiết kế như thế nào để đảm bảo được những mục tiêu vừa được đề cập ở trên
1.1 Cụm Riak
Mỗi cụm Riak là một không gian các số nguyên 160 bit, được mô hình hóa là một vòng tròn được chia thành các partition bằng nhau Mỗi máy chủ vật lý là một node, mỗi node chứa 1 hoặc nhiều node ảo được gọi là vnode Mỗi vnode sẽ phụ trách 1 partition trên vòng tròn cụm
Mỗi node trong hệ thống phụ trách 1/(tổng số node) vòng tròn Số vnode tại mỗi node sẽ được tính là (số partition / số node) Ví dụ, nếu vòng tròn cụm có 32 partition, có
4 node, thì số vnode trên mỗi node là 32/4 = 8 Cấu hình này được thể hiện thông qua hình vẽ dưới đây:
Trang 6Riak sử dụng giao thức gossip để chia sẻ và thông tin về trạng thái vòng tròn cụm,
và các thuộc tính của các bucket trong cụm Bất cứ khi nào một node thay đổi vai trò của
nó trên vòng tròn cụm (như thêm/bớt partition), nó thông báo sự thay đổi này qua giao thức gossip Mỗi node sẽ định kỳ (mặc định là 60 giây) gửi thông tin trạng thái hiện tại của vòng tròn cụm mà nó biết đến một số node được lựa chọn ngẫu nhiên
Giao thức gossip nhằm phát hiện các node bị mất kết nối với vòng tròn cụm, và tránh cho các request của client được chuyển hướng tới các node hỏng
Giao thức gossip làm tăng tính nhất quán của dữ liệu Nó phản ánh kịp thời node nào sẽ phụ trách partition nào khi trên vòng tròn cụm có sự thay đổi số node
Trang 71.3 Backend lưu trữ dữ liệu
Dữ liệu trong Riak có dạng key/value, nên Riak cần sử dụng một engine lưu trữ đặc biệt được tối ưu cho việc truy xuất dạng dữ liệu này
Riak sử dụng API để tương tác với hệ thống con lưu trữ dữ liệu API này cho phép Riak hỗ trợ nhiều loại backend, mà có thể đưa thêm vào hoặc gỡ ra khi cần Ngoài ra cũng có thể cấu hình sao cho mỗi bucket sử dụng một backend khác nhau, nhằm tối ưu việc lưu trữ đối với kiểu dữ liệu khác nhau Mặc định Riak dùng Bitcask làm backend cho việc lưu trữ dữ liệu, ngoài ra người dùng có thể chọn các backend khác như InnoStore.Bitcask là một engine lưu dữ liệu, được thiết kế để tối ưu hóa việc thao tác với dữ liệu dạng key/value Mô hình tổ chức dữ liệu trong Bitcask như sau:
Hình 2: Tổ chức dữ liệu trong Bitcask
Bitcask lưu dữ liệu trong 1 một thư mục Tại một thời điểm chỉ có một tiến trình được thực hiện thao tác ghi trong thư mục này Ở thời điểm bất kỳ, trong thư mục chỉ có một file ở trạng thái tích cực (active), và các thao tác ghi chỉ diễn ra trên file này Khi kích
cỡ file này đạt ngưỡng, nó sẽ được đóng lại, và 1 file tích cực khác được tạo ra Khi một file bị đóng lại thì nội dung của nó là bất biến, không thể bị thay đổi nữa, tức nó sẽ không bao giờ được mở lại để thực hiện thao tác ghi
Bitcask sử dụng cơ chế append khi ghi vào file tích cực, điều này không yêu cầu phải thực hiện tìm vị trí ghi trên đĩa (disk seeking), khiến thao tác ghi được diễn ra rất nhanh Mỗi entry có định dạng đơn giản như dưới đây:
Trang 8Hình 3: Cấu trúc 1 entry trong Bitcask
Khi ghi, một entry mới được thêm vào cuối file tích cực Việc xóa 1 entry chỉ đơn giản là ghi một entry chứa giá trị đặc biệt có khóa trùng với khóa của entry cần xóa Tiến trình hợp nhất (merge) dữ liệu được đề cập dưới đây sẽ xóa entry này đi Một file dữ liệu của Bitcask là một dãy tuyến tính các entry như hình sau:
Hình 4: Cấu trúc file dữ liệu trong Bitcask
Sau khi việc ghi dữ liệu vào file tích cực hoàn thành, một cấu trúc dữ liệu nằm trong
bộ nhớ trong có tên là “keydir” được cập nhật keydir là một bảng băm thực hiện ánh xạ mỗi khóa với một cấu trúc dữ liệu có kích thước cố định mô tả các thông tin về file, vị trí của entry trong file, timestamp Khi đã biết file id và vị trí của entry trong file, ta có thể
dễ dàng đọc được giá trị ứng với khóa
Trang 9Hình 5: Cấu trúc keydir trong Bitcask
Khi thao tác ghi xảy ra, bảng keydir được cập nhật vị trí của dữ liệu mới nhất Dữ liệu cũ vẫn tồn tại trên đĩa, nhưng mọi hành động đọc dữ liệu về sau sẽ sử dụng phiên bản mới nhất trong bảng keydir Tiến trình hợp nhất dữ liệu sẽ xóa những dữ liệu cũ đi Việc đọc dữ liệu được thực hiện cực kỳ đơn giản, chỉ cần một thao tác nhảy vị trí trong file Khi đọc dữ liệu, đầu tiên ta tra cứu khóa trong bảng keydir, tìm được file id và vị trí entry trong file, rồi tiến hành nhảy đến vị trí xác định trong file này và đọc dữ liệu ứng với khóa
Cơ chế lưu dữ liệu như trên khiến cho lượng dữ liệu được lưu tăng rất nhanh chóng,
do chúng ta chỉ ghi dữ liệu mới mà không động chạm đến dữ liệu cũ Tiến trình hợp nhất
dữ liệu sẽ giải quyết vấn đề này Tiến trình hợp nhất dữ liệu rà soát trên tất cả các file đang ở trạng thái không tích cực (in-active) và tạo ra một tập các file dữ liệu mới chỉ chứa các phiên bản “sống” hay các phiên bản mới nhất của dữ liệu ứng với các khóa hiện tại Tiến trình hợp nhất dữ liệu cũng tạo ra một file hint bên cạnh mỗi file dữ liệu mới File hint không chứa dữ liệu mà chỉ chứa các tham số định vị và đặc tả dữ liệu File hint cho phép việc tạo ra bảng keydir diễn ra nhanh chóng trong trường hợp ta cần tạo lại bảng này
từ 1 danh sách các file dữ liệu có sẵn, việc đọc file hint rõ ràng sẽ nhanh hơn nhiều so với việc đọc file dữ liệu có kích thước lớn
Trang 10Hình 6: Tiến trình hợp nhất dữ liệu trong Bitcask
Một số ưu điểm của việc sử dụng Bitcask
• Độ trễ khi đọc/ghi nhỏ
• Thông lượng lớn, đặc biệt khi cần ghi một luồng dữ liệu của những đối tượng ngẫu nhiên
• Có thể xử lý những tập dữ liệu có kích thước lớn hơn dung lượng của RAM
• Chống chịu lỗi tốt, khả năng phục hồi nhanh và không làm mất mát dữ liệu.Điều này có được nhờ sử dụng các file hint trong quá trình khởi động lại hệ thống bitcask để tạo lại bảng keydir
• Dễ dàng backup và phục hồi dữ liệu
Do các file dữ liệu có nội dung bất biến một khi đã được đóng lại và chuyển sang trạng thái không tích cực, nên việc backup dữ liệu rất đơn giản, chỉ việc copy các file này sang 1 ổ chứa khác Việc phục hồi dữ liệu cũng đơn giản và nhanh chóng, chỉ cần copy lại các file dữ liệu vào 1 thư mục định sẵn
• Định dạng dữ liệu đơn giản, và cấu trúc code sáng sủa, dễ hiểu
• Chịu được tải truy cập và lượng dữ liệu lớn
1.4 Hinted Handoff
Riak sử dụng kỹ thuật Hinted handoff để bù cho những node bị mất kết nối trên vòng tròn cụm Các node lân cận của node bị mất kết nối sẽ thực hiện phần việc của node
Trang 11này, cho phép hệ thống tiếp tục xử lý công việc như bình thường Điều này có thể được xem như một hình thức tự chữa bệnh.
1.5 Cơ chế tạo bản sao dữ liệu
Riak điều khiển số bản sao của dữ liệu thông qua việc thiết lập giá trị N (n_val) Giá trị này có thể được tùy chỉnh với mỗi bucket khác nhau Các đối tượng trong Riak kế thừa n_val từ bucket chứa nó Mọi node trong cùng 1 vòng tròn cụm dùng chung giá trị n_val Giá trị mặc định là n_val = 3, nghĩa là khi lưu 1 đối tượng dữ liệu trong 1 bucket, Riak sẽ tạo 3 bản sao của đối tượng này và lưu trên 3 partition khác nhau trên vòng tròn cụm Hình vẽ dưới đây mô phỏng cách Riak tạo bản sao dữ liệu với n_val = 3:
Hình 7: Cơ chế tạo bản sao dữ liệu
Việc tạo bản sao được thực hiện tự động trong Riak, điều này bảo đảm cho dữ liệu không bị mất nếu có node bị mất kết nối Tất cả dữ liệu trong Riak đều có bản sao được lưu ở trên một số node nhất định trong cụm, tùy thuộc vào giá trị n_val được thiết lập cho bucket
Chọn giá trị n_val như thế nào phụ thuộc vào ứng dụng và hình thái dữ liệu Nếu dữ liệu có tính tạm thời và có thể được tạo lại một cách dễ dàng bởi ứng dụng thì việc chọn giá trị n_val nhỏ giúp tăng hiệu suất đáng kể Nếu bạn cần đảm bảo dữ liệu luôn sẵn sàng
kể cả khi có node bị mất kết nối thì việc chọn giá trị n_val cao sẽ giúp ngăn ngừa mất dữ liệu Bạn hy vọng tại một thời điểm chỉ có bao nhiêu node bị mất kết nối? Hãy chọn giá trị n_val lớn hơn số node này và dữ liệu của bạn vẫn có thể truy cập được nếu số node này thực sự bị mất kết nối Giá trị n_val cũng tác động đến cách cư xử của các request đọc
Trang 12(GET) và ghi (PUT) Các tham số có thể tùy chỉnh thông qua các request này bị giới hạn bởi giá trị n_val Chằng hạn, nếu n_val = 3, giá trị R (Read Quorum) sẽ có giá trị lớn nhất
là 3 R là giá trị do client thiết lập nhằm đảm bảo tính nhất quán của dữ liệu Một request đọc dữ liệu với R = 2 sẽ yêu cầu 2 node phải trả về kết quả để thao tác đọc dữ liệu được xem là thành công Nếu giá trị R lớn hơn số node hiện tại có thể trả về dữ liệu thì thao tác đọc dữ liệu sẽ bị lỗi
Tiến trình Read Repair
Riak không khuyến khích việc thay đổi giá trị n_val sau khi bucket đã chứa dữ liệu Nếu n_val thay đổi, đặc biệt là khi ta tăng giá trị của nó, ta cần phải thực hiện tiến trình Read Repair
Read Repair xảy ra khi một thao tác đọc thành công, nhưng không phải tất cả các bản sao của đối tượng cần đọc đều khớp với nhau Tình huống này có thể xảy ra trong 2 trường hợp sau:
1) Có một node trả về thông điệp “not found”, nghĩa là nó không chứa bản sao của
dữ liệu Tiến trình Read Repair sẽ giải quyết vấn đề này Với mỗi đối tượng dữ liệu (hoặc
cả bucket) mà thao tác đọc bị lỗi, thực hiện đọc đối tượng đó với giá trị R nhỏ hơn hoặc bằng số bản sao ban đầu của đối tượng Chằng hạn với giá trị n_val ban đầu bằng 3 và giá trị hiện tại được tăng lên n_val = 5, tiến hành thao tác đọc với R = 3 hoặc nhỏ hơn Điều này sẽ khiến những node không chứa bản sao đối tượng trả về thông điệp “not found”, tức
sẽ khởi tạo 1 tiến trình Read Repair ngay sau đó
Khi n_val = 3 nghĩa là có 3 partition (vnode) khác nhau sẽ chứa những bản sao này Không có gì đảm bảo rằng 3 bản sao này sẽ được chứa trên 3 node vật lý khác nhau, tuy
Trang 13nhiên Riak sẽ thực hiện chia các bản sao về các partition một cách công bằng Khi có node được thêm vào hay bị đưa ra khỏi vòng tròn cụm, sự phân bổ các partition về các node bị thay đổi và có thể khiến cho sự phân bố dữ liệu bị lệch Riak sẽ tự động phân chia lại các partition về các node để đảm bảo cân bằng tải Trong trường hợp số node nhỏ hơn giá trị n_val, dữ liệu sẽ bị trùng lặp trên một số node Ví dụ, với n_val = 3, và có 2 node trên vòng tròn cụm, sẽ có ít nhất một node chứa 2 bản sao dữ liệu.
Ví dụ minh họa cơ chế tạo bản sao
Để hiểu rõ về cơ chế tạo bản sao, ta xét 1 ví dụ về 1 put request có cặp khóa dữ liệu bucket-key là <<"my_bucket">>/<<"my_key">> gửi đến 1 hệ thống Riak bao gồm 3 node,
8 partition, và n_val=3
Mỗi partition được biểu diễn bởi 1 cặp sau: { partition_ identifier , parent_node }
Giả sử vòng tròn cụm sẽ có 8 partition như sau:
dự trữ, phòng khi có một số partition được chọn không sẵn sàng Node A gửi 1 message đến mỗi node cha của các partition được chọn, message này chứa Object và định danh partition:
'dev1@127.0.0.1' ! { put, Object, 1096126227998177188652763624537212264741949407232 }
Trang 14'dev2@127.0.0.1' ! { put, Object, 1278813932664540053428224228626747642198940975104 }
'dev1@127.0.0.1' ! { put, Object , }
Nếu một trong số các partition được chọn không sẵn sàng, node A sẽ gửi object đến một partition dự phòng Khi message được gửi đến node dự phòng, message tham chiếu object và định danh partition ban đầu Chẳng hạn, nếu node ‘dev2@127.0.0.1’ không sẵn sàng, node A sẽ cố gắng thử từng node dự phòng
Giả sử node dự phòng sẵn sàng là ‘dev3@127.0.0.1’ Node A sẽ gửi một message tới node dự phòng này với object và định danh partition ban đầu:
'dev3@127.0.0.1' ! { put , Object , 1278813932664540053428224228626747642198940975104 }
Lưu ý rằng giá trị định danh partition trong message này giống với giá trị định danh trong message đã được gửi cho node ‘dev2@127.0.0.1’ trước đó Mặc dù
‘dev3@127.0.0.1’ không phải là node cha của partition đó nhưng nó hiểu rằng nó đang giữ hộ object cho đến khi node ‘dev2@127.0.0.1’ hoạt động bình thường trở lại
1.6 Xử lý các request
Việc xử lý các request tại mỗi partition là khá đơn giản Mỗi node chạy một tiến
trình (process) riak_kv_vnode_master thực hiện điều phối các request đến các tiến trình
riak_kv_vnode trên các partition Tiến trình riak_kv_vnode_master duy trì một danh sách
các định danh của các partition và các tiến trình tương ứng trên các partition Nếu một tiến trình tương ứng với 1 partition không tồn tại, một tiến trình mới được sinh ra để quản
lý partition này
Tiến trình riak_kv_vnode_master xử lý các request bình đẳng và tạo ra các tiến trình
tương ứng với các partition khi cần, thậm chí trong cả trường hợp node hiện tại không quản lý partition là đích của các request mà node nhận được Khi node cha của partition chưa sẵn sàng, các request được gửi đến các node dự phòng (handoff) Tiến trình
riak_kv_vnode_master trên node dự phòng tạo ra một tiến trình để quản lý partition mặc
dù partition này không thuộc về node dự phòng hiện thời
Mỗi tiến trình quản lý partition sẽ thực hiện chức năng hometest xuyên suốt vòng đời của nó Hometest thực hiện kiểm tra xem node hiện tại có phải là node cha của partition (được định nghĩa trên vòng tròn cụm) không Nếu tiến trình biết được partition A
Trang 15mà nó đang quản lý thuộc về một node khác, nó sẽ cố gắng liên lạc với node đó Nếu node cha kia trả lời, tiến trình sẽ đẩy toàn bộ các đối tượng dữ liệu mà nó đã xử lý liên quan đến partition A cho node cha kia và kết thúc thực thi Nếu node cha kia không trả lời, tiến trình sẽ tiếp tục quản lý partition A và kiểm tra node cha của partition A sau một khoảng thời gian trễ nào đó Hometest cũng được chạy để tính toán sự thay đổi trên vòng tròn cụm khi có node được thêm vào hay bị đưa ra.
Riak có thể trả về các đối tượng dựa trên các liên kết (link) được chứa trong đối tượng Kỹ thuật này được gọi là Link-walking Link-walking có thể được dùng để trả về một tập các đối tượng có liên quan với đối tượng gốc bằng chỉ một request Chi tiết về Link-walking được trình bày ở mục 1.9
Ghi dữ liệu
Mỗi cập nhật cho một đối tượng dữ liệu được đánh dấu bằng 1 vector clock Các vector clock cho phép Riak xác định thứ tự thao tác và phát hiện các xung đột trong 1 hệ thống phân tán
Riak sử dụng 2 cách để giải quyết các xung đột trong việc cập nhật các đối tượng dữ liệu: cho phép lần cập nhật gần nhất giành chiến thắng hoặc trả về cho client tất cả các phiên bản của đối tượng dữ liệu Điều này tạo cho client cơ hội tự nó giải quyết xung đột.Riak cho phép client thiết lập 1 giá trị W cho mỗi cập nhật Giá trị W xác định số node phải báo cáo cập nhật thành công để 1 thao tác cập nhật được xem là hoàn thành (N-W) là số node có thể bị mất kết nối mà cụm Riak vẫn có thể cho phép thao tác ghi được đáp ứng
Trang 161.7 Giải quyết xung đột
Với việc mọi node đều có thể nhận và xử lý các request, cần có một phương pháp để kiểm tra phiên bản nào của dữ liệu là mới nhất Khái niệm vector clock được sử dụng để giải quyết vấn đề này Khi một đối tượng dữ liệu được lưu trong Riak, nó được đánh dấu bằng 1 vector clock Sau mỗi lần cập nhật dữ liệu, vector clock này cũng được cập nhật sao cho Riak có thể so sánh được 2 phiên bản khác nhau của đối tượng dữ liệu và có khả năng xác định những điều sau:
• Một đối tượng dữ liệu có phải là con cháu trực tiếp của 1 đối tượng dữ liệu khác hay không
• Các đối tượng dữ liệu có chung 1 đối tượng dữ liệu cha hay không
• Các đối tượng dữ liệu không nằm trên cùng 1 cây phả hệ
Sử dụng kiến thức trên, Riak có khả năng tự động sửa chữa những dữ liệu không được đồng bộ, hoặc ít nhất là nó có thể cung cấp cho client cơ hội để tự lựa chọn phiên bản dữ liệu phù hợp
Cấu trúc của 1 vector clock là một danh sách các cập nhật từ mỗi client id
1) Các thao tác ghi xảy ra đồng thời: Khi có 2 thao tác ghi xảy ra đồng thời từ những client có ID khác nhau nhưng có cùng giá trị vector clock, Riak sẽ không thể xác định được đối tượng dữ liệu nào là đúng và hệ thống sẽ tạo ra 2 sibling cho đối tượng Những thao tác ghi này có thể xảy ra trên cùng 1 node hay trên những node khác nhau
2) Sử dụng giá trị vector clock sai: Xảy ra khi các thao tác ghi từ một client sử dụng 1 vector clock sai Giả sử client A thực hiện một request đọc để lấy giá trị vector clock hiện thời trước khi thực hiện thao tác ghi Tuy nhiên, nếu có một