פריסות מבוזרות

הגש מסמך אחד ממספר צמתים

כאשר כמה צמתים משרתים את אותם המסמכים, הצומת שמרנדר את המסמך אינו בהכרח הצומת שמגיב לבקשות העמוד של הדפדפן. גרסה זו מחלקת את המשימה לשניים — צד כתיבה שמפרסם את האמנות של המסמך באחסון משותף, ו-צד קריאה שמשרת אותם מכל צומת.

אם היישום שלך כרגע קורא ל-UseDoconutWebFarm(...), דף זה הוא ההחלפה עבורך. גם UseDoconutWebFarm וגם WebFarmOptions אינם קיימים בגרסה זו.

הטופולוגיה שלך לא משתנה. הפנה את השורש המשותף לאותו נתיב UNC שבו אתה משתמש היום וכל צומת ממשיך לפעול כפי שהוא פועל כעת. מה שמשתנה הוא שהפרסום הוא פעולה שאתה קורא לה, במקום להיות תופעת לוואי של הרינדור.

מה החליף את ה‑middleware של web‑farm

בפעם הקודמת middleware אחד עשה את הכל: הוא שירת תמונות מרונדרות מראש מתיקייה משותפת ו-סקר בזמן שצומת אחר עדיין כתב אותן. ההמתנה הייתה תקציב עיוור — "חסום עד שלוש דקות בתקווה שהדף יופיע".

כעת אותה משימה היא שני קריאות שאתה יכול לראות ולנתח:

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,               // 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 הוא גנרי בגרסה זו: הספק הוא ארגומנט הסוג במקום ערך Location שמוחלף בתוך ה‑middleware. המטפלים נקראים <Provider>CloudHandler — FileShareCloudHandler, AzureCloudHandler, AmazonS3CloudHandler, GoogleCloudHandler, DropBoxCloudHandler, RedisCloudHandler, FTPCloudHandler, ו‑CDNCloudHandler.

מטפל ה‑CDN הוא קריאה‑בלבד — הוא משרת את מה שכבר נמצא ב‑CDN; הוא אינו מעלה.

עדיפות לדף הראשון ו‑HTTP 202

FirstPagePriority = true גורם לדף 1 להיות ניתן לשירות מיידית. דפים מאוחרים יותר משיבים HTTP 202 Accepted עד שהם נכתבים, במקום לחסום את הבקשה.

זה מעביר את ההמתנה ללקוח, שם היא מוגבלת וגלויה. אפשרו ניסיון חוזר על הווידג'ט:

javascript
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 מרנדר לפני שהוא מחזיר. כדי לחזור ברגע שהטוקן נוצר ולאפשר לעובד מתארח לרנדר באותו תיקייה, רשמו את התור המופעל:

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 שאינו אפס כדי שהוא ימתין בקצרה לדף שהעובד עדיין לא כתב.

אבטחה בין צמתים

טוקנים קשורים למושב מניחים שהצומת המשרת ראה את בקשת הפתיחה. בחווה זה לא קורה, ולכן הבדיקה לכל מושב הייתה דוחה בקשות לגיטימיות. זו הסיבה שהיישום ההפניה המפוזר מגדיר 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 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.CloudDoconut.Clouds

רשימת בדיקה

  1. בנה את FileShareConfig בעוזר אחד וקרא לו משני החצאים.
  2. הצב את RootPath על השיתוף שאתה כבר משתמש בו.
  3. פרסם עם PublishAsync(...); מחק כל קריאות ExportToPng / ExportToCloud.
  4. הגש עם UseDoconutCloud<FileShareCloudHandler>(...) על הסניף שהלקוח קורא.
  5. הפעל את FirstPagePriority ו-retryOn409 יחד, או השאר את שניהם כבויים.
  6. החלף את הבדיקה לכל סשן בכרטיסי גישה חתומים — אל תשלח UnsafeMode = true לבד.
  7. אמת על ידי פתיחת מסמך בצומת אחד ובקשת דף מאוחר מצומת אחר.

האם דף זה היה מועיל?