פריסות מבוזרות
הגש מסמך אחד ממספר צמתים
כאשר כמה צמתים משרתים את אותם המסמכים, הצומת שמרנדר את המסמך אינו בהכרח הצומת שמגיב לבקשות העמוד של הדפדפן. גרסה זו מחלקת את המשימה לשניים — צד כתיבה שמפרסם את האמנות של המסמך באחסון משותף, ו-צד קריאה שמשרת אותם מכל צומת.
אם היישום שלך כרגע קורא ל-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 | כמה זמן צד הקריאה מחכה לעמוד שעדיין נכתב; 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 הוא גנרי בגרסה זו: הספק הוא ארגומנט הסוג במקום ערך Location שמוחלף בתוך ה‑middleware. המטפלים נקראים <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
});לקוח שלא מבצע retry מציג אריחים שבורים עבור דפים שעדיין מתרשמים. תקציב הניסיונות החוזרים והברירות המחדל שלו (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 שאינו אפס כדי שהוא ימתין בקצרה לדף שהעובד עדיין לא כתב.
אבטחה בין צמתים
טוקנים קשורים למושב מניחים שהצומת המשרת ראה את בקשת הפתיחה. בחווה זה לא קורה, ולכן הבדיקה לכל מושב הייתה דוחה בקשות לגיטימיות. זו הסיבה שהיישום ההפניה המפוזר מגדיר 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 plus 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לבד. - אמת על ידי פתיחת מסמך בצומת אחד ובקשת דף מאוחר מצומת אחר.
האם דף זה היה מועיל?