استقرارهای توزیع‌شده

یک سند را از چندین گره سرو کنید

هنگامی که چندین گره همان اسناد را سرو می‌کنند، گره‌ای که سند را رندر می‌کند لزوماً گره‌ای نیست که درخواست‌های صفحه مرورگر را پاسخ دهد. این نسخه کار را به دو نیمهٔ صریح تقسیم می‌کند — یک بخش نوشتن که آرتیفکت‌های سند را در ذخیره‌سازی مشترک منتشر می‌کند، و یک بخش خواندن که آن‌ها را از هر گره‌ای سرو می‌کند.

اگر برنامهٔ شما در حال حاضر UseDoconutWebFarm(...) را فراخوانی می‌کند، این صفحه جایگزین شماست. نه UseDoconutWebFarm و نه WebFarmOptions در این نسخه وجود دارند.

توپولوژی شما تغییر نمی‌کند. ریشهٔ مشترک را به همان مسیر UNC که امروز استفاده می‌کنید اشاره کنید و هر گره همان‌طور که اکنون کار می‌کند ادامه می‌دهد. تغییر این است که انتشار یک عملیات است که شما فراخوانی می‌کنید، نه یک اثر جانبی رندر کردن.

چه چیزی میدلویر web‑farm را جایگزین کرد

قبلاً یک میدلویر همه کارها را انجام می‌داد: تصاویر پیش‌رندر شده را از یک پوشهٔ مشترک سرو می‌کرد و پول می‌کرد در حالی که گرهٔ دیگری هنوز آن‌ها را می‌نوشت. انتظار یک بودجهٔ کور بود — «تا سه دقیقه بلوک بمانید تا صفحه ظاهر شود».

اکنون همان کار به دو فراخوانی تقسیم شده که می‌توانید ببینید و دربارهٔ آن‌ها استدلال کنید:

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

پیکربندی مشترکی که هر دو نیمه به آن نیاز دارند

ناشر با یک FileShareConfig می‌نویسد و هندلر خواندن با یک FileShareConfig سرو می‌کند. اگر RootPath یا UsePerTokenSubfolders بین آن‌ها متفاوت باشد، گرهٔ دیگر 404 برمی‌گرداند. پیکربندی را دقیقاً در یک مکان بسازید و از هر دو طرف فراخوانی کنید — برنامهٔ مرجع یک کمک‌کنندهٔ SharedStore.Config(...) تک دارد به همین دلیل:

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 در Doconut.Clouds زندگی می‌کند، همراه با AzureConfig, AmazonConfig, GoogleCloudConfig, DropBoxConfig, RedisConfig, CDNConfig و FTPConfig. در کتابخانهٔ قبلی این‌ها در Doconut.Configs.Cloud بودند؛ نام کلاس‌ها تغییر نکرده‌اند.

ویژگیهدف
LocationCloudLocation.FileShare برای یک اشتراک UNC/محلی
RootPathپوشهٔ مشترکی که هر گره می‌تواند به آن دسترسی داشته باشد
UsePerTokenSubfoldersآرتیفکت‌های هر سند را در پوشهٔ {token} خود نگه دارید
ReadReadyPollMsمدت زمانی که سمت خواندن برای صفحه‌ای که هنوز در حال نوشتن است صبر می‌کند؛ 0 نظرسنجی را غیرفعال می‌کند

بخش نوشتن — انتشار سند

DistributedDocumentPublisher را تزریق کنید و به جای باز کردن یک جلسهٔ محلی، سند را منتشر کنید. یک فراخوانی تمام مجموعهٔ آرتیفکت‌ها را رندر و می‌نویسد — صفحات، اطلاعات سند، ایندکس جستجو و متادیتا:

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 شامل Token، ArtifactPath، TotalPages و Format است. توکن دقیقاً همان‌طور که یک توکن نمایشگر عادی عمل می‌کند به مرورگر ارسال می‌شود.

این فراخوانی تک‌جایگزین جفت قبلی است — باز کردن سند در یک جلسهٔ محلی و سپس ریختن تصاویر روی اشتراک با ExportToPng(sharedPath). ExportToPng و ExportToCloud در اینجا معادل ندارند.

سمت خواندن — سرو از اشتراک

دستگیرهٔ FileShare را بر همان شاخه‌ای که مشتری قبلاً فراخوانی می‌کند، نصب کنید:

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 در این نسخه به‌صورت عمومی است: فراهم‌کننده به‌عنوان آرگومان نوع (type argument) مشخص می‌شود نه مقدار Location که داخل میدلور سوئیچ می‌شود. دستگیره‌ها به شکل <Provider>CloudHandler نام‌گذاری می‌شوند — FileShareCloudHandler، AzureCloudHandler، AmazonS3CloudHandler، GoogleCloudHandler، DropBoxCloudHandler، RedisCloudHandler، FTPCloudHandler و CDNCloudHandler.

دستگیرهٔ CDN فقط‑خواندنی است — آنچه در CDN موجود است را سرو می‌کند؛ آپلود نمی‌کند.

اولویت صفحهٔ اول و HTTP 202

FirstPagePriority = true باعث می‌شود صفحهٔ ۱ بلافاصله سرو شود. صفحات بعدی تا زمان نوشتن، پاسخ HTTP 202 Accepted می‌دهند، به‌جای مسدود کردن درخواست.

این کار انتظار را به سمت مشتری منتقل می‌کند، جایی که محدود و قابل مشاهده است. در ویجت قابلیت retry را فعال کنید:

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

