Rozproszone wdrożenia

Serwuj jeden dokument z kilku węzłów

Gdy kilka węzłów serwuje te same dokumenty, węzeł, który renderuje dokument, nie musi być tym węzłem, który odpowiada na żądania stron przeglądarki. To wydanie dzieli to zadanie na dwie wyraźne części — stronę zapisu, która publikuje artefakty dokumentu w udostępnionym magazynie, oraz stronę odczytu, która serwuje je z dowolnego węzła.

Jeśli Twoja aplikacja obecnie wywołuje UseDoconutWebFarm(...), ta strona jest jej zamiennikiem. Ani UseDoconutWebFarm, ani WebFarmOptions nie istnieją w tym wydaniu.

Twoja topologia nie zmienia się. Wskaż udostępniony katalog główny na tę samą ścieżkę UNC, której używasz dziś, i każdy węzeł będzie działał tak, jak dotąd. Zmiana polega na tym, że publikowanie jest operacją, którą wywołujesz, zamiast skutkiem ubocznym renderowania.

Co zastąpiło middleware web-farm

Wcześniej jedno middleware robiło wszystko: serwowało wstępnie wyrenderowane obrazy z udostępnionego folderu i pollowało, podczas gdy inny węzeł nadal je zapisywał. Oczekiwanie było ślepym budżetem — „zablokuj do trzech minut, mając nadzieję, że strona się pojawi”.

Teraz to samo zadanie to dwa wywołania, które możesz zobaczyć i zrozumieć:

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

Wspólna konfiguracja potrzebna obu części

Publikator zapisuje przy użyciu FileShareConfig, a obsługa odczytu serwuje przy użyciu tego samego. Jeśli RootPath lub UsePerTokenSubfolders różnią się między nimi, drugi węzeł zwraca 404. Zbuduj konfigurację w dokładnie jednym miejscu i wywołuj ją z obu stron — aplikacja referencyjna utrzymuje pojedynczy pomocnik SharedStore.Config(...) z tego powodu:

csharp
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 znajduje się w Doconut.Clouds, razem z AzureConfig, AmazonConfig, GoogleCloudConfig, DropBoxConfig, RedisConfig, CDNConfig i FTPConfig. W poprzedniej bibliotece były one w Doconut.Configs.Cloud; nazwy klas nie uległy zmianie.

WłaściwośćCel
LocationCloudLocation.FileShare dla udziału UNC/lokalnego
RootPathUdostępniony folder, do którego może dotrzeć każdy węzeł
UsePerTokenSubfoldersPrzechowuj artefakty każdego dokumentu w jego własnym folderze {token}
ReadReadyPollMsJak długo strona odczytu czeka na stronę, która jest jeszcze zapisywana; 0 wyłącza polling

Strona zapisu — publikowanie dokumentu

Wstrzyknij DistributedDocumentPublisher i publikuj zamiast otwierać lokalną sesję. Jedno wywołanie renderuje i zapisuje kompletny zestaw artefaktów — strony, informacje o dokumencie, indeks wyszukiwania i metadane:

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

return Content(result.Token);

DistributedDocumentPublishResult zawiera Token, ArtifactPath, TotalPages i Format. Token jest przekazywany do przeglądarki dokładnie tak, jak zwykły token przeglądarki.

To pojedyncze wywołanie zastępuje poprzednią parę — otwieranie dokumentu w lokalnej sesji i następnie zrzucanie obrazów na udział przy użyciu ExportToPng(sharedPath). ExportToPng i ExportToCloud nie mają tutaj odpowiedników.

Strona odczytu — serwuj z udostępnienia

Zamontuj obsługę FileShare w tej samej gałęzi, którą już wywołuje klient:

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 jest generyczny w tej wersji: dostawca jest argumentem typu, a nie wartością Location przełączaną wewnątrz middleware. Obsługiwacze noszą nazwę <Provider>CloudHandler — FileShareCloudHandler, AzureCloudHandler, AmazonS3CloudHandler, GoogleCloudHandler, DropBoxCloudHandler, RedisCloudHandler, FTPCloudHandler i CDNCloudHandler.

Obsługa CDN jest tylko do odczytu — serwuje to, co już znajduje się w CDN; nie dokonuje przesyłania.

Priorytet pierwszej strony i HTTP 202

FirstPagePriority = true powoduje, że strona 1 jest dostępna od razu. Późniejsze strony zwracają HTTP 202 Accepted, dopóki nie zostaną zapisane, zamiast blokować żądanie.

Przenosi to oczekiwanie na klienta, gdzie jest ograniczone i widoczne. Włącz ponawianie w widżecie:

