Despliegues Distribuidos
Servir un documento desde varios nodos
Cuando varios nodos sirven los mismos documentos, el nodo que renderiza un documento no es necesariamente el nodo que responde a las solicitudes de página del navegador. Esta versión divide esa tarea en dos mitades explícitas — un lado de escritura que publica los artefactos de un documento en almacenamiento compartido, y un lado de lectura que los sirve desde cualquier nodo.
Si su aplicación actualmente llama a UseDoconutWebFarm(...), esta página es su reemplazo. Ni UseDoconutWebFarm ni WebFarmOptions existen en esta versión.
Su topología no cambia. Apunte la raíz compartida al mismo camino UNC que usa hoy y cada nodo seguirá funcionando como lo hace ahora. Lo que cambia es que la publicación es una operación que usted llama, en lugar de un efecto secundario del renderizado.
Qué reemplazó al middleware web-farm
Anteriormente un solo middleware hacía todo: servía imágenes pre-renderizadas desde una carpeta compartida y encuestaba mientras otro nodo aún las escribía. La espera era un presupuesto ciego — "bloquear hasta tres minutos esperando que la página aparezca".
Ahora la misma tarea son dos llamadas que puede ver y razonar:
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 pageLa configuración compartida que ambas mitades necesitan
El publicador escribe con un FileShareConfig y el manejador de lectura sirve con uno. Si RootPath o UsePerTokenSubfolders difieren entre ellos, el otro nodo responde 404. Construya la configuración en un solo lugar y llámela desde ambos lados — la aplicación de referencia mantiene un único asistente SharedStore.Config(...) por esta razón:
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 se encuentra en Doconut.Clouds, junto con AzureConfig, AmazonConfig, GoogleCloudConfig, DropBoxConfig, RedisConfig, CDNConfig y FTPConfig. En la biblioteca anterior estaban en Doconut.Configs.Cloud; los nombres de las clases no cambiaron.
| Propiedad | Propósito |
|---|---|
Location | CloudLocation.FileShare para un recurso UNC/local |
RootPath | La carpeta compartida a la que puede acceder cualquier nodo |
UsePerTokenSubfolders | Mantenga los artefactos de cada documento en su propia carpeta {token} |
ReadReadyPollMs | Cuánto tiempo espera el lado de lectura por una página que aún se está escribiendo; 0 desactiva el sondeo |
Lado de escritura — publicar el documento
Inyecte DistributedDocumentPublisher y publique en lugar de abrir una sesión local. Una llamada renderiza y escribe el conjunto completo de artefactos — páginas, información del documento, índice de búsqueda y metadatos:
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 lleva Token, ArtifactPath, TotalPages y Format. El token se envía al navegador exactamente como lo hace un token de visor normal.
Esta única llamada reemplaza al par anterior — abrir el documento en una sesión local y luego volcar imágenes al recurso compartido con ExportToPng(sharedPath). ExportToPng y ExportToCloud no tienen equivalente aquí.
Lado de lectura — servir desde el share
Monte el controlador FileShare en la misma rama que ya llama el cliente:
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 es genérico en esta versión: el proveedor es el argumento de tipo en lugar de un valor Location conmutado dentro del middleware. Los controladores se nombran <Provider>CloudHandler — FileShareCloudHandler, AzureCloudHandler, AmazonS3CloudHandler, GoogleCloudHandler, DropBoxCloudHandler, RedisCloudHandler, FTPCloudHandler y CDNCloudHandler.
El controlador CDN es solo lectura — sirve lo que ya está en el CDN; no sube nada.
Prioridad de la primera página y HTTP 202
FirstPagePriority = true hace que la página 1 sea servible inmediatamente. Las páginas posteriores responden HTTP 202 Accepted hasta que se escriban, en lugar de bloquear la solicitud.
Eso traslada la espera al cliente, donde está limitada y visible. Habilite reintentos en el widget:
const objViewer = $('#div_ctlDoc').docViewer({
BasePath: '/',
ResPath: 'doconut-res/images',
retryOn409: true // retries 202 (and legacy 409) readiness responses
});Un cliente que no reintenta muestra mosaicos rotos para las páginas que aún se están renderizando. El presupuesto de reintentos y sus valores predeterminados (retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs) están documentados en ConfiguraciónDelVisor.
Este par — FirstPagePriority más la encuesta del lado de lectura — es lo que reemplaza las configuraciones anteriores PageWaitTimeSeconds y StartWaitFromPage. Ninguna de ellas existe ya.
Opcional: publicar en segundo plano
Por defecto PublishAsync renderiza antes de devolver. Para devolver tan pronto como se genera el token y permitir que un trabajador alojado renderice en la misma carpeta, registre la cola optativa:
builder.Services.AddDoconut(options => { /* … */ });
builder.Services.AddDoconutDistributedAsyncPublish();Entonces encole en lugar de publicar en línea, y establezca Async = true en las opciones de publicación. La cola está limitada: cuando está saturada, EnqueueAsync lanza DistributedPublishQueueFullException. Responda con un 503 y un encabezado Retry-After para que los clientes retrocedan en lugar de detenerse:
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.");
}Sin este registro no se registra nada y la publicación permanece sincrónica — la ruta predeterminada es idéntica byte a byte.
Cuando la publicación en segundo plano está activada, dé al lado de lectura un ReadReadyPollMs distinto de cero para que espere brevemente una página que el trabajador aún no ha escrito.
Seguridad entre nodos
Los tokens vinculados a la sesión asumen que el nodo que sirve vio la solicitud inicial. En una granja no lo hizo, por lo que la verificación por sesión rechazaría solicitudes legítimas. Por eso la aplicación de referencia distribuida establece UnsafeMode = true.
UnsafeMode = true no es el modelo de seguridad — es la eliminación de uno que ya no aplica. Reemplácelo con tickets de acceso firmados, que cada nodo puede verificar de forma independiente:
builder.Services.AddDoconutDistributedDocumentSecurity(options => { /* … */ });Nunca lleve UnsafeMode = true a una implementación de un solo nodo, y nunca lo trate como un atajo para una verificación de sesión fallida en un nodo — allí la solución correcta es AddSession() / UseSession(). Consulte Sesiones y Seguridad.
Cerrando un documento publicado
CloseDocument(token) en el Viewer inyectado elimina una sesión local. Para eliminar los artefactos publicados de un documento del recurso compartido, el publicador expone:
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)Pase el mismo FileShareConfig con el que se publicó el documento.
Mapeo configuración por configuración
| Anterior | Ahora |
|---|---|
UseDoconutWebFarm(new WebFarmOptions { … }) | PublishAsync(...) en el lado de escritura más UseDoconutCloud<FileShareCloudHandler>(...) en el lado de lectura |
WebFarmOptions.Path | FileShareConfig.RootPath |
PageWaitTimeSeconds / StartWaitFromPage | DistributedDocumentPublishOptions.FirstPagePriority más FileShareConfig.ReadReadyPollMs |
viewer.OpenDocument(...) then viewer.ExportToPng(share) | una llamada a PublishAsync(...) |
viewer.ExportToCloud(cloudConfig) | PublishAsync(...) con la configuración del proveedor |
UseDoconutCloud(new CloudOptions { Location = … }) | UseDoconutCloud<THandler>(options => …, pathPrefix) |
DocOptions.IsWebfarm / WebfarmPath | Aún presente, escrito IsWebFarm / WebFarmPath — no necesario en la ruta de publicación |
Doconut.Configs.Cloud | Doconut.Clouds |
Lista de verificación
- Construya el
FileShareConfigen una función auxiliar y llámelo desde ambas mitades. - Apunte
RootPathal recurso compartido que ya usa. - Publique con
PublishAsync(...); elimine cualquier llamada aExportToPng/ExportToCloud. - Sirva con
UseDoconutCloud<FileShareCloudHandler>(...)en la rama que llama el cliente. - Active
FirstPagePriorityyretryOn409juntos, o desactívelos ambos. - Reemplace la verificación por sesión con tickets de acceso firmado — no envíe
UnsafeMode = truesolo. - Verifique abriendo un documento en un nodo y solicitando una página tardía desde otro.
¿Fue útil esta página?