Tải bản đầy đủ (.doc) (32 trang)

TIỂU LUẬN MÔN CƠ SƠ DỮ LIỆU NÂNG CAO HỆ CƠ SỞ DỮ LIỆU PHÂN TÁN RIAK

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (494.74 KB, 32 trang )

eo
ĐẠI HỌC QUỐC GIA HÀ NỘI
TRƯỜNG ĐẠI HỌC CÔNG NGHỆ
HỆ CƠ SỞ DỮ LIỆU PHÂN TÁN
RIAK
BÀI TẬP LỚN MÔN CƠ SỞ DỮ LIỆU NÂNG CAO
Ngành: Công nghệ thông tin
Khóa: 18
Cán bộ hướng dẫn: PGS.TS Nguyễn Hà Nam
Nhóm 1:
Nguyễn Thanh Tịnh
Vương Đình Dũng
HÀ NỘI - 2012
Mục lục
1
MỞ ĐẦ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
Danh sách các hình vẽ
Hì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
Hệ cơ sở dữ liệu phân tán Riak


MỞ ĐẦ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 1
Hệ cơ sở dữ liệu phân tán Riak
Chươ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 2
Hệ cơ sở dữ liệu phân tán Riak
Hình 1: Vòng tròn cụm
Khi có node mới được thêm vào hoặc đưa 1 node ra khỏi vòng tròn cụm thì Riak sẽ
thực hiện phân bổ lại dữ liệu trên mỗi node một cách tự động.
Tất cả các node trong cụm là bình đẳng, không tồn tại khái niệm master node. Mỗi
node có đầy đủ khả năng để đáp ứng các request từ client.
1.2. Giao thức Gossip
Riak 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 3
Hệ cơ sở dữ liệu phân tán Riak
1.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 4
Hệ cơ sở dữ liệu phân tán Riak
Hì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 5
Hệ cơ sở dữ liệu phân tán Riak
Hì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 6
Hệ cơ sở dữ liệu phân tán Riak
Hì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 7
Hệ cơ sở dữ liệu phân tán Riak
nà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 8
Hệ cơ sở dữ liệu phân tán Riak
(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.
2) Một node trả về bản sao với vector clock bị cũ so với vector clock của một thao
tác đọc đúng. Khái niệm vector clock sẽ được bàn chi tiết ở mục 1.7.
Khi 1 trong 2 trường hợp này xảy ra, Riak sẽ bắt những node trả về dữ liệu không
đúng phải cập nhật lại dữ liệu của mình dựa trên dữ liệu của thao tác đọc đúng.

