分布式部署

从多个节点提供同一文档

当多个节点提供相同的文档时,渲染文档的节点不一定是响应浏览器页面请求的节点。此版本将该工作拆分为两个明确的部分——写入端负责将文档的制品发布到共享存储,读取端负责从任意节点提供这些制品。

如果你的应用当前调用 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;类名未发生变化。

属性用途
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。该 token 会像普通查看器 token 一样直接返回给浏览器。

这一次调用取代了之前的两步——打开文档进入本地会话,然后使用 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 使第 1 页可以立即提供服务。后续页面在写入完成之前会返回 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(
        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 以及 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. 通过在一个节点打开文档并在另一个节点请求后续页面来验证。

此页面对您有帮助吗?