Implantações Distribuídas
Sirva um documento a partir de vários nós
Quando vários nós servem os mesmos documentos, o nó que renderiza um documento não é necessariamente o nó que responde às solicitações de página do navegador. Esta versão divide essa tarefa em duas metades explícitas — um lado de escrita que publica os artefatos de um documento no armazenamento compartilhado, e um lado de leitura que os serve a partir de qualquer nó.
Se sua aplicação atualmente chama UseDoconutWebFarm(...), esta página é o seu substituto. Nem UseDoconutWebFarm nem WebFarmOptions existem nesta versão.
Sua topologia não muda. Aponte a raiz compartilhada para o mesmo caminho UNC que você usa hoje e cada nó continuará funcionando da mesma forma. O que muda é que a publicação é uma operação que você chama, em vez de um efeito colateral da renderização.
O que substituiu o middleware web-farm
Anteriormente um middleware fazia tudo: servia imagens pré‑renderizadas de uma pasta compartilhada e perguntava enquanto outro nó ainda as escrevia. A espera era um orçamento cego — "bloqueie até três minutos esperando que a página apareça".
Agora a mesma tarefa são duas chamadas que você pode ver e raciocinar:
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 pageA configuração compartilhada que ambas as metades precisam
O publicador grava com um FileShareConfig e o manipulador de leitura serve com um. Se RootPath ou UsePerTokenSubfolders diferirem entre eles, o outro nó responderá 404. Construa a configuração em um único lugar e chame-a de ambos os lados — a aplicação de referência mantém um único helper SharedStore.Config(...) por esse motivo:
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 está em Doconut.Clouds, junto com AzureConfig, AmazonConfig, GoogleCloudConfig, DropBoxConfig, RedisConfig, CDNConfig e FTPConfig. Na biblioteca anterior esses estavam em Doconut.Configs.Cloud; os nomes das classes não mudaram.
| Propriedade | Propósito |
|---|---|
Location | CloudLocation.FileShare para um compartilhamento UNC/local |
RootPath | A pasta compartilhada que todo nó pode acessar |
UsePerTokenSubfolders | Mantém os artefatos de cada documento em sua própria pasta {token} |
ReadReadyPollMs | Quanto tempo o lado de leitura aguarda por uma página que ainda está sendo escrita; 0 desabilita a sondagem |
Lado de escrita — publique o documento
Injete DistributedDocumentPublisher e publique em vez de abrir uma sessão local. Uma única chamada renderiza e grava o conjunto completo de artefatos — páginas, informações do documento, índice de busca e metadados:
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 contém Token, ArtifactPath, TotalPages e Format. O token vai para o navegador exatamente como um token de visualizador normal.
Essa única chamada substitui o par anterior — abrir o documento em uma sessão local e então despejar as imagens na partilha com ExportToPng(sharedPath). ExportToPng e ExportToCloud não têm equivalente aqui.
Lado de leitura — servir a partir do compartilhamento
Monte o manipulador FileShare no mesmo ramo que o cliente já chama:
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 é genérico nesta versão: o provedor é o argumento de tipo em vez de um valor Location alternado dentro do middleware. Os manipuladores são nomeados <Provider>CloudHandler — FileShareCloudHandler, AzureCloudHandler, AmazonS3CloudHandler, GoogleCloudHandler, DropBoxCloudHandler, RedisCloudHandler, FTPCloudHandler e CDNCloudHandler.
O manipulador CDN é somente leitura — ele serve o que já está no CDN; não faz upload.
Prioridade da primeira página e HTTP 202
FirstPagePriority = true faz com que a página 1 esteja disponível imediatamente. Páginas posteriores respondem HTTP 202 Accepted até serem gravadas, em vez de bloquear a requisição.
Isso transfere a espera para o cliente, onde ela é limitada e visível. Habilite a nova tentativa no widget:
const objViewer = $('#div_ctlDoc').docViewer({
BasePath: '/',
ResPath: 'doconut-res/images',
retryOn409: true // retries 202 (and legacy 409) readiness responses
});Um cliente que não tenta novamente exibe blocos quebrados para páginas que ainda estão sendo renderizadas. O orçamento de tentativas e seus padrões (retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs) estão documentados em ConfiguraçõesDoVisualizador.
Esse par — FirstPagePriority mais a sondagem do lado de leitura — é o que substitui as configurações anteriores PageWaitTimeSeconds e StartWaitFromPage. Nenhuma delas existe mais.
Opcional: publicar em segundo plano
Por padrão, PublishAsync renderiza antes de retornar. Para retornar assim que o token for emitido e permitir que um worker hospedado renderize na mesma pasta, registre a fila de opt-in:
builder.Services.AddDoconut(options => { /* … */ });
builder.Services.AddDoconutDistributedAsyncPublish();Em seguida, enfileire em vez de publicar inline e defina Async = true nas opções de publicação. A fila é limitada: quando está saturada, EnqueueAsync lança DistributedPublishQueueFullException. Responda com um 503 e um cabeçalho Retry-After para que os clientes façam back‑off em vez de travar:
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.");
}Sem esse registro nada é registrado e a publicação permanece síncrona — o caminho padrão é idêntico byte a byte.
Quando a publicação em segundo plano está ativada, forneça ao lado de leitura um ReadReadyPollMs diferente de zero para que ele espere brevemente por uma página que o worker ainda não escreveu.
Segurança entre nós
Tokens vinculados à sessão assumem que o nó de serviço viu a requisição inicial. Em um farm isso não ocorre, portanto a verificação por sessão rejeitaria solicitações legítimas. Por isso a aplicação de referência distribuída define UnsafeMode = true.
UnsafeMode = true não é o modelo de segurança — é a remoção de um que não se aplica mais. Substitua-o por tickets de acesso assinados, que cada nó pode verificar independentemente:
builder.Services.AddDoconutDistributedDocumentSecurity(options => { /* … */ });Nunca leve UnsafeMode = true de volta para uma implantação de nó único, e nunca o trate como um atalho para uma verificação de sessão falha em um nó — nesse caso a correção correta é AddSession() / UseSession(). Veja Sessões e Segurança.
Fechando um documento publicado
CloseDocument(token) no Viewer injetado encerra uma sessão local. Para remover os artefatos publicados de um documento do compartilhamento, o publicador expõe:
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)Passe o mesmo FileShareConfig com o qual o documento foi publicado.
Mapeamento configuração por configuração
| Anterior | Agora |
|---|---|
UseDoconutWebFarm(new WebFarmOptions { … }) | PublishAsync(...) no lado de escrita mais UseDoconutCloud<FileShareCloudHandler>(...) no lado de leitura |
WebFarmOptions.Path | FileShareConfig.RootPath |
PageWaitTimeSeconds / StartWaitFromPage | DistributedDocumentPublishOptions.FirstPagePriority mais FileShareConfig.ReadReadyPollMs |
viewer.OpenDocument(...) então viewer.ExportToPng(share) | uma chamada PublishAsync(...) |
viewer.ExportToCloud(cloudConfig) | PublishAsync(...) com a configuração do provedor |
UseDoconutCloud(new CloudOptions { Location = … }) | UseDoconutCloud<THandler>(options => …, pathPrefix) |
DocOptions.IsWebfarm / WebfarmPath | Ainda presente, escrito IsWebFarm / WebFarmPath — não necessário no caminho de publicação |
Doconut.Configs.Cloud | Doconut.Clouds |
Lista de verificação
- Construa o
FileShareConfigem um helper e chame‑o de ambas as metades. - Aponte
RootPathpara o share que você já usa. - Publique com
PublishAsync(...); exclua quaisquer chamadasExportToPng/ExportToCloud. - Sirva com
UseDoconutCloud<FileShareCloudHandler>(...)no ramo que o cliente chama. - Ative
FirstPagePriorityeretryOn409juntos, ou deixe ambos desativados. - Substitua a verificação por sessão por tickets de acesso assinados — não envie
UnsafeMode = truesozinho. - Verifique abrindo um documento em um nó e solicitando uma página tardia de outro.
Esta página foi útil?