Khi bạn tăng giá trị n_val của một bucket, bạn có thể khiến cho các thao tác đọc dữ
liệu sắp tới bị lỗi, đặc biệt là khi giá trị R được sử dụng lớn hơn số bản sao ban đầu 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 9
Hệ cơ sở dữ liệu phân tán Riak
nhiê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:
[{0,''},
{182687704666362864775460604089535377456991567872, ''},
{365375409332725729550921208179070754913983135744, ''},
{548063113999088594326381812268606132370974703616, ''},
{730750818665451459101842416358141509827966271488, ''},
{913438523331814323877303020447676887284957839360, ''},

{1096126227998177188652763624537212264741949407232, ''},
{1278813932664540053428224228626747642198940975104, ''}]
Khi 1 node A nhận được request này, nó sẽ băm khóa bucket-key và thu được 1 số
nguyên 160 bit, giả sử tên là DocIdx:
DocIdx = riak_core_util:chash_key({<<"my_bucket">>, <<"my_key">>})
1045375627425331784151332358177649483819648417632
Node A tìm kiếm các partition dựa trên giá trị DocIdx này trên vòng tròn cụm và thu
được 1 danh sách các partition phù hợp, gọi là danh sách preflist. Node A chỉ chọn
n_val=3 partition đầu tiên từ danh sách này, các partition còn lại được đưa vào danh sách
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:
'' ! {put, Object, 1096126227998177188652763624537212264741949407232}
Trang 10
Hệ cơ sở dữ liệu phân tán Riak
'' ! {put, Object, 1278813932664540053428224228626747642198940975104}
'' ! {put, Object, 0}
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 ‘’ 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à ‘’. 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:
'' ! {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 ‘’ trước đó. Mặc dù
‘’ 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 ‘’ 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 11
Hệ cơ sở dữ liệu phân tán Riak
mà 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.
Đọc dữ liệu
Các đối tượng dữ liệu có thể được lấy về trực tiếp nếu client biết bucket và khóa.
Đây là cách nhanh nhất để dữ liệu được lấy ra từ hệ thống Riak.
Riak cho phép client thiết lập một giá trị R trên mỗi request lấy dữ liệu. Giá trị R xác
định số node phải trả kết quả về cho một thao tác đọc dữ liệu để thao tác đọc này được
xem là thành công. (N-R) là số node có thể bị mất kết nối nhưng cụm Riak vẫn có thể
chấp nhận để tiếp tục đáp ứng các request đọc dữ liệu.
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 12
Hệ cơ sở dữ liệu phân tán Riak
1.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.
Ví dụ: [{client1,3,tstamp},{client2,1,tstamp},{client3,2,tstamp}]
Vector clock trên chỉ ra rằng client1 đã cập nhật đối tượng 3 lần, client 2 cập nhật
đối tượng 1 lần, và client3 cập nhật 2 lần.
Sibling
Một sibling được tạo ra khi Riak không thể xác định được phiên bản đúng của đối
tượng dữ liệu. Nếu bucket có thuộc tính allow_mult được thiết lập là true, sibling sẽ được
tạo ra trong 3 trường hợp sau:
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
Trang 13
Hệ cơ sở dữ liệu phân tán Riak
hành động ghi xảy ra từ một client B sau khi thao tác đọc từ client A hoàn
thành nhưng trước khi thao tác ghi từ client A được thực hiện, thì sẽ dẫn đến
giá trị vector clock được dùng trong thao tác ghi từ client A bị sai. Khi đó một
sibling cho đối tượng sẽ được tạo ra.
3) Thao tác ghi thiếu vector clock: Trường hợp này xảy ra khi có các thao tác cập
nhật lên một đối tượng dữ liệu sẵn có mà không sử dụng vector clock.
Riak sử dụng các sibling bởi nó không thể sắp thứ tự các sự kiện liên quan đến thời
gian trong một hệ thống phân tán. Nếu allow_mult = true, khi có xung đột xảy ra, Riak sẽ
không giải quyết nó giúp bạn mà bạn sẽ phải tự lựa chọn một sibling phù hợp hoặc thay
thế đối tượng dữ liệu cũ bằng một đối tượng dữ liệu khác.
Ví dụ dưới đây sẽ tạo ra 2 sibling:
# create a bucket with allow_mult true (if its not already)

$ curl -v -X PUT -H "Content-Type: application/json" -d '{"props":{"allow_mult":true}}'
\
http://127.0.0.1:8098/riak/kitchen
# create an object we will create a sibling of
$ curl -v -X POST -H "Content-Type: application/json" -d '{"dishes":11}' \
http://127.0.0.1:8098/riak/kitchen/sink?returnbody=true
# the easiest way to create a sibling is update the object without
# providing a vector clock in the headers
$ curl -v -X PUT -H "Content-Type: application/json" -d '{"dishes":9}' \
http://127.0.0.1:8098/riak/kitchen/sink?returnbody=true
Có thể lấy danh sách các sibling của đối tượng dữ liệu trong ví dụ này về thông qua
lệnh:
$ curl http://127.0.0.1:8098/riak/kitchen/sink
Khi đó Riak sẽ trả về 1 danh sách các vtag, mỗi vtag là 1 khóa duy nhất, giúp phân
biệt các sibling. Client sẽ dùng vtag để lấy sibling tương ứng về. Trong ví dụ này,
message mà client nhận được sẽ có định dạng như sau:
Trang 14
Hệ cơ sở dữ liệu phân tán Riak
Siblings:
175xDv0I3UFCfGRC7K7U9z
6zY2mUCFPEoL834vYCDmPe
Có thể lấy sibling tương ứng với vtag đầu tiên thông qua lệnh:
$ curl http://127.0.0.1:8098/riak/kitchen/sink?vtag=175xDv0I3UFCfGRC7K7U9z
Khi đó Riak sẽ trả về sibling sau: {"dishes":9}
Client cũng có thể lấy về tất cả các sibling chỉ với 1 request nếu header của request
là "Accept: multipart/mixed".
Giải quyết xung đột
Khi có nhiều giá trị để lựa chọn cho một đối tượng dữ liệu, bạn cần xác định giá trị
nào là đúng. Điều này có thể được thực hiện tự động bởi ứng dụng hoặc do người dùng
quyết định. Để cập nhật giá trị đúng cho đối tượng, bạn cần sử dụng giá trị vector clock

