Розподілені розгортання
Обслуговуйте один документ з кількох вузлів
Коли кілька вузлів обслуговують одні й ті ж документи, вузол, який рендерить документ, не обов’язково є тим вузлом, який відповідає на запити браузера щодо сторінок. Це випуск розділяє цю задачу на дві чіткі частини — write side, який публікує артефакти документа у спільне сховище, та read side, який обслуговує їх з будь‑якого вузла.
Якщо ваш застосунок зараз викликає UseDoconutWebFarm(...), ця сторінка є його заміною. Ні UseDoconutWebFarm, ні WebFarmOptions не існують у цьому випуску.
Ваша топологія не змінюється. Вкажіть спільний кореневий шлях на той самий UNC‑шлях, який ви використовуєте сьогодні, і кожен вузол продовжуватиме працювати так, як зараз. Змінюється лише те, що публікація — це операція, яку ви викликаєте, а не побічний ефект рендерингу.
Що замінило middleware web‑farm
Раніше один middleware робив усе: він обслуговував попередньо відрендерені зображення з спільної папки і опитував, поки інший вузол ще записує їх. Очікування було сліпим бюджетом — «блокувати до трьох хвилин у надії, що сторінка з’явиться».
Тепер та сама задача — це два виклики, які ви можете бачити та розуміти:
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Спільна конфігурація, потрібна обом частинам
Публікатор записує за допомогою FileShareConfig, а обробник читання обслуговує його за допомогою того ж. Якщо RootPath або UsePerTokenSubfolders відрізняються між ними, інший вузол відповідає 404. Створюйте конфігурацію в одному місці і викликайте її з обох сторін — референсний застосунок зберігає один допоміжний метод SharedStore.Config(...) з цієї причини:
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 знаходиться в Doconut.Clouds разом з AzureConfig, AmazonConfig, GoogleCloudConfig, DropBoxConfig, RedisConfig, CDNConfig та FTPConfig. У попередній бібліотеці вони були в Doconut.Configs.Cloud; назви класів не змінилися.
| Властивість | Призначення |
|---|---|
Location | CloudLocation.FileShare для UNC/локального шару |
RootPath | Спільна папка, до якої має доступ кожен вузол |
UsePerTokenSubfolders | Зберігати артефакти кожного документа у власній папці {token} |
ReadReadyPollMs | Тривалість очікування read side на сторінку, яка ще записується; 0 вимикає опитування |
Write side — публікація документа
Впровадьте DistributedDocumentPublisher і публікуйте замість відкриття локальної сесії. Один виклик рендерить і записує повний набір артефактів — сторінки, інформацію про документ, індекс пошуку та метадані:
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 містить Token, ArtifactPath, TotalPages та Format. Токен передається браузеру точно так само, як звичайний токен переглядача.
Цей один виклик замінює попередню пару — відкриття документа у локальну сесію та подальше вивантаження зображень у спільну папку за допомогою ExportToPng(sharedPath). ExportToPng та ExportToCloud тут не мають еквіваленту.
Читальна сторона — обслуговування з спільного сховища
Розмістіть обробник FileShare на тій же гілці, яку вже викликає клієнт:
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 є узагальненим у цьому випуску: провайдер передається як типовий аргумент, а не як значення Location, яке перемикається всередині проміжного програмного забезпечення. Обробники називаються <Provider>CloudHandler — FileShareCloudHandler, AzureCloudHandler, AmazonS3CloudHandler, GoogleCloudHandler, DropBoxCloudHandler, RedisCloudHandler, FTPCloudHandler і CDNCloudHandler.
Обробник CDN є лише для читання — він обслуговує те, що вже знаходиться на CDN; він не завантажує.
Пріоритет першої сторінки та HTTP 202
FirstPagePriority = true дозволяє сторінці 1 бути доступною одразу. Пізніші сторінки відповідають HTTP 202 Accepted, доки вони не будуть записані, замість блокування запиту.
Це переносить очікування на клієнт, де воно обмежене та видиме. Увімкніть повторні спроби у віджеті:
const objViewer = $('#div_ctlDoc').docViewer({
BasePath: '/',
ResPath: 'doconut-res/images',
retryOn409: true // retries 202 (and legacy 409) readiness responses
});Клієнт, який не виконує повторних спроб, показує пошкоджені плитки для сторінок, які ще рендеряться. Бюджет повторних спроб і його типові значення (retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs) задокументовані в КонфігураціяПереглядача.
Ця пара — FirstPagePriority плюс опитування читальної сторони — замінює попередні налаштування PageWaitTimeSeconds і StartWaitFromPage. Жодне з них більше не існує.
Необов’язково: публікація у фоні
За замовчуванням PublishAsync виконує рендеринг перед поверненням. Щоб повернутися одразу після створення токена і дозволити розгорнутому робітнику рендерити в ту ж папку, зареєструйте чергу за згодою:
builder.Services.AddDoconut(options => { /* … */ });
builder.Services.AddDoconutDistributedAsyncPublish();Потім додавайте завдання в чергу замість інлайн‑публікації та встановіть Async = true у параметрах публікації. Черга обмежена: коли вона переповнюється, EnqueueAsync викидає DistributedPublishQueueFullException. Відповідайте на це 503 і заголовком Retry-After, щоб клієнти зменшували навантаження, а не зависали:
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.");
}Без цієї реєстрації нічого не реєструється, і публікація залишається синхронною — шлях за замовчуванням ідентичний побайтово.
Коли фонова публікація ввімкнена, задайте читальній стороні ненульове ReadReadyPollMs, щоб вона коротко чекала на сторінку, яку робітник ще не записав.
Безпека між вузлами
Токени, прив’язані до сеансу, передбачають, що вузол, що обслуговує, бачив початковий запит. У фермі це не так, тому перевірка per‑session відхиляє легітимні запити. Ось чому розподілене довідкове застосування встановлює UnsafeMode = true.
UnsafeMode = true не є моделлю безпеки — це видалення моделі, яка більше не застосовується. Замініть її підписаними квитками доступу, які кожен вузол може перевіряти незалежно:
builder.Services.AddDoconutDistributedDocumentSecurity(options => { /* … */ });Ніколи не переносіть UnsafeMode = true у розгортання з одним вузлом і ніколи не використовуйте його як короткий шлях для невдалої перевірки сеансу на одному вузлі — у цьому випадку правильне рішення — AddSession() / UseSession(). Дивіться Сесії та безпека.
Закриття опублікованого документа
CloseDocument(token) на інжектованому Viewer завершує локальну сесію. Щоб видалити опубліковані артефакти документа з ресурсу, видавець надає:
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)Передайте той самий FileShareConfig, яким документ був опублікований.
Відповідність налаштувань
| Попереднє | Тепер |
|---|---|
UseDoconutWebFarm(new WebFarmOptions { … }) | PublishAsync(...) на боці запису, а також UseDoconutCloud<FileShareCloudHandler>(…) на боці читання |
WebFarmOptions.Path | FileShareConfig.RootPath |
PageWaitTimeSeconds / StartWaitFromPage | DistributedDocumentPublishOptions.FirstPagePriority плюс FileShareConfig.ReadReadyPollMs |
viewer.OpenDocument(...) then viewer.ExportToPng(share) | один виклик PublishAsync(...) |
viewer.ExportToCloud(cloudConfig) | PublishAsync(...) з конфігурацією провайдера |
UseDoconutCloud(new CloudOptions { Location = … }) | UseDoconutCloud<THandler>(options => …, pathPrefix) |
DocOptions.IsWebfarm / WebfarmPath | Все ще присутні, названі IsWebFarm / WebFarmPath — не потрібні у шляху публікації |
Doconut.Configs.Cloud | Doconut.Clouds |
Контрольний список
- Створіть
FileShareConfigв одному допоміжному засобі та викликайте його з обох частин. - Вкажіть
RootPathна ресурс, який ви вже використовуєте. - Опублікуйте за допомогою
PublishAsync(...); видаліть усі викликиExportToPng/ExportToCloud. - Обслуговуйте за допомогою
UseDoconutCloud<FileShareCloudHandler>(...)у гілці, яку викликає клієнт. - Увімкніть
FirstPagePriorityіretryOn409одночасно, або залиште їх вимкненими. - Замініть перевірку на кожну сесію підписаними квитками доступу — не розповсюджуйте лише
UnsafeMode = true. - Перевірте, відкривши документ на одному вузлі та запросивши пізню сторінку з іншого.
Чи була ця сторінка корисною?