استقرارهای توزیعشده
یک سند را از چندین گره سرو کنید
هنگامی که چندین گره همان اسناد را سرو میکنند، گرهای که سند را رندر میکند لزوماً گرهای نیست که درخواستهای صفحه مرورگر را پاسخ دهد. این نسخه کار را به دو نیمهٔ صریح تقسیم میکند — یک بخش نوشتن که آرتیفکتهای سند را در ذخیرهسازی مشترک منتشر میکند، و یک بخش خواندن که آنها را از هر گرهای سرو میکند.
اگر برنامهٔ شما در حال حاضر UseDoconutWebFarm(...) را فراخوانی میکند، این صفحه جایگزین شماست. نه UseDoconutWebFarm و نه WebFarmOptions در این نسخه وجود دارند.
توپولوژی شما تغییر نمیکند. ریشهٔ مشترک را به همان مسیر UNC که امروز استفاده میکنید اشاره کنید و هر گره همانطور که اکنون کار میکند ادامه میدهد. تغییر این است که انتشار یک عملیات است که شما فراخوانی میکنید، نه یک اثر جانبی رندر کردن.
چه چیزی میدلویر web‑farm را جایگزین کرد
قبلاً یک میدلویر همه کارها را انجام میداد: تصاویر پیشرندر شده را از یک پوشهٔ مشترک سرو میکرد و پول میکرد در حالی که گرهٔ دیگری هنوز آنها را مینوشت. انتظار یک بودجهٔ کور بود — «تا سه دقیقه بلوک بمانید تا صفحه ظاهر شود».
اکنون همان کار به دو فراخوانی تقسیم شده که میتوانید ببینید و دربارهٔ آنها استدلال کنید:
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(...) تک دارد به همین دلیل:
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 | مدت زمانی که سمت خواندن برای صفحهای که هنوز در حال نوشتن است صبر میکند؛ 0 نظرسنجی را غیرفعال میکند |
بخش نوشتن — انتشار سند
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 در این نسخه بهصورت عمومی است: فراهمکننده بهعنوان آرگومان نوع (type argument) مشخص میشود نه مقدار Location که داخل میدلور سوئیچ میشود. دستگیرهها به شکل <Provider>CloudHandler نامگذاری میشوند — FileShareCloudHandler، AzureCloudHandler، AmazonS3CloudHandler، GoogleCloudHandler، DropBoxCloudHandler، RedisCloudHandler، FTPCloudHandler و CDNCloudHandler.
دستگیرهٔ CDN فقط‑خواندنی است — آنچه در CDN موجود است را سرو میکند؛ آپلود نمیکند.
اولویت صفحهٔ اول و HTTP 202
FirstPagePriority = true باعث میشود صفحهٔ ۱ بلافاصله سرو شود. صفحات بعدی تا زمان نوشتن، پاسخ HTTP 202 Accepted میدهند، بهجای مسدود کردن درخواست.
این کار انتظار را به سمت مشتری منتقل میکند، جایی که محدود و قابل مشاهده است. در ویجت قابلیت retry را فعال کنید:
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 را ثبت کنید:
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 بدهید تا کمی برای صفحهای که worker هنوز ننوشته است، صبر کند.
امنیت در میان گرهها
توکنهای وابسته به جلسه فرض میکنند گرهٔ سرویسدهنده درخواست اولیه را دیده است. در یک فارم اینگونه نیست، بنابراین بررسی هر جلسه درخواستهای معتبر را رد میکند. به همین دلیل برنامهٔ مرجع توزیعی 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(...) سپس 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را بهتنهایی ارسال نکنید. - با باز کردن یک سند در یک گره و درخواست صفحهای دیرهنگام از گره دیگر، تأیید کنید.
آیا این صفحه مفید بود؟