hiện thời. Giả sử {"dishes":11} là giá trị đúng, quá trình cập nhật lại dữ liệu có thể được
thực hiện như sau:
Lấy giá trị vector clock hiện tại:
$ curl -v http://127.0.0.1:8098/riak/kitchen/sink
Giả sử hệ thống Riak trả về như sau:
<X-Riak-Vclock:
a85hYGBgzmDKBVIsTFUPPmcwJTLmsTIcmsJ1nA8qzK7HcQwqfB0hzNacxCYWcA1ZIg
sA>
Với giá trị vector clock này, có thể cập nhật lại giá trị cho đối tượng dữ liệu như sau:
$ curl -v -X PUT -H "Content-Type: application/json" -d '{"dishes":11}' \
-H "X-Riak-Vclock:
a85hYGBgzmDKBVIsTFUPPmcwJTLmsTIcmsJ1nA8qzK7HcQwqfB0hzNacxCYWcA1ZIg
sA=" http://127.0.0.1:8098/riak/kitchen/sink?returnbody=true
Cần lưu ý là nếu chương trình ứng dụng cố gắng giải quyết xung đột một cách tự
động, sẽ có khả năng có 2 client đồng thời giải quyết xung đột và sẽ tạo ra những xung
đột mới. Để tránh xảy ra điều này, ứng dụng cần có cơ chế giới hạn số client được giải
quyết xung đột.
Trang 15
Hệ cơ sở dữ liệu phân tán Riak
Ta xét trường hợp sử dụng thiết lập allow_mult=false, và last_write_wins=true cho
bucket. Thiết lập allow_mult=false sử dụng vector clock để giải quyết xung đột, còn
last_write_wins=true chỉ đơn giản đọc timestamp để xác định phiên bản mới nhất. Sâu
hơn chút nữa, allow_mult=false vẫn cho phép các sibling tồn tại khi chúng đã được tạo ra,
còn last_write_wins=true sẽ chỉ dùng phiên bản có timestamp mới nhất để ghi dữ liệu.
Trong những trường hợp mà các khóa được ghi lại một cách thường xuyên (và nhanh) và
giá trị mới không nhất thiết phụ thuộc vào giá trị cũ, last_write_wins sẽ đem đến hiệu suất
tốt hơn. Các trường hợp bạn có thể muốn sử dụng last_write_wins là caching, session
storage, và insert-only (không có cập nhật).
Vấn đề bùng nổ số lượng sibling
Số lượng sibling của một đối tượng bùng nổ khi có nhiều thao tác ghi xảy ra trên đối

tượng mà không có cơ chế hòa giải xung đột. Điều này có thể dẫn đến vô số vấn đề tồi tệ
trên hệ thống. Việc có một đối tượng khổng lồ trên 1 node có thể khiến cho những thao
tác đọc đối tượng này làm cho node xảy ra sự cố. Các vấn đề khác có thể tính đến như
làm tăng độ trễ khi truy cập dữ liệu hay các lỗi tràn bộ nhớ.
Vấn đề bùng nổ dung lượng vector clock
Bên cạnh vấn đề bùng nổ số lượng sibling, độ lớn vector clock cũng có thể tăng lên
rất lớn khi có một lượng lớn các thao tác cập nhật xảy ra trên 1 đối tượng dữ liệu trong 1
khoảng thời gian ngắn. Riak sử dụng kỹ thuật Vector clock pruning để ngăn ngừa dung
lượng vector clock tăng lên quá nhanh. Ta sẽ nói thêm về kỹ thuật này bên dưới.
Vector Clock Pruning
Riak dùng kỹ thuật Vector Clock Pruning để ngăn ngừa sự gia tăng đột biến của
dung lượng các vector clock. Kỹ thuật này sử dụng 4 tham số sau:
• small_vclock
• big_vclock
• young_vclock
• old_vclock
Trang 16
Hệ cơ sở dữ liệu phân tán Riak
Hai tham số small_vclock và big_vclock liên quan đến độ dài của danh sách các
entry trong vector clock. Nếu độ dài của danh sách nhỏ hơn giá trị small_vclock, vector
clock sẽ không bị tỉa bớt. Nếu độ dài lớn hơn giá trị big_vclock nó sẽ bị tỉa bớt.
Các tham số young_vclock và old_vclock liên quan đến timestamp của mỗi entry.
Nếu độ dài danh sách nằm giữa 2 giá trị small_vclock và big_vclock, tuổi của từng entry
sẽ được kiểm tra. Nếu entry trẻ hơn giá trị young_vclock, vector clock sẽ không bị tỉa bớt.
Nếu entry già hơn giá trị old_vclock, vector clock sẽ bị tỉa bớt.
Hình 8: Vector Clock Pruning
1.8. Map-Reduce
Map-Reduce trong Riak cho phép xử lý dữ liệu theo thời gian thực và xử lý song
song. Các job của Map-Reduce được chứa trong định dạng JSON mô tả các input, các
phase, và thời gian timeout của 1 job. Một job có thể bao gồm một số tùy ý các Map

