分布式部署
从多个节点提供同一文档
当多个节点提供相同的文档时,渲染文档的节点不一定是响应浏览器页面请求的节点。此版本将该工作拆分为两个明确的部分——写入端负责将文档的制品发布到共享存储,读取端负责从任意节点提供这些制品。
如果你的应用当前调用 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 写入,读取处理程序使用同样的配置提供服务。如果 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。该 token 会像普通查看器 token 一样直接返回给浏览器。
这一次调用取代了之前的两步——打开文档进入本地会话,然后使用 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 值切换。处理程序的命名规则为 <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 // 重试 202(以及旧版 409)就绪响应
});如果客户端 不 重试,则会看到仍在渲染的页面出现破碎的瓦片。重试预算及其默认值(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 以及 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。 - 通过在一个节点打开文档并在另一个节点请求后续页面来验证。
此页面对您有帮助吗?