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:

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

A 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:

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 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.

PropriedadePropósito
LocationCloudLocation.FileShare para um compartilhamento UNC/local
RootPathA pasta compartilhada que todo nó pode acessar
UsePerTokenSubfoldersMantém os artefatos de cada documento em sua própria pasta {token}
ReadReadyPollMsQuanto 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:

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 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:

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 é 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:

javascript
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:

csharp
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:

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.");
}

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:

csharp
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:

text
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)

Passe o mesmo FileShareConfig com o qual o documento foi publicado.

Mapeamento configuração por configuração

AnteriorAgora
UseDoconutWebFarm(new WebFarmOptions { … })PublishAsync(...) no lado de escrita mais UseDoconutCloud<FileShareCloudHandler>(...) no lado de leitura
WebFarmOptions.PathFileShareConfig.RootPath
PageWaitTimeSeconds / StartWaitFromPageDistributedDocumentPublishOptions.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 / WebfarmPathAinda presente, escrito IsWebFarm / WebFarmPath — não necessário no caminho de publicação
Doconut.Configs.CloudDoconut.Clouds

Lista de verificação

  1. Construa o FileShareConfig em um helper e chame‑o de ambas as metades.
  2. Aponte RootPath para o share que você já usa.
  3. Publique com PublishAsync(...); exclua quaisquer chamadas ExportToPng / ExportToCloud.
  4. Sirva com UseDoconutCloud<FileShareCloudHandler>(...) no ramo que o cliente chama.
  5. Ative FirstPagePriority e retryOn409 juntos, ou deixe ambos desativados.
  6. Substitua a verificação por sessão por tickets de acesso assinados — não envie UnsafeMode = true sozinho.
  7. Verifique abrindo um documento em um nó e solicitando uma página tardia de outro.

Esta página foi útil?