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ć:
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 pageWspó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:
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 |
|---|---|
Location | CloudLocation.FileShare dla udziału UNC/lokalnego |
RootPath | Udostępniony folder, do którego może dotrzeć każdy węzeł |
UsePerTokenSubfolders | Przechowuj artefakty każdego dokumentu w jego własnym folderze {token} |
ReadReadyPollMs | Jak 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:
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:
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:
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:
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ę:
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:
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:
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)Przekaż ten sam FileShareConfig, z którym dokument został opublikowany.
Mapowanie ustawień po ustawieniu
| Poprzedni | Teraz |
|---|---|
UseDoconutWebFarm(new WebFarmOptions { … }) | PublishAsync(...) po stronie zapisu plus UseDoconutCloud<FileShareCloudHandler>(...) po stronie odczytu |
WebFarmOptions.Path | FileShareConfig.RootPath |
PageWaitTimeSeconds / StartWaitFromPage | DistributedDocumentPublishOptions.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 / WebfarmPath | Wciąż obecne, zapisane jako IsWebFarm / WebFarmPath — nie potrzebne w ścieżce publikacji |
Doconut.Configs.Cloud | Doconut.Clouds |
Lista kontrolna
- Zbuduj
FileShareConfigw jednym pomocniku i wywołaj go z obu części. - Ustaw
RootPathna udział, którego już używasz. - Publikuj za pomocą
PublishAsync(...); usuń wszystkie wywołaniaExportToPng/ExportToCloud. - Serwuj przy użyciu
UseDoconutCloud<FileShareCloudHandler>(...)na gałęzi, którą wywołuje klient. - Włącz
FirstPagePriorityiretryOn409razem, albo wyłącz oba. - Zastąp sprawdzanie per sesji podpisanymi biletami dostępu — nie dostarczaj samego
UnsafeMode = true. - Zweryfikuj, otwierając dokument na jednym węźle i żądając późniejszej strony z innego.
Czy ta strona była pomocna?