การปรับใช้แบบกระจาย

ให้บริการเอกสารหนึ่งจากหลายโหนด

เมื่อหลายโหนดให้บริการเอกสารเดียวกัน โหนดที่ทำการเรนเดอร์เอกสารไม่ได้จำเป็นต้องเป็นโหนดที่ตอบสนองคำขอหน้าเว็บของเบราว์เซอร์ รุ่นนี้แบ่งงานนั้นออกเป็นสองส่วนที่ชัดเจน — ด้านการเขียน ที่เผยแพร่ศิลปวัตถุของเอกสารไปยังที่เก็บข้อมูลร่วม, และ ด้านการอ่าน ที่ให้บริการจากโหนดใดก็ได้

หากแอปพลิเคชันของคุณในขณะนี้เรียก 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 และตัวจัดการการอ่านให้บริการด้วยอันเดียวกัน หาก 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; ชื่อคลาสไม่ได้เปลี่ยนแปลง.

PropertyPurpose
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 เป็น generic ในรุ่นนี้: ผู้ให้บริการเป็น type argument แทนค่าของ 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
});

ไคลเอนต์ที่ ไม่ ลองใหม่จะแสดงแผ่นภาพที่เสียสำหรับหน้าที่ยังกำลังเรนเดอร์อยู่. งบประมาณการลองใหม่และค่าเริ่มต้นของมัน (retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs) ถูกอธิบายไว้ใน การกำหนดค่า Viewer.

คู่นี้ — FirstPagePriority พร้อมกับการโพลด้านการอ่าน — คือสิ่งที่แทนที่การตั้งค่า PageWaitTimeSeconds และ StartWaitFromPage ก่อนหน้า. ทั้งสองไม่มีอยู่แล้ว

ตัวเลือก: เผยแพร่ในพื้นหลัง

โดยค่าเริ่มต้น PublishAsync จะเรนเดอร์ก่อนที่จะคืนค่า. เพื่อให้คืนค่าโดยเร็วที่สุดเมื่อโทเค็นถูกสร้างและให้ worker ที่โฮสต์เรนเดอร์ลงในโฟลเดอร์เดียวกัน, ลงทะเบียนคิวที่เลือกใช้:

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

จากนั้นใช้ enqueue แทนการเผยแพร่แบบอินไลน์, และตั้งค่า 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.");
}

หากไม่มีการลงทะเบียนนี้ จะไม่มีการลงทะเบียนใด ๆ และการเผยแพร่จะทำแบบ synchronous — เส้นทางเริ่มต้นจะเหมือนกันแบบไบต์ต่อไบต์

เมื่อเปิดการเผยแพร่แบบพื้นหลัง, ให้ด้านการอ่านมีค่า ReadReadyPollMs ที่ไม่เป็นศูนย์เพื่อให้รอสั้น ๆ สำหรับหน้าที่ worker ยังไม่ได้เขียน

ความปลอดภัยข้ามโหนด

โทเค็นที่ผูกกับเซสชันสมมติว่าโหนดที่ให้บริการเห็นคำขอเริ่มต้น. ในฟาร์มโหนดอาจไม่เห็น, ดังนั้นการตรวจสอบต่อเซสชันจะปฏิเสธคำขอที่ถูกต้อง. นั่นคือเหตุผลที่แอปอ้างอิงแบบกระจายตั้งค่า UnsafeMode = true

UnsafeMode = true ไม่ใช่โมเดลความปลอดภัย — มันเป็นการลบโมเดลที่ไม่ใช้แล้ว. แทนที่ด้วยตั๋วการเข้าถึงที่ลงลายเซ็น, ซึ่งแต่ละโหนดสามารถตรวจสอบได้อย่างอิสระ:

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

อย่าใช้ UnsafeMode = true ในการปรับใช้แบบโหนดเดียว, และอย่าใช้เป็นทางลัดสำหรับการตรวจสอบเซสชันที่ล้มเหลวบนโหนดเดียว — ที่นั่นการแก้ไขที่ถูกต้องคือ AddSession() / UseSession(). ดูที่ เซสชันและความปลอดภัย.

ปิดเอกสารที่เผยแพร่แล้ว

CloseDocument(token) บน Viewer ที่ฉีดเข้าไปทำการกำจัดเซสชันท้องถิ่น เพื่อเอา artefacts ที่เผยแพร่ของเอกสารออกจาก share, ผู้เผยแพร่ให้ฟังก์ชัน:

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 ให้ชี้ไปที่ share ที่คุณใช้อยู่แล้ว
  3. เผยแพร่ด้วย PublishAsync(...); ลบการเรียก ExportToPng / ExportToCloud ใด ๆ
  4. ให้บริการด้วย UseDoconutCloud<FileShareCloudHandler>(…) บนสาขาที่ไคลเอนท์เรียกใช้
  5. เปิด FirstPagePriority และ retryOn409 พร้อมกัน, หรือปิดทั้งสอง
  6. แทนที่การตรวจสอบต่อเซสชันด้วยตั๋วการเข้าถึงที่ลงลายมือชื่อ — อย่าส่ง UnsafeMode = true เพียงอย่างเดียว
  7. ตรวจสอบโดยการเปิดเอกสารบนโหนดหนึ่งและขอหน้าที่ล่าช้าจากอีกโหนดหนึ่ง

หน้านี้เป็นประโยชน์หรือไม่?