javascript
const objViewer = $('#div_ctlDoc').docViewer({
    BasePath:   '/',
    ResPath:    'doconut-res/images',
    retryOn409: true      // retries 202 (and legacy 409) readiness responses
});

Klient, który nie ponawia, wyświetla uszkodzone kafelki dla stron, które nadal są renderowane. Budżet ponowień i jego domyślne wartości (retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs) są opisane w ViewerConfig.

Ta para — FirstPagePriority oraz odpytywanie po stronie odczytu — zastępuje poprzednie ustawienia PageWaitTimeSeconds i StartWaitFromPage. Żadne z nich już nie istnieje.

Opcjonalnie: publikowanie w tle

Domyślnie PublishAsync renderuje przed zwróceniem wyniku. Aby zwrócić odpowiedź natychmiast po wygenerowaniu tokenu i pozwolić hostowanemu pracownikowi renderować do tego samego folderu, zarejestruj kolejkę z opcją opt‑in:

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

Następnie umieszczaj w kolejce zamiast publikować bezpośrednio i ustaw Async = true w opcjach publikacji. Kolejka jest ograniczona: gdy jest nasycona, EnqueueAsync rzuca DistributedPublishQueueFullException. Odpowiedz kodem 503 i nagłówkiem Retry-After, aby klienci odczekali zamiast zablokować się:

csharp
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.");
}

Bez tej rejestracji nic nie jest zarejestrowane i publikowanie pozostaje synchroniczne — domyślna ścieżka jest identyczna bajt po bajcie.

Gdy publikowanie w tle jest włączone, ustaw po stronie odczytu niezerowy ReadReadyPollMs, aby krótkotrwale czekał na stronę, której pracownik jeszcze nie zapisał.

Bezpieczeństwo pomiędzy węzłami

Tokeny powiązane z sesją zakładają, że węzeł obsługujący widział początkowe żądanie. W farmie tak nie jest, więc sprawdzenie per‑sesja odrzucałoby prawidłowe żądania. Dlatego rozproszona aplikacja referencyjna ustawia UnsafeMode = true.

UnsafeMode = true nie jest modelem bezpieczeństwa — jest to usunięcie modelu, który już nie ma zastosowania. Zastąp go podpisanymi biletami dostępu, które każdy węzeł może weryfikować niezależnie:

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

Nigdy nie przenoś UnsafeMode = true do wdrożenia jednowęzłowego i nigdy nie traktuj go jako obejścia nieudanego sprawdzenia sesji na jednym węźle — w takim przypadku właściwą poprawką jest AddSession() / UseSession(). Zobacz Sesje i bezpieczeństwo.

Zamknięcie opublikowanego dokumentu

CloseDocument(token) na wstrzykniętym Viewer zamyka lokalną sesję. Aby usunąć opublikowane artefakty dokumentu z udziału, wydawca udostępnia:

text
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)

Przekaż ten sam FileShareConfig, z którym dokument został opublikowany.

Mapowanie ustawień po ustawieniu

PoprzedniTeraz
UseDoconutWebFarm(new WebFarmOptions { … })PublishAsync(...) po stronie zapisu plus UseDoconutCloud<FileShareCloudHandler>(...) po stronie odczytu
WebFarmOptions.PathFileShareConfig.RootPath
PageWaitTimeSeconds / StartWaitFromPageDistributedDocumentPublishOptions.FirstPagePriority plus FileShareConfig.ReadReadyPollMs
viewer.OpenDocument(...) then viewer.ExportToPng(share)jedno wywołanie PublishAsync(...)
viewer.ExportToCloud(cloudConfig)PublishAsync(...) z konfiguracją dostawcy
UseDoconutCloud(new CloudOptions { Location = … })UseDoconutCloud<THandler>(options => …, pathPrefix)
DocOptions.IsWebfarm / WebfarmPathWciąż obecne, zapisane jako IsWebFarm / WebFarmPath — nie potrzebne w ścieżce publikacji
Doconut.Configs.CloudDoconut.Clouds

Lista kontrolna

  1. Zbuduj FileShareConfig w jednym pomocniku i wywołaj go z obu części.
  2. Ustaw RootPath na udział, którego już używasz.
  3. Publikuj za pomocą PublishAsync(...); usuń wszystkie wywołania ExportToPng / ExportToCloud.
  4. Serwuj przy użyciu UseDoconutCloud<FileShareCloudHandler>(...) na gałęzi, którą wywołuje klient.
  5. Włącz FirstPagePriority i retryOn409 razem, albo wyłącz oba.
  6. Zastąp sprawdzanie per sesji podpisanymi biletami dostępu — nie dostarczaj samego UnsafeMode = true.
  7. Zweryfikuj, otwierając dokument na jednym węźle i żądając późniejszej strony z innego.

Czy ta strona była pomocna?