مشتری که retry نمی‌کند، کاشی‌های شکسته برای صفحاتی که هنوز در حال رندر هستند نشان می‌دهد. بودجهٔ retry و مقادیر پیش‌فرض آن (retryInitialDelayMs، retryBackoffFactor، retryMaxDelayMs، retryMaxAttempts، retryMaxTotalMs) در ViewerConfig مستند شده‌اند.

این ترکیب — FirstPagePriority به‌اضافهٔ poll سمت خواندن — جایگزین تنظیمات قبلی PageWaitTimeSeconds و StartWaitFromPage می‌شود. دیگر هیچ‌کدام وجود ندارند.

اختیاری: انتشار در پس‌زمینه

به‌طور پیش‌فرض PublishAsync قبل از بازگشت رندر می‌شود. برای بازگشت بلافاصله پس از صدور توکن و اجازه دادن به یک worker میزبانی‌شده برای رندر در همان پوشه، صف opt‑in را ثبت کنید:

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

سپس به‌جای انتشار درون‌خطی، در صف قرار دهید و در گزینه‌های انتشار Async = true را تنظیم کنید. صف محدود است: وقتی اشباع می‌شود، EnqueueAsync استثنای DistributedPublishQueueFullException را پرتاب می‌کند. با یک پاسخ 503 و هدر Retry-After به آن پاسخ دهید تا مشتریان به‌جای گیر کردن، پس‌زمینه بگیرند:

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

بدون این ثبت، هیچ‌چیزی ثبت نمی‌شود و انتشار به‌صورت همزمان باقی می‌ماند — مسیر پیش‌فرض دقیقاً یک‌به‌یک مشابه است.

زمانی که انتشار پس‌زمینه فعال است، به سمت خواندن مقدار غیرصفر ReadReadyPollMs بدهید تا کمی برای صفحه‌ای که worker هنوز ننوشته است، صبر کند.

امنیت در میان گره‌ها

توکن‌های وابسته به جلسه فرض می‌کنند گرهٔ سرویس‌دهنده درخواست اولیه را دیده است. در یک فارم این‌گونه نیست، بنابراین بررسی هر جلسه درخواست‌های معتبر را رد می‌کند. به همین دلیل برنامهٔ مرجع توزیعی UnsafeMode = true را تنظیم می‌کند.

UnsafeMode = true مدل امنیتی نیست — بلکه حذف یک مدل است که دیگر اعمال نمی‌شود. آن را با بلیت‌های دسترسی امضا‌شده جایگزین کنید، که هر گره می‌تواند به‌صورت مستقل آن را تأیید کند:

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

هرگز UnsafeMode = true را به یک استقرار تک‌گره‌ای منتقل نکنید، و هرگز آن را به‌عنوان یک راه‌حل کوتاه برای بررسی جلسهٔ ناموفق در یک گره در نظر نگیرید — در آنجا اصلاح صحیح AddSession() / UseSession() است. ببینید جلسات و امنیت.

بستن یک سند منتشرشده

CloseDocument(token) روی Viewer تزریق‌شده یک جلسه محلی را از بین می‌برد. برای حذف آثار منتشر شدهٔ یک سند از اشتراک، ناشر این متد را ارائه می‌دهد:

text
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)

همان FileShareConfig که سند با آن منتشر شده است را پاس دهید.

نگاشت تنظیم‑به‑تنظیم

قبلیاکنون
UseDoconutWebFarm(new WebFarmOptions { … })PublishAsync(...) در سمت نوشتن به‌علاوه UseDoconutCloud<FileShareCloudHandler>(...) در سمت خواندن
WebFarmOptions.PathFileShareConfig.RootPath
PageWaitTimeSeconds / StartWaitFromPageDistributedDocumentPublishOptions.FirstPagePriority به‌علاوه FileShareConfig.ReadReadyPollMs
viewer.OpenDocument(...) سپس viewer.ExportToPng(share)یک فراخوانی PublishAsync(...)
viewer.ExportToCloud(cloudConfig)PublishAsync(...) با تنظیمات ارائه‌دهنده
UseDoconutCloud(new CloudOptions { Location = … })UseDoconutCloud<THandler>(options => …, pathPrefix)
DocOptions.IsWebfarm / WebfarmPathهنوز موجود است، به صورت IsWebFarm / WebFarmPath نام‌گذاری شده — نیازی به آن در مسیر انتشار نیست
Doconut.Configs.CloudDoconut.Clouds

فهرست بررسی

  1. ساخت FileShareConfig در یک کمک‌کننده و فراخوانی آن از هر دو نیمه.
  2. RootPath را به‌سوی اشتراک‌ای که قبلاً استفاده می‌کنید تنظیم کنید.
  3. با PublishAsync(...) منتشر کنید؛ تمام فراخوانی‌های ExportToPng / ExportToCloud را حذف کنید.
  4. در شاخه‌ای که مشتری فراخوانی می‌کند، با UseDoconutCloud<FileShareCloudHandler>(...) سرو کنید.
  5. FirstPagePriority و retryOn409 را همزمان فعال کنید، یا هر دو را غیرفعال بگذارید.
  6. بررسی هر جلسه را با بلیت‌های دسترسی امضاشده جایگزین کنید — UnsafeMode = true را به‌تنهایی ارسال نکنید.
  7. با باز کردن یک سند در یک گره و درخواست صفحه‌ای دیرهنگام از گره دیگر، تأیید کنید.

آیا این صفحه مفید بود؟