phase hay Reduce phase. Vì thế mà Map-Reduce trong Riak có thể được xem như một hệ
thống Hadoop con.
Job của Map-Reduce có thể được viết bằng Javascript và gửi đi thông qua HTTP,
điều này rất thuận tiện cho những nhà phát triển Web Service.
Map-Reduce trong Riak còn có một ý nghĩa khác, đó là làm tăng tính địa phương
hóa cho dữ liệu. Khi xử lý một tập dữ liệu lớn, việc đưa code tính toán đến nơi chứa dữ
liệu sẽ hiệu quả hơn việc đưa dữ liệu đến nơi chứa code tính toán. Trên thực tế, job của
Map-Reduce thường có code nhỏ hơn 10 KB, sẽ là hiệu quả hơn khi đưa 10KB code đến
Trang 17
Hệ cơ sở dữ liệu phân tán Riak
nơi chứa dữ liệu để thực hiện việc xử lý, hơn là việc đưa hàng GB dữ liệu đến nơi chứa
10KB code. Đây là giải pháp của Riak cho vấn đề địa phương hóa dữ liệu, theo đó Riak
trải rộng việc xử lý trên toàn cụm. Theo cùng cách điều phối 1 thao tác đọc hay ghi, mỗi
node cũng có thể điều phối 1 truy vấn Map-Reduce bằng cách gửi một request map-step
trực tiếp đến node chứa dữ liệu input. Kết quả của map-step được gửi ngược trở lại node
điều phối, và việc xử lý reduce-step tại đây sẽ tạo ra một kết quả hợp nhất. Có thể hiểu
đơn giản hơn như sau: Riak chạy các hàm map-step tại node chứa dữ liệu input của những
hàm này, và Riak chạy các hàm reduce-step tại node điều phối truy vấn Map-Reduce.
Mỗi truy vấn Map-Reduce trong Riak có 2 thành phần: danh sách các input và danh
sách các step (hay danh sách các phase). Mỗi phần tử trong danh sách input là một cặp
khóa dữ liệu bucket-key. Khóa này sẽ được truyền làm tham số của hàm map - hàm sẽ
thực hiện tính toán trên đối tượng dữ liệu có khóa là cặp bucket-key này.
Mỗi phần tử trong danh sách các phase là một mô tả của một hàm map, hàm reduce,
hay một hàm link. Mô tả này bao gồm: nơi chứa code của các hàm phase (map phase hay
reduce phase), dữ liệu static được truyền vào hàm mỗi khi hàm được thực thi trong suốt
phase, và một cờ (flag) chỉ thị các kết quả của phase có được bao gồm trong kết quả cuối
cùng của truy vấn hay không. Danh sách phase mô tả chuỗi các hành động mà đầu ra của
phase này là đầu vào của phase tiếp theo, trong đó phase đầu tiên có đầu vào là các giá trị
input ban đầu.
Map phase

