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

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

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

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

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

چه چیزی جایگزین میدلور وب‌فارم شد

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

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

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 می‌نویسد و هندلر خواندن با یکی سرو می‌کند. اگر RootPath یا UsePerTokenSubfolders بین آن‌ها متفاوت باشند، گرهٔ دیگر 404 برمی‌گرداند. پیکربندی را دقیقاً در یک مکان بسازید و از هر دو طرف فراخوانی کنید — برنامهٔ مرجع یک کمک‌کنندهٔ واحد SharedStore.Config(...) برای این دلیل نگه می‌دارد:

csharp
internal static class SharedStore
{
    public static FileShareConfig Config(string rootPath, bool asyncPublish) => new()
    {
        Location              = CloudLocation.FileShare,
        RootPath              = rootPath,               // مثلاً \\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مدت زمانی که read side برای صفحه‌ای که هنوز نوشته می‌شود صبر می‌کند؛ 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(
    path, 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 در این نسخه عمومی است: ارائه‌دهنده به‌جای مقدار Location که داخل میدلور سوئیچ می‌شود، آرگومان نوع است. دستگیره‌ها با نام‌های <Provider>CloudHandler — FileShareCloudHandler، AzureCloudHandler، AmazonS3CloudHandler، GoogleCloudHandler، DropBoxCloudHandler، RedisCloudHandler، FTPCloudHandler و CDNCloudHandler نام‌گذاری شده‌اند.

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

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

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

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

javascript
const objViewer = $('#div_ctlDoc').docViewer({
    BasePath:   '/',
    ResPath:    'doconut-res/images',
    retryOn409: true      // تلاش‌های مجدد 202 (و 409 قدیمی) پاسخ‌های آمادگی
});

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

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

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

به‌طور پیش‌فرض PublishAsync پیش از بازگشت رندر می‌شود. برای بازگشت به‌محض صدور توکن و اجازه دادن به یک کارگر میزبانی‌شده برای رندر در همان پوشه، صف انتخابی را ثبت کنید:

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

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

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

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

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

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

توکن‌های وابسته به نشست فرض می‌کنند گرهٔ سرویس‌دهنده درخواست اولیه را دیده است. در یک مزرعه این‌طور نیست، بنابراین بررسی بر پایه نشست درخواست‌های معتبر را رد می‌کند. به همین دلیل برنامهٔ مرجع توزیعی 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(...) then 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. بررسی per‑session را با بلیت‌های دسترسی امضا‌شده جایگزین کنید — UnsafeMode = true را به تنهایی ارسال نکنید.
  7. با باز کردن یک سند در یک گره و درخواست صفحهٔ دیرهنگام از گرهٔ دیگر، صحت را تأیید کنید.

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