Upload
akaihoang
View
4
Download
0
Embed Size (px)
Citation preview
Phần 5: THỰC HIỆN HIỆU QUẢ CỦA ĐÔNG HỒ VECTOR
Nếu số lượng các tiến trình trong một tính toán phân tán là lớn, thì đồng hồ vector sẽ
yêu cầu kèm thêm một lượng lớn thông tin trong các thông điệp nhằm phục vụ mục đích
của quá trình khuyếch tán thời gian và cập nhật đồng hồ. Chi phí thông điệp tăng tuyến
tính với số lượng tiến trình trong hệ thống và khi có đến hàng nghìn tiến trình trong hệ
thống, kích cỡ thông điệp trở lên rất lớn ngay cả khi chỉ có một vài sự kiện diễn ra trong
vài tiến trình. Phần này thảo luận đến các cách hiệu quả để duy trì đồng hồ vector, các
phương pháp cơ bản tương tự có thể dùng để thực hiện hiêu quả đồng hồ ma trận.
Charron-Bost đã chỉ ra [2] rằng nếu đồng hồ vector phải đáp ứng tính chất nhất quán
mạnh, thì nhìn chung nhãn vector thời gian phải có cỡ ít nhất phải là n, tất cả các tiến
trình. Do vậy, nhìn chung kích thước của một nhãn vector thời gian là số lượng tiến trình
liên quan trong tính toán phân tán; tuy nhiên, một vài sự tối ưu hóa có thể xảy ra và tiếp
theo chúng tôi sẽ thảo luận về các kĩ thuật thực hiện đồng hồ vector một cách hiệu quả.
5.1 Phương pháp vi phân của Singhal-Kshemkalyani
Phương pháp vi phân của Singhal-Kshemkalyani [25] dựa trên việc quan sát giữa
thông điệp gửi thành công đến các tiến trình tương tự, chỉ một vài thành phần của đồng
hồ vector có thể bị thay đổi trong quá trình gửi thông điệp. Điều này dễ xảy ra hớn khi số
lượng tiến trình lớn do chỉ một vài tiến trình sẽ tương tác với thường xuyên bằng cách gửi
các thông điệp. Trong kỹ thuật này, khi một tiến trình pi gửi một thông điệp đến một tiến
trình pj, nó chỉ kèm thêm những thành phần của đồng hồ vector khác với thông điệp cuối
cùng được gửi đến pj.
Kỹ thuật này hoạt động như sau: nếu các thành phần i1,i2,…,in của đồng hồ vector tại
pi thay đổi tương ứng thành v1,v2,…vn, kể từ thông điệp cuối cùng được gửi đến pj, thì
tiến trình pi kèm theo một nhãn thời gian theo dạng
{(i1,v1), (i2,v2),…(in1,vn1)}
cùng thông điệp tiếp theo tới pj. Khi pj nhận được thông điệp này, nó sẽ cập nhật
đồng hồ vector của nó như sau:
vti[ik] = max(vti[ik],vk) với k=1,2,…n1.
Như vậy, phương pháp này cắt giảm kích thước thông điệp, băng thông truyền và yêu
câu vùng đệm (để lưu giữ thông điệp). Trong trường hợp xấu nhất, mọi thành phần của
đồng hồ vector được cập nhật tại pi do thông điệp cuối cùng tới tiến trình p j, và thông
điệp tiếp theo từ pi đến pj sẽ cần mang nhãn vector thời gian hoàn chỉnh của kích cỡ n.
Tuy nhiên, thường thì kích thước của nhãn thời gian trên một thông điệp sẽ ít hơn n. Lưu
ý rằng kỹ thuật này yêu cầu mỗi tiến trình ghi nhớ nhãn vector thời gian trong thông điệp
cuối gửi đến mọi tiến trình khác. Thực thi trực tiếp việc này sẽ dẫn đến chi phí lưu trữ
O(n2) tại mỗi tiến trình. Kỹ thuật này còn yêu cầu các kênh giao tiếp theo FIFO cho việc
truyền (gửi) thông điệp.
Singhal và Kshemkalyani đã phát triển một kỹ thuật thông minh cắt giảm chi phí lưu
trữ này tại mỗi tiến trình xuống O(n). Kỹ thuật này hoạt động theo cách sau: tiến trình p i
duy trì hai vector bổ sung sau:
LSi[l…n] (lần gửi cuối cùng): LSi[j] thể hiện giá trị của vti[i] khi tiến trình pi
gửi đi thông điệp cuối cùng đến tiến trình pj.
LUi[l…n] (lần cập nhật cuối cùng): LUi[j] thể hiện giá trị của vti[j] khi tiến
trình pi cập nhật lần cuối thành phần vti[j].
Rõ ràng, LUi[i]=vti[i] tại mọi thời điểm và LUi[j] cần được cập nhật chỉ khi nhận
được một thông điệp khiến pi cập nhật thành phần vti[j]. Đồng thời, LSi[j] cần cập nhật
chỉ khi pi gửi một thông điệp đến pj. Kể từ lần giao tiếp cuối cùng từ pi đến pj, chỉ những
thành phần k của đồng hồ vector vti[k] thay đổi do LSi[j] < LUi[k]. Do đó, những thành
phần này chỉ cần gửi trong thông điệp từ pi tới pj. Khi pi gửi một thông điệp tới pj, nó chỉ
gửi một bộ dữ liệu,
{(x, vti [x])|LSi[j]<LUi[x]},
là nhãn vector thời gian đến pj, thay vì gửi một vector n thành phần trong một thông điệp.
Do vậy, vector nguyên với kích thước n không phải gửi cùng với thông điệp. Thay
vào đó, chỉ các thành phần trong đồng hồ vector đã thay đổi từ thông điệp cuối cùng gửi
tới tiến trình kia được gửi theo dạng {(p1, latest_value), (p2,latest_value),..}, trong đó pi
thể hiện rằng thành phần lần thứ pi của đồng hồ véc tơ đã thay đổi.
Phương pháp này được minh họa trong hình 3.4. Ví dụ, thông điệp thứ hai từ p 3 tới p2
(bao gồm một nhãn thời gian {(3,2)} thông báo tới p2 rằng thành phần thứ ba của đồng hồ
vector đã được điều chỉnh và giá trị mới là 2. Điều này là do tiến trình p 3 (được thể hiện
bởi thành phần thứ ba của vector) làm tăng giá trị đồng hồ từ 1 lên 2 kể từ thông điệp
cuối cùng gửi tới p2.
Chi phí duy trì đồng hồ vector trong hệ thống lớn có thể giảm đáng kể bằng kỹ thuật
này, đặc biệt nếu sự tương tác tiến trình diễn ra cục bột heo không gian và thời gian. Kỹ
thuật này sẽ trở nên có lợi trong nhiều ứng dụng bao gồm cả bộ nhớ chia sẻ phân tán
nhân quả, phát hiện bế tắc phân tán, thi hành loại trừ lẫn nhau và truyền thông cục bộ
điển hình được quan sát trong hệ thống phân tán.
5.2 Kỹ thuật phụ thuộc trực tiếp của Fowler – Zwaenepoel
Kỹ thuật phụ thuộc trực tiếp [6] Fowler – Zwaenepoel làm giảm kích thước của
thông điệp bằng cách chỉ truyền một giá trị vô hướng trong thông điệp. Không có đồng
hồ vector được duy trì tức thời (on-the-fly). Thay vào đó, một tiến trình chỉ duy trì thông
tin liên quan đến tính phụ thuộc trực tiếp đến các tiến trình khác. Một vectơ thời gian cho
một sự kiện, thể hiện cho tính phụ thuộc bắc cầu trên tiến trình khác, được xây dựng
ngoại tuyến từ một tìm kiếm đệ quy của thông tin phụ thuộc trực tiếp vào các tiến trình.
Mỗi tiến trình pi duy trì một vector phụ thuộc Di. Ban đầu, Di[j] = 0 với j = 1, …, n.
Di được cập nhật như sau:
1. Bất cứ khi nào một sự kiện xảy ra tại pi, Di[i] := Di[i]+1. Có nghĩa là, các thành
phần của vector tương ứng với thời gian cục bộ được tăng lên một.
2. Khi một tiến trình pi gửi một thông điệp tới tiến trình pj, nó mang (piggybacks)
giá trị cập nhật của Di[i] trong thông điệp.
3. Khi tiến trình pi nhận một thông điệp từ pj với giá trị piggybacks d, pi cập nhật
vector phụ thuộc của nó như sau: Di[j]:= max {Di[j],d}.
Do vậy, vector phụ thuộc Di chỉ phản ánh phụ thuộc trực tiếp. Tại bất cứ thời điểm
nào, Di[j] biểu thị số thứ tự của các sự kiện mới nhất trên tiến trình p j cái mà trực tiếp ảnh
hưởng đến trạng thái hiện tại. Lưu ý rằng sự kiện này có thể đứng trước sự kiện mới nhất
tại pj nào ảnh hưởng đến trạng thái hiện tại.
Hình 3.5 minh họa kỹ thuật Fowler – Zwaenepoel. Ví dụ, khi tiến trình p4 gửi một
thông điệp tới tiến trình p3, nó piggybacks một đại lượng vô hướng chỉ ra sự phụ thuộc
trực tiếp của p3 vào p4 bởi thông điệp này. Sau đó, tiến trình p3 sẽ gửi một thông điệp tới
tiến trình p2 piggybacking một đại lượng vô hướng để chỉ sự phụ thuộc trực tiếp của p2
vào p3 bởi thông điệp này. Bây giờ, tiến trình p2 trên thực tế gián tiếp phụ thuộc vào quá
trình p4 vì tiến trình p3 là phụ thuộc vào tiến trình p4. Tuy nhiên, tiến trình p2 không bao
giờ được biết về tính phụ thuộc gián tiếp của nó vào p4.
Như vậy mặc dù tính phụ thuộc trực tiếp được thông báo hợp lệ cho các tiến trình
tiếp nhận, phụ thuộc bắc cầu (gián tiếp) không được duy trì bởi phương pháp này. Chúng
chỉ có thể thu được bằng cách đệ quy tìm dấu vết các vectơ phụ thuộc trực tiếp của các sự
kiện không trực tuyến. Điều này liên quan đến việc tính toán chi phí và độ trễ.
Như vậy phương pháp này là lý tưởng chỉ cho những ứng dụng mà không đòi hỏi
tính toán tính phụ thuộc bắc cầu tức thì. Tổng phí tính toán đặc trưng của phương pháp
này làm cho nó phù hợp nhất cho các ứng dụng như điểm ngắt nhân quả và phục hồi tính
năng kiểm tra từng điểm không đồng bộ trong đó các tính toán của quan hệ phụ thuộc
nhân quả được thực hiện ngoại tuyến.
Kỹ thuật này giúp tiết kiệm đáng kể chi phí; chỉ có một đại lượng vô hướng
piggybacked trên mỗi thông điệp. Tuy nhiên, các vector phụ thuộc không thể hiện tính
phụ thuộc bắc cầu (tức là, một nhãn thời gian vector). Tính phụ thuộc bắc cầu (hoặc
nhãn thời gian vector) của một sự kiện thu được bằng cách đệ quy truy tìm các vectơ phụ
thuộc trực tiếp của tiến trình. Rõ ràng, điều này sẽ có chi phí và sẽ liên quan đến độ trễ.
Do đó, kỹ thuật này là không phù hợp với các ứng dụng yêu cầu tính toán nhãn vector
thời gian tức thì. Tuy nhiên, kỹ thuật này là lý tưởng cho các ứng dụng mà các tính toán
của quan hệ nhân quả phụ thuộc thực hiện ngoại tuyến (ví dụ, điểm ngắt nhân quả, phục
hồi không đồng bộ checkpointing).
Tính phụ thuộc bắc cầu có thể được xác định bằng cách kết hợp sự kiện phụ thuộc
trực tiếp của một sự kiện với sự phụ thuộc trực tiếp vào nó. Trong hình 3.5, sự kiện thứ
tư của tiến trình p3 phụ thuộc vào sự kiện đầu tiên của tiến trình p4 và sự kiện thứ tư của
tiến trình p2 phụ thuộc vào sự kiện thứ tư của tiến trình p3. Bằng cách kết hợp hai phụ
thuộc trực tiếp, có thể suy luận rằng sự kiện thứ tư của tiến trình p2 phụ thuộc vào sự kiện
đầu tiên của tiến trình p4. Cần hết sức lưu ý rằng nếu sự kiện ej tại tiến trình pj xảy ra
trước sự kiện ei tại tiến trình pi, thì tất cả các sự kiện từ e0 đến ej-1 trong tiến trình pj cũng
xảy ra trước ei. Do đó, ghi lại cho ei sự kiện mới nhất của tiến trình pj đã xảy ra trước ei là
đủ. Bằng cách này, mỗi sự kiện sẽ ghi lại sự phụ thuộc của nó vào các sự kiện mới nhất
trên mỗi tiến trình khác mà nó phụ thuộc vào và duy trì những sự kiện phụ thuộc riêng
của chúng. Kết hợp tất cả các phụ thuộc, các tập nguyên (tức là vector nguyên bản) của
một sự kiện cụ thể phụ thuộc vào các sự kiện có thể được xác định ngoại tuyến.
Tính toán ngoại tuyến của phụ thuộc bắc cầu có thể được thực hiện bằng cách sử
dụng một thuật toán đệ quy đề xuất trong [6] và được minh họa bằng một dạng thức có
sửa đổi trong thuật toán 3.1. DTV là vector theo dõi phụ thuộc có kích thước n (trong đó
n là số tiến trình) mà lẽ ra phải theo dõi tất cả các phụ thuộc quan hệ nhân quả của một sự
kiện cụ thể ei ở tiến trình pi. Các thuật toán sau đó cần phải được gọi là DependencyTrack
(i, Die[i] ). Các thuật toán khởi tạo DTV có giá trị nhãn thời gian ít nhất có thể là 0 cho tất
cả các thành phần trừ i mà giá trị được đặt cho Die[i]: với mọi k = 1, …,n và k i,
DTV[k] = 0 và DTV[i] = Die[i] .
Sau đó thuật toán gọi giải thuật VisitEvent đối với tiến trình pi và sự kiện ei.
VisitEvent kiểm tra tất cả các thành phần (1, …,n) của DTV và Die và nếu giá trị trong Di
e
là lớn hơn giá trị trong DTV tại thành phần đó, sau đó DTV giả định giá trị của D ie cho
thành phần đó. Điều này đảm bảo rằng sự kiện mới nhất trong tiến trình j mà e i đó phụ
thuộc vào được ghi lại trong DTV. VisitEvent được gọi đệ qui đối với tất cả các thành
phần mà mới được đưa vào trong DTV để các thông tin phụ thuộc mới nhất có thể được
theo dõi một cách chính xác.
Minh họa cho thuật toán theo dõi phụ thuộc đệ quy bằng cách theo dõi các phụ thuộc
của sự kiện thứ tư tại tiến trình p2. Các thuật toán được gọi là DependencyTrack (2 4).
DTV ban đầu được thiết lập là <0 4 0 0> bởi DependencyTrack. Sau đó nó gọi
VisitEvent(2 4). Các giá trị được tổ chức bởi D42 là < 1 4 4 0 >. Vì vậy, DTV lúc này
được cập nhật thành < 1 4 0 0 > và VisitEvent (1 1) được gọi.
Các giá trị được tổ chức bởi D11 là < 1 0 0 0 >. Vì khi không có thành phần nào là lớn
hơn các thành phần tương ứng trong DTV, thuật toán lặp lại. Một lần nữa các giá trị được
tổ chức bởi D24 được kiểm tra và ần này thành phần 3 được tìm thấy là trong D2
4 lớn hơn
DTV. Vì vậy, DTV được cập nhật thành < 1 4 4 0 > và VisiEvent(3 4) được gọi. Các giá
trị được tổ chức bởi D34 là < 0 0 4 1 >. Vì thành phần 4 của D3
4 lớn hơn của DTV, nó
được cập nhật thành < 1 4 4 1 > và VisitEvent(4 1) được gọi. Vì tất cả các thành phần đã
được kiểm tra, Visit(2,4) được giải phóng và DependencyTrack cũng vậy.
Kỹ thuật này có thể tiết kiệm đáng kể chi phí chỉ có một đại lượng vô hướng
piggybacked trên mỗi thông điệp. Một trong những yêu cầu quan trọng là một tiến trình
cập nhật và ghi lại vectơ phụ thuộc của nó sau khi nhận được một thông điệp và trước khi
gửi đi bất kỳ thông điệp nào. Ngoài ra, nếu sự kiện xảy ra thường xuyên, kỹ thuật này sẽ
cần ghi lại lịch sử của một số lớn các sự kiện.