Danh sách input của một map phase phải là một danh sách các cặp khóa dữ liệu
bucket-key. Với mỗi khóa, Riak sẽ gửi request ước lượng hàm map đến partition A chứa
đối tượng dữ liệu có khóa là khóa này. Vnode đảm nhận partition A sẽ tìm đối tượng dựa
theo khóa, và ước lượng hàm map với đối tượng tìm được là tham số. Các tham số khác
của hàm map có thể là dữ liệu static của phase được xác định trong truy vấn.
Việc tìm dữ liệu được xử lý song song và tỉ lệ với số node trong vòng tròn cụm.
Riak ghi nhớ kết quả của việc tìm dữ liệu nhằm giảm tải cho storage backend.
Link phase
Trang 18
Hệ cơ sở dữ liệu phân tán Riak
Link phase là phiên bản đặc biệt của map phase. Một link phase tìm các đối tượng
dựa vào link-walking. Các link phase có thể được dùng để thực hiện xử lý map/reduce
trên tập các đối tượng liên quan nhau.
Reduce phase
Reduce phase có thể thực hiện xử lý tùy ý trên dữ liệu nhận được từ map phase hoặc
link phase. Không giống CouchDB, Reduce phase của Riak không được yêu cầu chỉ trả
về một câu trả lời duy nhất.
1.9. Link-walking
Link-walking là một trong những kỹ thuật nổi bật nhất của Riak.
Các link là metadata thiết lập các mối quan hệ một chiều giữa các đối tượng dữ liệu
trong Riak. Link cho phép bạn thực hiện những truy vấn lần theo (walk) các mối quan hệ
từ một đối tượng đến một đối tượng khác. Với link, bạn có thể tạo ra các con trỏ (pointer)
giữa các dữ liệu của bạn, chẳng hạn, con trỏ trỏ từ ‘projects’ đến ‘milestones’, rồi từ
‘milestones’ đến ‘tasks’, sau đó lấy dữ liệu dọc theo cây sử dụng các lệnh từ client API.
Link là một đặc trưng rất mạnh của Riak.
Các thao tác đọc và cập nhật link được thực hiện thông qua sử dụng HTTP Link
header. Header này mô phỏng ý nghĩa của các thẻ <link> trong HTML, đó là tạo ra mối
quan hệ đến các tài nguyên HTTP khác. Riak sử dụng định dạng khai báo link như sau:
Link: </riak/bucket/key>; riaktag= "tag"
Bên trong cặp dấu (<,>) là đường dẫn URL tương đối đến đối tượng dữ liệu khác

trong Riak. “tag” là chuỗi định danh do ứng dụng đặt cho link này.
Một đối tượng có thể có nhiều link, phân cách nhau bởi dấu phẩy. Ví dụ, nếu một
đối tượng là một phần tử của 1 danh sách liên kết kép, nó có thể có link như sau:
Link: </riak/list/1>; riaktag="previous", </riak/list/3>; riaktag="next"
Link-walking là một trường hợp đặc biệt của truy vấn Map-Reduce, và có thể được
thực hiện thông qua HTTP. Quá trình này bắt đầu từ một đối tượng dữ liệu đầu vào, lần
theo các link của đối tượng đó để tìm những đối tượng phù hợp với những đặc tả xác
định.
Trang 19
Hệ cơ sở dữ liệu phân tán Riak
Một truy vấn dùng Link-walking được gửi qua HTTP có cấu trúc như sau:
GET /riak/buckets/keys/[bucket],[tag],[keep]
Trong đó:
- “/riak” là gốc của URL namespace.
- buckets là tên bucket của đối tượng gốc.
- keys là khóa của đối tượng gốc.
- Có thể có nhiều bộ ba [bucket],[tag],[keep] trên 1 truy vấn. trong đó:
+ bucket: chỉ xét các link trong bucket này.
+ tag: chỉ xét các link có tag này.
+ keep: 1 hoặc 0. Chỉ ra có trả về kết quả tại phase này hay không.
+ Dấu gạch dưới (_) biểu thị mọi giá trị đều phù hợp.
Trang 20
Hệ cơ sở dữ liệu phân tán Riak
Chương 2: Phân tích ưu nhược điểm của Riak
Trong chương này chúng ta sẽ cùng phân tích một số ưu nhược điểm của hệ cơ sở
dữ liệu phân tán Riak.
2.1. Những ưu điểm của Riak
Chống chịu lỗi tốt
Một trong những chức năng hấp dẫn nhất của Riak là nó có khả năng chống chịu lỗi
mạnh mẽ. Nếu một node bị mất kết nối, các node lân cận sẽ tạm thời đảm đương nhiệm vụ

