Triển khai Phân tán
Phục vụ một tài liệu từ nhiều nút
Khi nhiều nút phục vụ cùng một tài liệu, nút thực hiện việc render tài liệu không nhất thiết là nút trả lời các yêu cầu trang của trình duyệt. Bản phát hành này chia công việc đó thành hai nửa rõ ràng — một bên ghi xuất bản các artefact của tài liệu vào bộ nhớ chia sẻ, và một bên đọc phục vụ chúng từ bất kỳ nút nào.
Nếu ứng dụng của bạn hiện đang gọi UseDoconutWebFarm(...), trang này là sự thay thế cho bạn. Cả UseDoconutWebFarm và WebFarmOptions đều không tồn tại trong bản phát hành này.
Kiến trúc của bạn không thay đổi. Đặt root chia sẻ ở cùng một đường UNC mà bạn đang sử dụng hiện nay và mọi nút sẽ tiếp tục hoạt động như trước. Điều thay đổi là việc xuất bản trở thành một thao tác bạn gọi, thay vì là hiệu ứng phụ của quá trình render.
Những gì thay thế middleware web-farm
Trước đây, một middleware duy nhất thực hiện mọi việc: nó phục vụ các hình ảnh đã được render trước từ một thư mục chia sẻ và được truy vấn trong khi một nút khác vẫn đang ghi chúng. Thời gian chờ là một ngân sách mù — "chặn tối đa ba phút hy vọng trang xuất hiện".
Bây giờ cùng một công việc được chia thành hai lời gọi mà bạn có thể nhìn thấy và suy xét:
node A OpenDocument request
-> PublishAsync(...) writes pages, document info,
search index and metadata
into RootPath/{token}/
-> returns the token
node B ?token=...&page=7
-> UseDoconutCloud<FileShareCloudHandler>
-> reads RootPath/{token}/ and serves the pageCấu hình chia sẻ mà cả hai nửa cần
Publisher ghi bằng một FileShareConfig và trình xử lý đọc phục vụ bằng một FileShareConfig. Nếu RootPath hoặc UsePerTokenSubfolders khác nhau giữa chúng, nút còn lại sẽ trả về 404. Xây dựng cấu hình ở đúng một nơi và gọi nó từ cả hai phía — ứng dụng tham chiếu giữ một helper SharedStore.Config(...) duy nhất vì lý do này:
internal static class SharedStore
{
public static FileShareConfig Config(string rootPath, bool asyncPublish) => new()
{
Location = CloudLocation.FileShare,
RootPath = rootPath, // e.g. \\fileserver\doconut
UsePerTokenSubfolders = true,
ReadReadyPollMs = asyncPublish ? 250 : 0,
};
}FileShareConfig nằm trong Doconut.Clouds, cùng với AzureConfig, AmazonConfig, GoogleCloudConfig, DropBoxConfig, RedisConfig, CDNConfig, và FTPConfig. Trong thư viện trước, chúng nằm trong Doconut.Configs.Cloud; tên lớp không thay đổi.
| Property | Mục đích |
|---|---|
Location | CloudLocation.FileShare cho một chia sẻ UNC/cục bộ |
RootPath | Thư mục chia sẻ mà mọi nút có thể truy cập |
UsePerTokenSubfolders | Giữ các artefact của mỗi tài liệu trong thư mục {token} riêng của nó |
ReadReadyPollMs | Thời gian bên đọc chờ một trang vẫn đang được ghi; 0 tắt việc polling |
Bên ghi — xuất bản tài liệu
Tiêm DistributedDocumentPublisher và xuất bản thay vì mở một phiên cục bộ. Một lời gọi sẽ render và ghi toàn bộ bộ artefact — các trang, thông tin tài liệu, chỉ mục tìm kiếm và siêu dữ liệu:
var shareConfig = SharedStore.Config(rootPath, asyncPublish: false);
var documentOptions = new DocOptions { TimeOut = 60 };
var config = new PdfConfig { AllowSearch = true };
var publishOptions = new DistributedDocumentPublishOptions
{
CleanUpStagingDirectory = true,
FirstPagePriority = true,
};
DistributedDocumentPublishResult result = await _publisher.PublishAsync(
pathToFile, shareConfig, config, documentOptions, publishOptions, ct);
return Content(result.Token);DistributedDocumentPublishResult chứa Token, ArtifactPath, TotalPages, và Format. Token được gửi tới trình duyệt giống như một token viewer thông thường.
Lời gọi duy nhất này thay thế cặp lệnh trước — mở tài liệu vào một phiên cục bộ và sau đó đổ các hình ảnh lên chia sẻ bằng ExportToPng(sharedPath). ExportToPng và ExportToCloud không có tương đương ở đây.
Đọc phía — phục vụ từ chia sẻ
Gắn trình xử lý FileShare vào cùng nhánh mà client đã gọi:
app.UseDoconutResources();
app.MapWhen(
ctx => ctx.Request.Path.Value?.EndsWith("DocImage.axd", StringComparison.OrdinalIgnoreCase) == true,
branch => branch.UseDoconutCloud<FileShareCloudHandler>(options =>
{
options.Location = CloudLocation.FileShare;
options.CloudUploadConfig = SharedStore.Config(rootPath, asyncPublish: false);
}, "/"));UseDoconutCloud là generic trong phiên bản này: nhà cung cấp là đối số kiểu thay vì một giá trị Location được chuyển đổi bên trong middleware. Các trình xử lý được đặt tên <Provider>CloudHandler — FileShareCloudHandler, AzureCloudHandler, AmazonS3CloudHandler, GoogleCloudHandler, DropBoxCloudHandler, RedisCloudHandler, FTPCloudHandler, và CDNCloudHandler.
Trình xử lý CDN là chỉ-đọc — nó phục vụ những gì đã có trên CDN; nó không tải lên.
Ưu tiên trang đầu và HTTP 202
FirstPagePriority = true cho phép trang 1 được phục vụ ngay lập tức. Các trang sau sẽ trả về HTTP 202 Accepted cho đến khi chúng được ghi, thay vì chặn yêu cầu.
Điều này chuyển việc chờ sang phía client, nơi nó được giới hạn và có thể quan sát. Bật tính năng thử lại trên widget:
const objViewer = $('#div_ctlDoc').docViewer({
BasePath: '/',
ResPath: 'doconut-res/images',
retryOn409: true // retries 202 (and legacy 409) readiness responses
});Một client không thử lại sẽ hiển thị các ô bị hỏng cho những trang vẫn đang được render. Ngân sách thử lại và các giá trị mặc định (retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs) được mô tả trong Cấu hình Viewer.
Cặp này — FirstPagePriority cộng với việc poll phía đọc — là thứ thay thế các cài đặt PageWaitTimeSeconds và StartWaitFromPage trước đây. Cả hai đều không còn tồn tại nữa.
Tùy chọn: xuất bản trong nền
Mặc định PublishAsync thực hiện render trước khi trả về. Để trả về ngay khi token được tạo và cho phép một worker được host render vào cùng thư mục, đăng ký hàng đợi opt‑in:
builder.Services.AddDoconut(options => { /* … */ });
builder.Services.AddDoconutDistributedAsyncPublish();Sau đó enqueue thay vì publish inline, và đặt Async = true trên các tùy chọn publish. Hàng đợi có giới hạn: khi bị đầy, EnqueueAsync sẽ ném DistributedPublishQueueFullException. Trả về lỗi 503 cùng header Retry-After để client giảm tốc thay vì bị treo:
try
{
var token = await _publishQueue.EnqueueAsync(
pathToFile, shareConfig, config, documentOptions, publishOptions, ct);
return Content(token);
}
catch (DistributedPublishQueueFullException)
{
Response.Headers.RetryAfter = "5";
Response.StatusCode = 503;
return Content("The publish queue is full. Please retry.");
}Nếu không đăng ký, sẽ không có gì được đăng ký và việc xuất bản sẽ vẫn đồng bộ — đường dẫn mặc định sẽ hoàn toàn giống nhau byte‑đối‑byte.
Khi bật xuất bản nền, hãy cung cấp cho phía đọc một giá trị ReadReadyPollMs khác 0 để nó chờ một thời gian ngắn cho trang mà worker chưa ghi.
Bảo mật trên các nút
Token ràng buộc phiên làm việc giả định nút phục vụ đã nhìn thấy yêu cầu mở đầu. Trong một farm, nút đó không nhìn thấy, vì vậy kiểm tra theo phiên sẽ từ chối các yêu cầu hợp lệ. Đó là lý do ứng dụng tham chiếu phân tán đặt UnsafeMode = true.
UnsafeMode = true không phải là mô hình bảo mật — nó là việc loại bỏ một mô hình không còn áp dụng. Thay thế nó bằng các ticket truy cập có chữ ký, mà mỗi nút đều có thể xác minh độc lập:
builder.Services.AddDoconutDistributedDocumentSecurity(options => { /* … */ });Không bao giờ đưa UnsafeMode = true trở lại môi trường triển khai một nút duy nhất, và không bao giờ coi nó như một lối tắt cho việc kiểm tra phiên thất bại trên một nút — ở đó cách khắc phục đúng là AddSession() / UseSession(). Xem Phiên làm việc & Bảo mật.
Đóng tài liệu đã xuất bản
CloseDocument(token) trên Viewer được tiêm sẽ giải phóng một phiên cục bộ. Để xóa các artefact đã xuất bản của tài liệu khỏi chia sẻ, nhà xuất bản cung cấp:
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)Gửi cùng một FileShareConfig mà tài liệu đã được xuất bản với.
Bản đồ thiết lập theo thiết lập
| Trước | Hiện tại |
|---|---|
UseDoconutWebFarm(new WebFarmOptions { … }) | PublishAsync(...) ở phía ghi và UseDoconutCloud<FileShareCloudHandler>(...) ở phía đọc |
WebFarmOptions.Path | FileShareConfig.RootPath |
PageWaitTimeSeconds / StartWaitFromPage | DistributedDocumentPublishOptions.FirstPagePriority và FileShareConfig.ReadReadyPollMs |
viewer.OpenDocument(...) then viewer.ExportToPng(share) | một lời gọi PublishAsync(...) |
viewer.ExportToCloud(cloudConfig) | PublishAsync(...) với cấu hình của nhà cung cấp |
UseDoconutCloud(new CloudOptions { Location = … }) | UseDoconutCloud<THandler>(options => …, pathPrefix) |
DocOptions.IsWebfarm / WebfarmPath | Vẫn tồn tại, viết là IsWebFarm / WebFarmPath — không cần trên đường xuất bản |
Doconut.Configs.Cloud | Doconut.Clouds |
Danh sách kiểm tra
- Xây dựng
FileShareConfigtrong một helper và gọi nó từ cả hai nửa. - Đặt
RootPathvào chia sẻ mà bạn đã sử dụng. - Xuất bản bằng
PublishAsync(...); xóa mọi lời gọiExportToPng/ExportToCloud. - Phục vụ bằng
UseDoconutCloud<FileShareCloudHandler>(...)trên nhánh mà client gọi. - Bật
FirstPagePriorityvàretryOn409cùng lúc, hoặc tắt cả hai. - Thay thế kiểm tra mỗi phiên bằng vé truy cập có chữ ký — không chỉ giao
UnsafeMode = true. - Xác minh bằng cách mở một tài liệu trên một nút và yêu cầu một trang trễ từ nút khác.
Trang này có hữu ích không?