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 write side xuất bản các artefact của tài liệu vào bộ nhớ chia sẻ, và một read side 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 nó. Cả UseDoconutWebFarm và WebFarmOptions đều không tồn tại trong bản phát hành này.

Topology của bạn không thay đổi. Đặt root chia sẻ ở cùng đường UNC mà bạn đang dùng hôm nay và mọi nút sẽ tiếp tục hoạt động như hiện tại. Đ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.

Điều gì đã thay thế middleware web-farm

Trước đây một middleware làm 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à polled 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ù — "khóa 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ể thấy và suy xét:

text
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 page

Cấ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. 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:

csharp
internal static class SharedStore
{
    public static FileShareConfig Config(string rootPath, bool asyncPublish) => new()
    {
        Location              = CloudLocation.FileShare,
        RootPath              = rootPath,               // ví dụ: \\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 đây chúng nằm trong Doconut.Configs.Cloud; tên lớp không thay đổi.

Thuộc tínhMục đích
LocationCloudLocation.FileShare cho một chia sẻ UNC/cục bộ
RootPathThư mục chia sẻ mà mọi nút có thể truy cập
UsePerTokenSubfoldersGiữ các artefact của mỗi tài liệu trong thư mục {token} riêng của nó
ReadReadyPollMsThời gian mà read side chờ một trang vẫn đang được ghi; 0 vô hiệu hoá việc polling

Write side — 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:

csharp
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(
    path, shareConfig, config, documentOptions, publishOptions, ct);

return Content(result.Token);

DistributedDocumentPublishResult mang Token, ArtifactPath, TotalPages, và Format. Token này được gửi tới trình duyệt chính xác như một token viewer thông thường.

Lời gọi duy nhất này thay thế cặp trước — mở tài liệu vào một phiên cục bộ và sau đó đổ hình ảnh lên chia sẻ bằng ExportToPng(sharedPath). ExportToPng và ExportToCloud không có tương đương ở đây.

Bên đọc — phục vụ từ chia sẻ

Gắn trình xử lý FileShare vào cùng nhánh mà phần khách đã gọi:

csharp
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 làm cho trang 1 có thể phục vụ ngay lập tức. Các trang sau trả lời 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ờ đợi sang phần khách, nơi nó được giới hạn và hiển thị. Kích hoạt việc thử lại trên tiện ích:

javascript
const objViewer = $('#div_ctlDoc').docViewer({
    BasePath:   '/',
    ResPath:    'doconut-res/images',
    retryOn409: true      // thử lại 202 (và 409 kế thừa) phản hồi sẵn sàng
});

Phần khách không thử lại sẽ hiển thị các ô bị lỗi cho các trang vẫn đang được kết xuất. Ngân sách thử lại và các giá trị mặc định (retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs) được tài liệu hoá trong Cấu hình Viewer.

Cặp này — FirstPagePriority cộng với việc thăm dò 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.

Tùy chọn: xuất bản nền

Mặc định PublishAsync sẽ kết xuất trước khi trả về. Để trả về ngay khi mã thông báo được tạo và cho phép bộ xử lý thực hiện kết xuất vào cùng thư mục, đăng ký hàng đợi opt‑in:

csharp
builder.Services.AddDoconut(options => { /* … */ });
builder.Services.AddDoconutDistributedAsyncPublish();

Sau đó đưa vào hàng đợi thay vì xuất bản nội tuyến, và đặt Async = true trên các tùy chọn xuất bản. Hàng đợi có giới hạn: khi đầy, EnqueueAsync sẽ ném DistributedPublishQueueFullException. Đáp lại bằng một 503 và một header Retry-After để phần khách giảm tốc thay vì bị treo:

csharp
try
{
    var token = await _publishQueue.EnqueueAsync(
        path, 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 có đăng ký này, không có gì được đăng ký và việc xuất bản sẽ đồng bộ — đường dẫn mặc định vẫn giống hệt nhau từng byte.

Khi bật xuất bản nền, hãy cung cấp cho phía đọc một ReadReadyPollMs khác 0 để nó chờ một thời gian ngắn cho trang mà bộ xử lý chưa ghi.

Bảo mật trên các nút

Mã thông báo ràng buộc phiên giả định nút phục vụ đã nhìn thấy yêu cầu mở đầu. Trong một cụm, điều này không xảy ra, 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 vé truy cập có chữ ký, mà mỗi nút có thể xác minh độc lập:

csharp
builder.Services.AddDoconutDistributedDocumentSecurity(options => { /* … */ });

Không bao giờ mang 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 đường 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 một 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:

text
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)

Cung cấp cùng một FileShareConfig mà tài liệu đã được xuất bản bằng.

Bản đồ cài đặt theo cài đặt

TrướcHiện tại
UseDoconutWebFarm(new WebFarmOptions { … })PublishAsync(...) trên phía ghi cộng với UseDoconutCloud<FileShareCloudHandler>(...) trên phía đọc
WebFarmOptions.PathFileShareConfig.RootPath
PageWaitTimeSeconds / StartWaitFromPageDistributedDocumentPublishOptions.FirstPagePriority cộng với FileShareConfig.ReadReadyPollMs
viewer.OpenDocument(...) then viewer.ExportToPng(share)một cuộc 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 / WebfarmPathVẫn tồn tại, viết là IsWebFarm / WebFarmPath — không cần thiết trên đường xuất bản
Doconut.Configs.CloudDoconut.Clouds

Danh sách kiểm tra

  1. Xây dựng FileShareConfig trong một helper và gọi nó từ cả hai nửa.
  2. Chỉ định RootPath tới share mà bạn đã sử dụng.
  3. Xuất bản với PublishAsync(...); xóa mọi lời gọi ExportToPng / ExportToCloud.
  4. Phục vụ với UseDoconutCloud<FileShareCloudHandler>(...) trên nhánh mà client gọi.
  5. Bật FirstPagePriority và retryOn409 cùng lúc, hoặc để cả hai tắt.
  6. Thay thế kiểm tra per-session bằng vé truy cập có chữ ký — không gửi UnsafeMode = true riêng lẻ.
  7. Xác minh bằng cách mở một tài liệu trên một node và yêu cầu một trang trễ từ node khác.

Trang này có hữu ích không?