của node này cho đến khi nó được kết nối trở lại hệ thống, đây là kỹ thuật Hinted handoff.
Nếu node đó không kết nối lại được, người dùng có thể bổ sung 1 node mới và vòng tròn
cụm sẽ tự động thực hiện cân bằng tải.
Riak được phát triển dựa trên một nền tảng tốt. Riak được viết dựa trên Erlang - với
nguyên lý thiết kế OTP (Open Telecommunications Platform). Một khía cạnh của OTP là
các tiến trình trong hệ thống được tổ chức trên 1 cây giám sát. Khi 1 tiến trình bị chết, nó
sẽ được khởi động lại từ tiến trình giám sát của nó.
Tính sẵn sàng và tính Scalability cao
Điều này có được là do Riak là một phiên bản cài đặt của Amazon Dynamo.
Nếu cần nhiều dung lượng lưu trữ hơn, thông lượng lớn hơn hay có tính sẵn sàng
hơn, chỉ cần thêm node vào vòng tròn cụm Riak.
Điều gì khiến cho Riak có khả năng mở rộng xử lý hơn MongoDB, CouchDB hay
MySQL? Đó là nó được xây dựng ngay từ đầu với khái niệm không có bản sao chính.
Mọi node có vai trò bình đẳng trong cụm.
Hoàn toàn địa phương hóa dữ liệu
Không có master node. Mỗi node đều có khả năng nhận và xử lý mọi request từ
client.
Độ trễ thấp và thông lượng lớn
Trang 21
Hệ cơ sở dữ liệu phân tán Riak
- Sử dụng hàm băm khóa để cân bằng tải tốt.
- Xử lý truy vấn song song với Map-Reduce.
- Ưu điểm của storage backend mặc định - Bitcask.
HTTP + JSON
Riak sử đụng Erlang để viết bộ công cụ xây dựng các tài nguyên HTTP (HTTP
resource) tuân theo RFC. Điều này có nghĩa là, khi tương tác với 1 hệ thống Riak, bạn có
thể dùng các HTTP request theo cách tự nhiên và lấy về các kết quả ở dạng JSON. Tác
dụng chủ yếu của việc kết hợp Riak với các tài nguyên HTTP là hệ thống vận hành trơn
tru với những hạ tầng liên quan đến HTTP, bao gồm proxy, cache, và các loại client khác
nhau.

Kỹ thuật Link-walking
Phần lớn các phiên bản cài đặt của Amazon Dynamo là những hệ thống lưu trữ dữ
liệu dạng key/value. Hệ thống kiểu này có 1 vấn đề đối với việc lưu trữ những đối tượng
dữ liệu nhị phân lớn, chẳng hạn đôi khi bạn cần tìm những thứ mà không biết trước khóa
của nó.
Cách Riak sử dụng để giải quyết vấn đề này là sử dụng kỹ thuật link-walking. Mỗi
đối tượng dữ liệu được lưu trong Riak có thể có những mối quan hệ 1 chiều với đối tượng
dữ liệu khác thông qua Link trong HTTP header. Ta xét một ví dụ sau, giả sử khóa của 1
ban nhạc trong bucket có tên “artists”. Nếu ban nhạc đó được liên kết đến các album của
nó, rồi các album lại được liên kết đến các bản nhạc, bạn có thể tìm được tất cả các bản
nhạc chỉ với một request duy nhất. Điều này rõ ràng hiệu quả hơn rất nhiều so với việc sử
dụng các phép JOIN trong SQL, bởi mỗi item được xử lý một cách độc lập, hơn là 1 bảng
tại 1 thời điểm. Truy vấn trong ví dụ này có thể là:
GET /riak/artists/TheBeatles/albums,_,_/tracks,_,1
Cách liên kết như trên mang tính tự nhiên hơn nhiều so với cú pháp sau trong SQL:
SELECT tracks.* FROM tracks
INNER JOIN albums ON tracks.album_id = albums.id
INNER JOIN artists ON albums.artist_id = artists.id
WHERE artists.name = "The Beatles"
Trang 22

×