Sơ lược về các dạng DHT (Distributed Hash Table)
Nhắn gửi: Đây là bài về 4 loại DHT. Rất mong được nhận sự góp ý và phản hồi của các bạn. - rogp10
DHT là một hệ thống phân tán dùng để tìm kiếm (siêu) dữ liệu, dựa trên bảng hash table cổ điển. DHT có hai cặp hash:
- ID -> địa chỉ IP: mỗi client (còn gọi là node) có một định danh ID riêng để nhận và truyền tin.
- HID -> (meta)data: mỗi gói dữ liệu có một fingerprint riêng để nhận dạng, lưu trên client và giữ toàn vẹn dữ liệu.
Mỗi client trong mạng DHT sẽ lưu các HID “lân cận” với ID của nó.
DHT có những mục tiêu cơ bản như sau:
- Dữ liệu được phân bố đều về mặt địa lí. (chia tải)
- Chỉ cần một (vài) node bất kì để hòa mạng. (phân tán)
- Kích thước bảng định tuyến và chi phí tìm kiếm tối đa tăng theo hàm log của số node đang trong mạng. (mở rộng)
- Duy trì kết nối luận lí trước sự hòa mạng và rời mạng liên tục. (ổn định)
Sự linh hoạt trong việc chọn node kết nối có thể tăng tốc đồng bộ và thiết lập DHT.
Hàm hash thường được chọn từ 128 bit trở lên để đáp ứng cho hàng chục triệu node (khoảng 59 bit với khả năng đụng là 50%) và gói dữ liệu. Client dùng đặc điểm của nó kèm theo 1 chuỗi ngẫu nhiên (nonce) để hash và tự đặt ID cho mình.
Có ba thao tác chung:
- Kiểm tra xem node có ID này còn sống hay không.
- Tìm dữ liệu theo HID.
- Tìm thông tin của một ID xác định, hoặc những ID lân cận với ID đó.
I. Chord
Không gian ID trong Chord được thiết kế thành vòng tròn kín, với mỗi node đều có node liền trước (pred) và các node đứng sau. Node X trong Chord lưu một bảng “finger table” lưu các cặp (ID, IP) sao cho finger[i]
có ID thuộc (X+2^(i-1), X+2^i], nghĩa là X+2 trong slot 1, X+[3…4] slot 2, slot 3 lưu X+[5…8], v.v. cùng với ID liền trước nó (pred) để hỗ trợ thao tác hòa mạng. Đồng thời node X lưu hết thảy các dữ liệu nó biết có HID nằm trong nửa khoảng (pred, X].
pred ====> X ====> finger[1..w]
- Tìm HID
Để tìm một HID (get
) ta sẽ đi theo slot có nhiều khả năng nhất. Ta sẽ minh họa với vòng 128 slot, HID là 09 (Z/128Z, +)
ID 37: 9 - 37 (= 128+9 - 37) = 100 > 64, slot 7 có máy 103 (< 9).
ID 103: 9 - 103 = 34 > 32, slot 6 có máy 14 > 9, slot 5 có máy 123
ID 123: 9 - 123 = 14 > 8, slot 4 có máy 6 < 9.
ID 6: succ(6) = 10, vậy trả về (10, IP_10) cho ID 37.
- Hòa mạng
Hàm succ(ID) dùng để tìm ID của client nằm sau một client cho trước. Để hòa mạng, một client có ID tự đặt là M kết nối với client X trong mạng:
- M tự xưng danh với X.
- X truy vấn succ(M).
- X trả về kết quả succ(M) cùng bảng finger.
- M liên lạc với succ(M), ta gọi là M’.
- M’ kiểm tra tham số pred của mình
- Nếu pred nằm sau M và còn sống thì M’ phải trả về pred. M tiếp nhận M’ này vào bảng finger. (TH1)
- Ngược lại M’ báo lại cho pred của mình và cập nhật lại M’.pred = M. M’ trao lại bảng finger của mình cho M. (TH2)
(TH2): M hòa mạng thành công. M yêu cầu M’ trao lại các key M’ đang giữ.
(TH1): M liên lạc với pred, hoặc nếu không có pred thì lại tiếp tục qua bảng finger đã nhận từ X.
- Điều chỉnh
Sau một khoảng thời gian, node X xem lại tất cả những kết nối của mình. Có hai thao tác chính:
- Gọi succ(pred), nếu bằng X thì thôi, ngược lại yêu cầu cho succ(pred) kiểm tra pred và nhận key, tương tự như khi hòa mạng.
- Ping tất cả các ID trong bảng finger để điều chỉnh.
- Phát hành
Để đưa một HID mới vào mạng, ta phát yêu cầu succ(HID) qua bảng finger, sau đó yêu cầu ID này lưu HID của ta.
- Các thông điệp truyền:
- OK: gồm chữ “OK” và ID. NOK tương tự.
- START + ID: xưng danh, nhận PAIR và FING.
- JOINS + ID: liên lạc với succ(M) để hòa mạng, nhận (OK và FING) hoặc (NOK và PAIR)
- PING + ID: trả về OK hay timeout.
- FING + PAIRID(s): bảng finger.
- SUCC + ID: trả về PAIRID.
- LEAVE + ID: gửi cho tất cả liên kết.
- STORE + PAIRHID: yêu cầu lưu một HID, trả về OK hay NOK (quá đầy hoặc hash không đúng).
- RETK + PAIRHID(s): trả về (những) key mà node đang giữ.
Tóm tắt:
- Để tìm một HID
h
trong Chord, ta cần phải hỏi những máy có ID (nằm sau)h
. - Chord khá giống 1 DSLK đơn vòng với b con trỏ: 1, 2, 4, 8, 16, 32, …, 2^(b-1) vì pred không tham gia định tuyến.
- Node pred rất quan trọng trong Chord.
II. Kademlia
Kademlia là một hệ thống có tính lan truyền cao và độ trễ thấp, cùng những tính chất lí tưởng khác nên rất thông dụng. Kademlia được sử dụng trong giao thức BitTorrent, với các ứng dụnhg như OpenBitTorrent, uTorrent, Azureus, v.v.
Thay vì sử dụng phép trừ làm khoảng cách như Chord, Kademlia sử dụng phép XOR ⊕ có hai đặc tính ưu việt:
Do nhu cầu thực tế, ngoài IP client còn phải lưu thêm port, nên cặp (ID, IP) trở thành (ID, IP:port). Kademlia sử dụng ID dài 160 bit, và mỗi client có một dãy bucket để lưu các cặp ID, mỗi bucket gồm tối đa k cặp như vậy. Nguyên tắc chọn ID của Kademlia là: ID càng kết nối lâu càng nhiều khả năng sống lâu, được phát biểu như sau:
Với x.alive(t) là biến cố node x sống tại thời điểm t >= 0s, thì
P(x.alive(t + 3600s) | x.alive(t)) >= 1/2
và đơn điệu tăng theo t.Đại diện cho mỗi bucket là một mặt nạ bit, sao cho các ID trong bucket đều khớp với mặt nạ bit đó, giống như 192 168 1 23 ứng với 192.168.1.0/24 vậy. Điều khác biệt là khi X có một bucket đã đủ túc số và tiếp nhận địa chỉ M phù hợp thì có hai trường hợp sẽ xảy ra:
Khi vẽ ra sẽ giống một cây nhị phân.
Dễ thấy rằng mỗi bucket thu hẹp khoảng cách hơn một nửa, nên số thông điệp truy vấn sẽ vào khoảng O(logn) với n là số client trong mạng. Ngoài ra, một client sẽ biết rõ những ID gần mình nhất hơn là những ID xa hơn, đây là đặc trưng của DHT, không chỉ của Kademlia.
Kadmlia quy định bốn câu lệnh cơ bản:
Trong truy vấn lặp, chỉ có máy khởi truy vấn hỏi và máy trung gian trả lời bằng ID trung gian tiếp theo, hoặc đích. Ngược lại, với truy vấn đệ quy, máy trung gian thứ k sẽ hỏi máy trung gian tiếp theo, nhận câu trả lời để trả lời lại cho máy trung gian thứ k-1.
Truy vấn lặp: X <-> 1, X <-> 2, X <-> 3, … , X -> goal
Truy vấn đệ quy: X -> 1, 1 -> 2, 2 -> 3, …, n -> n-1, n-1 -> n-2, …, 1 -> X, X -> goal.
Để tìm một ID, X chọn một bucket của mình gần với ID đó nhất và phát truy vấn ID đến α máy cùng lúc trong bucket đó (tham số a này được chọn cẩn thận và α << k). X tiếp nhận tất cả ID trả về vào bucket của mình. X duy trì α (thường là 3) truy vấn chưa trả lời. Nếu không thể tiến gần đến ID hơn nữa, X sẽ phát lookup tới những node còn lại trong kết quả trả về (tối đa α node cùng lúc) và thất bại sau nỗ lực cuối cùng này. Bạn đọc tinh ý sẽ thấy đây chính là quá trình “leo đồi”.
Để hòa mạng, client M xưng danh với client X, gửi FIND_NODE(M) cho X và thực hiện quá trình vừa nêu trên. Sau đó M thực hiện “Làm tươi” những bucket không thể có X trong đó.
Để phát hành (HID, value) X phát lệnh STORE cho bucket gần HID nhất. Một HID được lan truyền bằng cách: nếu một node Y phát hiện Z gần với HID hơn (xor nhỏ hơn) thì Y phát STORE cho Z. Đồng thời, trong quá trình tìm kiếm, ID gần nhất mà không trả lời được sẽ nhận STORE từ ID yêu cầu, nếu tìm được để giảm số yêu cầu trong mạng. Ở đây có hai loại client:
Để duy trì mạng, client tiến hành “làm tươi” các bucket với những bucket đã nguội lạnh bằng cách thực hiện một truy vấn FIND_NODE với một ID ngẫu nhiên ứng với mỗi bucket đó.
Trình bày xong phần Kademlia.
Nói thêm: Dùng truy vấn đệ quy thì các node tham gia sẽ phải duy trì kết nối trong khoảng thời gian dài, và còn dễ bị lợi dụng (amplification) nữa. Nhưng dùng truy vấn lặp thì không phải máy nào cũng liên lạc trực tiếp được.
Với các mạng lưu metadata, có thể cân nhắc cho client loại 2 (ứng với HID cụ thể, không phải role đặc biệt) cũng có thể kích hoạt STORE (phát tán) nhưng vẫn bị hết hạn. Loại 3 (cache) mới không phát tán và chỉ lưu trữ trong thời gian ngắn.
Ngoài ra, các node phải ưu tiên cho những range IP gần mình hơn để tránh bị ISP tuýt còi. Tapestry và Pastry bổ sung bảng neighbor vào để phần nào giải quyết vấn đề này.
III. Tapestry
Tapestry và Pastry là hai DHT thuộc họ Plaxton. Mỗi node trong Tapestry có một ma trận định tuyến (gọi tắt là “ma trận”) Mx gồm m dòng [0…m-1] và 2^w cột đánh số [0…2^w - 1].
VD: Trong ma trận của node 687, ID 631 sẽ nằm ở dòng 1, cột 3; 682 là dòng 2 cột 2; 793 là dòng 0 cột 7.
Để tìm một node, client so sánh ID với ID đang tìm để tìm cột cho đúng. Nếu ô đó không có ID nào thì dùng những ô lân cận.
VD: lookup(4807): 3622 -> 4931 -> 4824 -> 4806, dòng 3 hết.
Để phát hành một HID, client X xác định node nào có ID “liền sau” (không giống Chord & Pastry, 2999 và 3000 thuộc về hai mạch khác nhau) HID sẽ trở thành root R (hay surrogate) của HID này. Nguyên tắc là 1 HID chỉ được có 1 surrogate, nhưng nhiều client có thể phát hành một HID. Các client trung gian lưu cặp (HID, X) cho đến R thì dừng lại. Để tìm một HID, client phát lệnh tìm kiếm HID cho đến khi gặp một client có cặp (HID, X). Do mỗi ô trong bảng định tuyến có ping thấp nhất cục bộ, nên client tìm được cũng có ping nhỏ nhất.
Các client lưu dữ liệu phải định kì “làm tươi” HID đang giữ, để đảm bảo root không mất.
Để hòa mạng:
VD: Node 168 chọn node 840 làm bootstrap
Q168: join 168 -> A840: id {273 439 504 667 301 194}
Q168: lookup 168 -> A301: id {184 124}, A667: id {100}
Q168: rank 168 -> A184: id {131 175 154 109 124} A194: {154 109 116 100}
Q168: lookup 168 -> A175: id {167 164}, A116: id {}
Q168: rank 168 -> A167: id {160 164}, A164: id {160 167}.
Q168: knock 168 -> A167: hid {163 839} {163 031} hoặc hid {}
Q168: lookup 163 … (166 không tồn tại)
Q168: reg 166 -> A839: OK A031: OK
Ma trận 168:
031 194 273 301 439 504 667 --- 839 --- 109 116 124 131 --- 154 --- 175 184 194 160 --- --- --- 164 --- --- 167 168 ---
Khi rời mạng, client gửi m ID (bằng 5 là ổn) từ những dòng thấp nhất để gửi cho tất cả ID ở phía trên dòng đó. Sau đó, nếu client đang là root thì client chọn một ID gần HID nhất trong bảng, yêu cầu tiếp nhận các HID mình đang giữ. Client đáp lại thực hiện như những bước trên.
Để ổn định mạng:
IV. Pastry
Ngoài ma trận định tuyến đã trình bày ở phần III, mỗi client Pastry còn lưu trữ thêm u node “hàng xóm” (leaf set) ở hai bên ID của mình (giống Chord: khoảng cách tính bằng phép trừ), kèm theo u’ node có ping thấp (neighborhood set). Thường chọn u bằng w hoặc 2w và u’ bằng 8w hoặc 16w.
Tìm node: Pastry sử dụng truy vấn đệ quy, nếu ID cần tìm rơi vào u node trong leaf set thì có thể kết luận ngay; ngược lại, nếu ID ứng với ô trống trong ma trận thì (hiếm) sẽ chọn 1 node trong neighborhood set càng gần ID càng tốt.
Sau đó, nếu ô ứng với ID cần tìm bị timeout thì việc sửa chữa sẽ tiến hành qua việc lookup qua ô tiếp theo.
Hòa mạng: Để client M hòa mạng, M chọn một bootstrap node X gần mình nhất và phát một yêu cầu đặc biệt, có ID của M.
Duy trì:
Đó là phần lõi của Pastry. Phần lưu file được hiện thực bằng một “app” của Pastry gọi là PAST. Ý niệm là một HID được lưu trữ (không chỉ là quản lí như Tapestry) bởi một “surrogate” và toàn bộ leaf set của nó. Điều này làm cho ping thấp hơn, do ID rải đều khắp các vùng địa lí.
Hết phần III và IV.
Ngoài tính bất đối xứng, do khoảng cách giữa các ID không phân bố đều, một số node Chord phải lưu rất nhiều HID vì có khoảng cách với pred lớn hơn bình thường. Kademlia thông dụng hơn hẳn, với phần định tuyến đơn giản, cách chọn kết nối linh động và khoảng cách có tính đối xứng.
Để hiện thực hóa một mạng P2P, ngoài DHT còn có thể dùng hình thức gossip: các node liên lạc và trao đổi liên kết một cách ngẫu nhiên với nhau; thông qua việc tối ưu cục bộ, dữ liệu sẽ tập trung ở các node có băng thông lớn và sống lâu: Việc tạo node và hòa mạng dễ dàng mở ra khả năng tạo ra thật nhiều client phá hoại trong mạng. Những mạng có dùng DHT phải đối mặt với kiểu tấn công đặc trưng: để cô lập một node S, chỉ cần đánh sập S để tất cả kết nối từ S đều bị timeout, sau đó tạo nhiều ID (có thể cùng trên một máy vật lí) gần với S và kết nối với S. Với số node tạo ra lên đến hàng nghìn, node S bị cô lập hoàn toàn. Điều này cực kì nguy hiểm với những ứng dụng tài chính.
Ngoài ra, cấu trúc DHT làm cho việc phục hồi kết nối trong tình huống chia cắt và cô lập bị hạn chế rất nhiều, và tình trạng đó vẫn kéo dài sau khi kết nối tầng thấp hơn được tái lập. DHT có khả năng tìm kiếm nhanh và chọn lọc, nhưng gossip có thể tìm theo từ khóa, thay vì phải tag vào hash. Như vậy, nghiên cứu kết hợp gossip trên nền DHT là hướng thật sự có tiềm năng và lí thú. (Chấm hết)
Tài liệu tham khảo
Antony Rowstron, Peter Druschel, Pastry: Scalable, decentralized object location and routing for large-scale peer-to-peer systems, 2001.
Ben Y. Zhao, Ling Huang, et al. Tapestry: A Resilient Global-Scale Overlay for Service Deployment, IEEE Journal on Selected Areas in Communications, Vol. 22(1), 2004.
Eng Keong Lua, Jon Crowcroft, et al. A Survey and Comparison of Peer-to-Peer Overlay Network Schemes, IEEE Communication Surveys and Tutorial, Vol. 7(2), 2005.
Heiko Niedermayer, Simon Rieche, et al., On the Distribution of Nodes in Distributed Hash Tables, 2005.
Ion Stoica, Robert Morris, David Karger, et al., Chord: A Scalable Peer-to-peer Lookup Service for Internet Applications, 2001.
Marin Bertier, François Bonnet, et al., D2HT: the best of both worlds, Integrating RPS and DHT. European Dependable Computing Conference, 2010.
Petar Maymounkov, David Mazières, Kademlia: A Peer-to-peer Information System Based on the XOR Metric, 2002.
Yuval Marcus, Ethan Heilman, Sharon Goldberg, Low-Resource Eclipse Attacks on Ethereum’s Peer-to-Peer Network, 2018.