Esta es la primera de una serie de varias partes para aprovechar la Servicio de eventos Couchbase para ejecutar múltiples tareas programadas a intervalos recurrentes específicos en un cron completamente dentro de la base de datos sin necesidad de infraestructura adicional a través de una única función de eventos de uso general.
En esta entrega, nos centraremos en la ejecución de rutinas fijas de usuario, funciones JavaScript definidas dentro de una función de eventos.
En artículos posteriores ampliaremos la cron como Eventing Function para programar y ejecutar sentencias N1QL dinámicas manejadas por la base de datos y finalmente exploraremos la programación de funciones JavaScript dinámicas manejadas por la base de datos.
Fondo
El servicio de eventos de Couchbase proporciona un marco para escribir tus propias rutinas, simples funciones JavaScript, para procesar los cambios en los documentos. Este servicio proporciona toda la infraestructura necesaria para crear funciones escalables y robustas basadas en la nube, permitiéndote centrarte en desarrollar pura lógica de negocio para interactuar casi en tiempo real con los cambios en tus datos. Tus funciones pueden acceder al servicio de datos de Couchbase (KV), al servicio de consultas de Couchbase (N1QL) y a puntos finales REST externos al sistema Couchbase.
En JSON modelo de datos de Couchbase procede de JavaScriptPor lo tanto, es natural que el servicio de eventos ofrezca la posibilidad de escribir código JavaScript para analizar y manipular documentos JSON en cualquier tipo de evento de cambio, incluyendo inserciones, actualizaciones, fusiones y eliminaciones (denominadas conjuntamente mutaciones).
Las funciones de eventos normalmente le permiten desplegar y ejecutar fragmentos de código personalizados para que reaccionen a miles e incluso millones de mutaciones por segundo en sus documentos. Algunos casos de uso típicos son documentado para desarrollar Funciones Eventing de alta velocidad a escala que respondan a mutaciones de documentos Couchbase.
Este artículo se centrará más bien en un caso de uso de muy baja velocidad del Servicio de Eventos construyendo un crontab distribuido fiable "en base de datos", permitiéndote ejecutar funciones JavaScript que interactúan con servicios Couchbase en un horario periódico regular.
Programación de la lógica empresarial para que se ejecute en una fecha u hora determinadas
Cronque lleva el nombre de "Chronos," la palabra griega para el tiempo es una de las utilidades más útiles en un sistema Linux. En Linux el cron está controlada por un archivo crontab (tabla cron), un archivo de configuración que especifica los comandos del shell que se ejecutarán periódicamente en un horario determinado.
Uno de los inconvenientes cron es que no está diseñado para ser un servicio distribuido; se ejecuta en una sola caja, por lo que presenta un único punto de fallo. Si el sistema está fuera de línea durante varias horas, se pierden todas las tareas programadas.
Sí, hay algunos distribuidos cron como Google's Cloud Service, AWS' Scheduled Tasks, y Azure Functions / Time Triggers. Pero las ofertas de cada proveedor de nube tienen sus propios modismos que no son directamente portables.
Además, es necesario asegurar la metodología de configuración y control, por ejemplo si se controla una cron a través de una API REST sobre HTTP/S, deberá tenerlo en cuenta en su plan de seguridad.
Utilizar el propio Couchbase para ejecutar comandos periódicos
Con una pequeña cantidad de código y planificación, puede aprovechar el servicio Eventing de Couchbase para proporcionar cron para las operaciones o el mantenimiento programados de la base de datos. La integración del programador en la base de datos permite obtener las siguientes ventajas:
- Portabilidad a través de proveedores Cloud, si reubicas tu cluster de Couchbase tu planificador no se ve afectado.
- Soporte, si utiliza Couchbase tiene un único proveedor para proporcionarle soporte y otros servicios.
- Distribuido, no hay un único punto de fallo y todos los servicios de Couchbase admiten réplicas distribuidas.
- Ejecución garantizada, su tarea se ejecuta incluso después de recuperarse de un fallo de nodo.
Programación de Couchbase, los temporizadores son la salsa secreta
Los temporizadores son construcciones del servicio de eventos de Couchbase mediante las cuales los desarrolladores pueden especificar una rutina (lógica de negocio) que se activará en un momento futuro. Utilizaremos esta funcionalidad para implementar un Couchbase configurable puro. crontab que le permite activar tareas repetitivas como parte de sus flujos de trabajo, tanto si necesita ejecutar una simple consulta N1QL como construir un complejo motor de reglas.
En todos los diseños posteriores limitaremos nuestra cron a una resolución de 15 segundos o más. Tenemos esta limitación porque, aunque los temporizadores se escalan a millones y se garantiza que se disparan y ejecutan, no son precisos como un reloj de pared y actualmente tienen un retardo de estado estacionario limitado a menos de 14 segundos. [1].
Por supuesto, si necesita una programación más ajustada, es decir, menos de 15 segundos, entonces simplemente debe procesar la mutación en sí en la lógica de eventos sin el uso de una construcción de temporizador para programar una llamada de vuelta en el futuro.
En el momento de escribir estas líneas, la versión actual de Couchbase es la 6.5.1 que dos limitaciones con las que debemos trabajar al hacer un robusto cron sistema.
- En las versiones 5.5.x, 6.0.x y 6.5.x, una función invocada por una llamada de retorno de un temporizador no puede crear un nuevo temporizador de forma fiable (se puede solucionar mediante una segunda función cooperativa).
- En las versiones 6.5.x, la creación de temporizadores en el futuro (como en una hora o más) en un sistema por lo demás inactivo puede dar lugar a un número creciente de operaciones de cubo de metadatos que eventualmente pueden bloquear las mutaciones para una función de Eventing dada (en 6.5.X se puede lograr una solución en el espacio de usuario a través de una segunda función cooperativa). La gravedad se rige por:
- El número de vBuckets que mantienen un temporizador activo. Por lo tanto, si solo hay unos pocos temporizadores en el futuro, es posible que el problema no se note o no se materialice. Este es el caso con sólo unos pocos cron horarios, pero para completar en caso de añadir la funcionalidad de la fecha puse en una solución para este problema para el código suministrado en este artículo.
- Si un temporizador de Eventing se ha disparado recientemente en un vBucket (lo que soluciona el problema para el vBucket en cuestión por función). Por lo tanto, los sistemas con mucha actividad de temporizadores a corto plazo no experimentarán este problema aunque los temporizadores estén programados para un futuro lejano.
Afortunadamente, en la versión 6.6.0 ambos problemas o restricciones se han eliminado y se puede crear un programador en una única y sencilla Función de Eventos unificada.
Requisitos previos
En este artículo utilizaremos la última versión de GA, es decir, la versión 6.5.1 de Couchbase (puede que tengas que hacer algunos cambios en las funciones de eventos descritas para versiones anteriores de Couchbase). El ejemplo de este artículo se ejecutará contra el conjunto de datos travel-sample que se entrega con el servidor Couchbase.
CONSEJO PROFESIONAL: Sólo para usuarios avanzados, si estás familiarizado con Couchbase Eventing y también con nuestras herramientas CLI / REST puedes saltarte la mayor parte de este blog y descargar un archivo ZIP para configurar y ejecutar rápidamente el sistema programador que se presenta a continuación. Haz clic con el botón derecho en el siguiente enlace y selecciona Guardar enlace como para descargar el archivo cron_impl_2func_CLI.zip, muévalo a un nodo Eventing, extraiga el archivo ZIP y consulte el archivo README.txt extraído.
Sin embargo, si usted no está familiarizado con Couchbase o el servicio Eventing por favor camine a través de GET STARTED y un ejemplo Eventing específicamente se refieren a los siguientes:
- Configure un servidor Couchbase 6.5.1 que funcione según las instrucciones de Comience aquí
- Asegúrese de que puede ejecutar una consulta N1QL en la base de datos viaje-muestra según las instrucciones de Ejecute su primera consulta N1QL.
- Comprender cómo desplegar una función básica de Eventing según las instrucciones de la sección Archivo de documentos que también utiliza viaje-muestra conjunto de datos.
- Asegúrese de tener la viaje-muestra en la vista de cubos de la interfaz de usuario.
- Asegúrese de que un cubo llamado metadatos en la vista Buckets de la interfaz de usuario debe tener un tamaño mínimo de 200 MB.
- En la vista Buckets de la interfaz de usuario, cree un bucket llamado crondata con un tamaño mínimo de 200 MB. Para obtener información detallada sobre cómo crear cubos, consulte Crear un cubo.
- Establecer allow_interbucket_recursion a verdadero para permitir que dos (2) funciones de Eventing modifiquen el mismo documento KV [2].
1curl -X POST -u "$CB_USERNAME:$CB_PASSWORD" 'http://localhost:8091/_p/event/api/v1/config' -d '{ "allow_interbucket_recursion":true }'
Implementación #1, programación tipo "cron" codificada.
Para nuestra primera implementación, por ejemplo la Parte 1 de la serie, diseñaremos una estructura de control simple que es simplemente un documento KV JSON y también dos (2) Funciones Eventing que responderán y actuarán sobre la información en la estructura de control.
A continuación se muestra un diseño de un documento JSON, o estructura de control, que nos permitirá tener múltiples "eventos" programados. Cada evento programado tendrá su propio documento de control con una CLAVE única como recurring_event::1, recurring_event::1, ... recurring_event::N. La propia estructura JSON contiene información para "reconstituir la clave", ya que nuestro sistema de programación responderá a los cambios o actualizaciones (mutaciones) de los documentos de control, como alternar el estado "activo" para activar o desactivar la acción o cambiar el campo "verbose" que controla la cantidad y el estilo del registro.
A continuación se muestra un ejemplo de documento de control con CLAVE evento_recurrente::1 que ejecutará la función JavaScript doCronActionA a las 14:54 (14:30) todos los días.
Registro de control JSON | Descripción |
---|---|
{ | |
"type": "recurring_event", | La CLAVE será <>::<>. |
"id":1, | |
"hora":14, | La hora del día 0-23, *, *2X, *4X para activar |
"min":54, | El minuto en la hora 0-59, *, *2X, *4X para activar |
"acción": "doCronActionA", | Función JavaScript que se ejecutará cuando se active el temporizador |
"activo":true, | Bandera para activar o desactivar este horario |
"verbose": { | [OPCIONAL] control de registro |
"user_func":2, | Nivel de registro para la lógica de acción : 0=ninguno, etc. etc. |
"programador":3 | Nivel de registro para la lógica cron : 0=ninguno, etc. etc. |
}, | |
"dinámico": { | control y estadísticas del sistema [DINÁMICO |
"estado": "brazo", | "armar"|"rearmar"|"pendiente" cualquier valor != "pendiente" iniciar una programación |
"next_sched": 0, | Número de segundos transcurridos desde la época hasta el siguiente horario deseado |
"prev_sched": 0, | Número de segundos transcurridos desde la fecha de la programación anterior |
"prev_etime": 0, | Número de segundos transcurridos desde la época para el tiempo de ejecución real del programa anterior |
"prev_delay": 0, | Número de segundos que el temporizador se ha retrasado con respecto a la programación |
"prev_atime": 0 | Número de segundos que ha tardado el usuario 'acción' |
} | |
} |
Como Linux tradicional crontab puede establecer la hora y el minuto en números enteros legales, y también puede establecer hora a "*" para procesar todas las horas o ajuste min a "*" para procesar todos los minutos.
Aunque no apoyaremos la crontab sintaxis admitimos dos configuraciones no estándar, a saber, si fijar ambos hora y min a "*4X" ejecutaremos y rearmaremos cuatro (4) veces por minuto y si los configuras ambos a "*2X" ejecutaremos y rearmaremos dos (2) veces por minuto. A continuación se muestra una tabla de horarios soportados con su descripción:
hora | min | Los valores pueden ser números o cadenas |
---|---|---|
13 | 32 | Corre a las 13:32 (o 13:32) |
* | 15 | Se ejecuta cada hora a los 15 minutos |
8 | 12 | Corre una vez al día a las 8:32 (o 8:32 am) |
* | * | Ejecutar una vez por minuto |
*2X | *2X | Se ejecuta dos veces por minuto - requiere que tanto la hora como el minuto estén ajustados a "*2X". |
*4X | *4X | Funciona cuatro veces por minuto - requiere que la hora y el minuto estén ajustados a "*2X". |
Eventualmente utilizaremos el Query Workbench para insertar el cron documentos de control, todos los cuales deben tener una CLAVE única de evento_recurrente::# a una hora de ejecución programada de 14:54 ( 2:54 pm), para la acción doCronActionA, podríamos utilizar la siguiente sentencia N1QL.
No te preocupes por ejecutar ninguna sentencia N1QL en este momento, realizaremos las sentencias N1QL más tarde después de haber construido y desplegado nuestra Función de Eventos.
Puede crear un registro (o registros) de control en el cubo viaje-muestray luego listarlo, armarlo, desarmarlo, ajustar el horario que sigue, cambiar el nivel de verbosidad para el registro, o borrarlo de la siguiente manera:
Acción | Declaración N1QL |
---|---|
Crear un calendario | INSERTAR EN viaje-muestra (CLAVE,VALOR) VALORES ("recurring_event::1", { "type": "recurring_event", "id":1, "hora": "14″, "min": "54″, "acción": "doCronActionA", "activo":true } ); |
Crear un índice para consultar datos sin especificar claves | CREAR un índice primario sobre crondata ; |
Mostrar todos los horarios ordenados por id | SELECT * FROM crondata WHERE type="recurring_event" order by id ; |
Mostrar horario específico | SELECT * FROM crondata WHERE type="recurring_event" AND id=1 ; |
Armar o activar | ACTUALIZACIÓN crondata SET active = true WHERE type="recurring_event" AND id=1 ; |
Desarmar o desactivar | ACTUALIZACIÓN crondata SET active = false WHERE type="recurring_event" AND id=1 ; |
Ajustar el tiempo de activación | ACTUALIZACIÓN crondata SET hora = 11, min = 30 WHERE type="recurring_event" AND id=1 ; |
Ajustar el registro de la "acción | ACTUALIZACIÓN crondata SET verbose.user_data = 0 WHERE type="recurring_event" AND id=1 ; |
Ajustar el registro de la lógica del programador | ACTUALIZACIÓN crondata SET verbose.scheduler = 0 WHERE type="recurring_event" AND id=1 ; |
Borrar el horario | DELETE FROM crondata WHERE type="recurring_event" AND id=1 ; |
Supongamos que tenemos cuatro (4) horarios activos, la ejecución de la primera sentencia N1QL, por encima de la lista de todos ellos, por ejemplo.
1 2 |
SELECT active, action, hour, min, type, id, verbose.user_func, verbose.scheduler FROM `crondata` where type="recurring_event" order by id ; |
Devolvería algo como la siguiente salida (vista de tabla en el Query Workbench):
activo | acción | hora | id | min | programador | tipo | usuario_func |
---|---|---|---|---|---|---|---|
verdadero | "doCronActionA" | 14 | 1 | 54 | 1 | "evento_recurrente" | 2 |
verdadero | "doCronActionB" | * | 2 | * | 1 | "evento_recurrente" | 1 |
verdadero | "doCronActionC" | *2X | 3 | *2X | 4 | "evento_recurrente" | 4 |
verdadero | "doCronActionD" | * | 4 | 0 | 0 | "evento_recurrente" | 1 |
En la tabla anterior tenemos cuatro acciones: la primera se ejecuta una vez al día, la segunda cada minuto, la tercera cada 30 segundos y la cuarta cada hora. En una próxima entrega de esta serie añadiremos la función "día de la semana".
El objeto anidado "verbose"si no se suministra será por defecto { "user_func":1, "scheduler":1 } indicando un nivel de registro bajo o escueto para la función de acción y también para la lógica de programación. Un valor de 0 suprimirá todos los mensajes de registro, es decir, doCronActionD, mientras que valores mayores serán más verbosos, es decir, como se define en doCronActionC.
El objeto anidado "dinámico" si normalmente nunca se suministra y será por defecto { "state": "arm", "next_sched": 0, "prev_sched": 0, "prev_etime": 0, "prev_delay": 0, "prev_atime": 0 } este es un bloc de notas para la programación lógica de eventos en ejecución y también proporciona estadísticas útiles sobre los tiempos de ejecución, por lo que debe ser tratado como de sólo lectura.
En este punto tenemos un diseño de control de alto nivel, pero necesitamos lógica para procesar nuestras estructuras de control, aquí es donde el Servicio de Eventos de Couchbase, específicamente una Función de Eventos entra en juego.
Funciones del concurso
Este diseño requiere dos (2) funciones de Eventing: una función JavaScript principal "cron_impl_2func_651" y una pequeña función JavaScript de ayuda "cron_impl_2func_651_help". Discutiremos cada sección de las funciones JavaScript que comprende la implementación inicial combinada de código JavaScript de casi 610 líneas (con aproximadamente 44% de las líneas son comentarios y espacios en blanco)
No te preocupes por hacer un cortar y pegar en este momentoComo más tarde voy a proporcionar un enlace para descargar (para la importación) las dos funciones de eventos necesarios y todos los ajustes necesarios en dos archivos llamados "cron_impl_2func_651.json" "cron_impl_2func_651_help.json" y también si lo prefiere las dos funciones unificadas completo que se puede cortar y pegar directamente.
Nuestra función principal de eventos "cron_impl_2func_651" estará compuesta por nueve (9) funciones JavaScript
- Tres (3) funciones de lógica empresarial (dos de las cuales están vacías).
- doCronActionA(doc) - una acción de usuario de ejemplo N1QL a ejecutar
- doCronActionB(doc) - un intérprete de comandos de acción de usuario vacío para experimentos
- doCronActionC(doc) - un intérprete de comandos de acción de usuario vacío para experimentos
- Un (1) punto de entrada para concursos completos.
- OnUpdate(doc, meta) - el punto de entrada estándar de Eventing para Inserciones o Actualizaciones
- Uno (1) cron analizador sintáctico para generar la siguiente programación.
- getNextRecurringDate(hour_str, min_str) - lógica cron para encontrar la siguiente Fecha programada
- Tres (3) funciones de apoyo para comprobar que la lógica de negocio existe o formatear resultados.
- verifyFunctionExistsViaEval(curDoc, id) - asegurarse de que tenemos una función que ejecutar
- toNumericFixed(number, precision) - formatea un flotante a un estilo compacto
- toLocalISOTime(d) - formatea una fecha a un estilo compacto
- Una (1) función callback cuando se ejecutan los temporizadores.
- Callback(doc) - una función de devolución de llamada para temporizadores programados
Nuestra función de ayuda para eventos "cron_impl_2func_651_help" estará compuesta por una (1) función JavaScript
- Un (1) punto de entrada para concursos completos.
- OnUpdate(doc, meta) - el punto de entrada estándar de Eventing para Inserciones o Actualizaciones
En las secciones siguientes recorreremos cada una de las funciones JavaScript anteriores.
Necesitamos una función JavaScript, por ejemplo, la lógica de negocio para ejecutar en un horario periódico.
Lo primero que queremos es una rutina o función que contenga nuestra lógica de negocio que ejecutaremos basándonos en nuestras reglas crontab. Llamaremos al método JavaScript doCronActionA(doc)sin embargo, puede llamarse como se quiera, por ejemplo doPeriodicLedgerBalance(doc), los únicos requisitos para nuestras funciones de "acción" que implementan nuestra lógica de negocio programada son los siguientes:
- Tiene un parámetro: doc, un documento de control como el descrito anteriormente de type="recurring_event".
- El nombre real de JavaScript coincide con el campo "acción" del documento de control.
- Devuelve verdadero sobre el éxito y falso sobre el fracaso
- Utiliza doc.verbose.user_func para controlar el registro si 0 es silencioso, si 1 emite una sola línea, si 2 emite cualquier información de registro que se necesite para depurar la función, etc. etc..
Escribiremos nuestra función doCronActionA(doc)para ejecutar una consulta N1QL incrustada ) para combinar los recuentos de líneas aéreas por país y, a continuación, crear un único documento KV con los datos calculados.
1 |
SELECT country, count( * ) AS cnt FROM `travel-sample` WHERE `type` = 'airline' GROUP BY country; |
En mi sistema de prueba un pequeño nodo único noMDS (ejecutando todos los servicios de Couchbase) el N1QL anterior tarda unos 20 ms. (para mayor claridad fingir que es supercomplejo tarda 10 segundos en completarse).
La idea aquí es que el documento KV final calculado y resumido pueda ser cargado rápidamente por 100K (o un millón) de mutaciones Eventing por segundo sin la sobrecarga adicional de comunicación con los nodos del servicio de Consulta y de procesar sentencias N1QL en cada mutación.
Debería ser obvio que el objetivo de esta lógica de negocio en particular, doCronActionA(doc)es crear una caché semiestática que se actualice periódicamente según un calendario.
Todo lo que estamos haciendo realmente (y es bastante rápido) es obtener un recuento de aerolíneas por país a partir del conjunto de documentos de la muestra de viajes. A medida que usamos N1QL construimos un documento y eventualmente lo escribimos en KV como un documento resumido. El punto clave a destacar aquí es que no queremos repetir el mismo trabajo para millones de mutaciones cada uno, sobre todo porque algunos cálculos podrían tomar 10 segundos de tiempo de cálculo del servicio de consulta cada vez que iniciamos una consulta N1QL incrustada desde una función Eventing.
A continuación mostramos la función JavaScript que queremos que se ejecute una vez al día (o quizás una vez cada hora, etc.). Observa que el nombre de la función coincide con el nombre del campo de acción de la estructura de control. Para más detalles sobre la terminología de Eventing y las construcciones del lenguaje, consulta los documentos y ejemplos de Couchbase en Servicio de Eventing: Fundamentos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
función doCronActionA(doc) { pruebe { // Comprobar que el doc tiene los valores deseados si (!doc.tipo || doc.tipo !== "evento_recurrente" || !doc.activo || doc.activo !== verdadero) devolver; si (doc.verbose.usuario_func >= 1) registro(doc.acción + Acción del usuario controlada por ' + doc.tipo + '::' + doc.id); // se trata de una consulta N1QL 6.5 (función no disponible en GA anterior a 6.5) // Crear un iterador N1QL embebido emitiendo una sentencia SELECT para obtener el // recuentos de compañías aéreas por país. Haz un nuevo documento y escríbelo a KV // Utilizaremos el iterador para crear un documento KV que represente los resultados de un // HARD largo incrustado N1QL consulta y escribir de nuevo a KV, la idea es mantener // un cálculo actualizado una vez al día para que pueda leerse "rápidamente // por otras Funciones de Eventos, otros servicios de Couchbase o SDKs. // Considere si tuviéramos 1 millón de documentos en un minuto ¿realmente queremos usar N1QL? // recalcular algo que es casi estático para todo 1 millón de documentos, de // por supuesto que no, así que hacemos un valor intermedio que se puede leer en Eventing // y se utiliza a través de una única lectura KV "ligera". var q_iter = SELECCIONE país, cuente( * ) cnt DESDE `viaje-muestra DONDE `tipo` = aerolínea GRUPO POR país; // bucle a través del conjunto de resultados y actualizar el mapa 'acumular' var acumular = {}; var idx = 0; para (var val de q_iter) { si (doc.verbose.usuario_func >= 2) registro(doc.acción + ' N1QL idx ' + idx + País + val.país + " cnt " + val.cnt); acumular[val.país] = val.cnt; idx++; } // cerrar el iterador N1QL incrustado q_iter.cerrar(); // Ahora hagamos un documento KV en caché que represente un N1QL incrustado de longitud HARD // consulta y escribirla de nuevo a KV, necesitamos una CLAVE y un tipo e id y luego // insértalo en el cubo `viaje-muestra`. var cachedoc = {}; cachedoc.tipo = "cron_cache"; cachedoc.id = "aerolíneas_por_país"; cachedoc.fecha = nuevo Fecha(); cachedoc.datos = acumular; var ckey = cachedoc.tipo + '::' + cachedoc.id; ts_bkt[ckey] = cachedoc; si (doc.verbose.usuario_func >= 2) { registro(doc.acción + ' upsert to KV with KEY ' + ckey + ' cachedoc ', cachedoc); } } captura (e) { registro(doc.acción + ' Excepción de error:', e); devolver falso; } devolver verdadero; } |
La función anterior simplemente 1) consulta el cubo de muestras de viajes para extraer datos, en este caso el recuento de aerolíneas para cada país, 2) crea un nuevo documento KV y una nueva clave y lo escribe en el cubo de muestras de viajes para su uso posterior.
Además, como parte de este ejemplo hemos construido un registro que responde a un ajuste numérico de verbosidad que a) registra una sola línea si el documento de control tiene un valor para doc.verbose.user_func == 1 o b) emite más información si el valor de doc.verbose.user_func >= 2.
Se trata de un marco genérico que puede ejecutar una (1) cron acción o incluso mil (1000) de cron acciones. Como tal, he proporcionado dos funciones adicionales "vacías" - como se señaló antes, podrían haber sido nombradas de cualquier manera.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
función doCronActionB(doc) { pruebe { // comprobar que el doc tiene los valores deseados si (doc.tipo !== "evento_recurrente" || doc.activo !== verdadero) devolver; si (doc.verbose.usuario_func >= 1) registro(doc.acción + Acción del usuario controlada por ' + doc.tipo + '::' + doc.id); // SU LÓGICA AQUÍ } captura (e) { registro(doc.acción + ' Excepción de error:', e); devolver falso; } devolver verdadero; } |
y
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
función doCronActionC(doc) { pruebe { // comprobar que el doc tiene los valores deseados si (doc.tipo !== "evento_recurrente" || doc.activo !== verdadero) devolver; si (doc.verbose.usuario_func >= 1) registro(doc.acción + Acción del usuario controlada por ' + doc.tipo + '::' + doc.id); // SU LÓGICA AQUÍ } captura (e) { registro(doc.acción + ' Excepción de error:', e); devolver falso; } devolver verdadero; } |
Estas funciones doCronActionB y doCronActionC son triviales, ya que simplemente registran información en el registro de la aplicación de eventos de la función de eventos. Consulte Funciones de registro para más detalles. Por supuesto, necesita un documento de control de type="recurring_event" con active=true y una acción como action = "doCronActionB" para habilitarlos y ejecutarlos realmente.
Necesitamos un punto de entrada o manejador de eventos
A partir de la versión 6.5, el servicio de eventos admite dos puntos de entrada o controladores OnUpdate(doc, meta) y OnDelete(meta) sólo nos interesa el OnUpdate(doc,meta) para este ejemplo.
En OnUpdate(doc,meta) es llamado cuando se crea o modifica (muta) cualquier documento del bucket de origen y filtra inmediatamente los documentos que no interesan. [3]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
función OnUpdate(doc, meta) { // fix for 6.5.X growing bucket ops si (doc.tipo === "_tmp_vbs") genNoopTimers(doc, meta, 30); si (!cron_bkt["fix_timer_scan_issue::1"]) { cron_bkt["fix_timer_scan_issue::1"] = {}; } pruebe { // Comprobar si es necesario un análisis adicional sólo activamos en un recurring_event activo si (doc.tipo !== "evento_recurrente" || doc.activo !== verdadero) devolver; var actualizar_doc = falso; si (!doc.dinámico) { // Añadir si falta doc.dynamic con valores por defecto doc.dinámico = { "estado": "brazo", "next_sched": 0, "prev_sched": 0, "prev_etime": 0, "prev_delay": 0, "prev_atime": 0 }; // necesitamos actualizar el documento una vez que tengamos el siguiente horario actualizar_doc = verdadero; } si (!doc.verbose) { // Añadir si falta doc.dynamic con valores por defecto doc.verbose = { "user_func": 1, "programador": 1 }; // necesitamos actualizar el documento una vez que tengamos el siguiente horario actualizar_doc = verdadero; } // No procesar dynamic.state pendiente si (!doc.dinámico || !doc.dinámico.estado || doc.dinámico.estado === "pendiente") devolver; var mediados de = doc.tipo + "::" + doc.id; // es lo mismo que meta.id o la CLAVE var hora = doc.hora; var min = doc.min; // Hacer una comprobación eval la función JavaScript existe. La eval se produce en un // función de utilidad compartida con RecurringCallback si (!verifyFunctionExistsViaEval(doc, mediados de)) { // doc.action no existía, ya hemos registrado el problema devolver; } // Obtener el siguiente tiempo de ejecución válido var temporizador_fecha = getNextRecurringDate(hora, min); var siguiente_sched = Matemáticas.redondo(temporizador_fecha.getTime() / 1000); si (!actualizar_doc && siguiente_sched !== doc.dinámico.siguiente_sched) { // el next_sched debe ser el mismo que el de la aplicación de ayuda, sin embargo // si desinstalamos/desinstalamos o pausamos/reanudamos podríamos tener que reprogramar a la siguiente franja horaria registro(OnUpdate U + mediados de + calculado next_sched !== doc.dynamic.next_sched, delta ' + (siguiente_sched - doc.dinámico.siguiente_sched) + ', reprogramar'); actualizar_doc = verdadero; } si (actualizar_doc) { // esta mutación es recursiva y se suprimirá, nos aseguramos de tener una estructura dinámica doc.dinámico.siguiente_sched = siguiente_sched; // en lugar de la llamada a una función, para atrapar y reintentar si hay un problema de recursos // cron_bkt[mid] = doc; si (!tryBucketKvWriteWithLog(OnUpdate F, mediados de, doc)) { // Fallo al escribir doc en cron_bkt[key] se ha registrado el error // y no hay nada más que podamos hacer. devolver; } } // Programar un temporizador Eventing var timer_id = crearTiempo(Devolución de llamada, temporizador_fecha, null, doc); si (doc.verbose.programador >= 1) { registro(OnUpdate A + mediados de + ' rcv mutación (inicial o rearme) programar temporizador en ' + toLocalISOTime(temporizador_fecha)); } si (doc.verbose.programador >= 2) { registro(OnUpdate B + mediados de + Se ha creado un temporizador recurrente, timer_id ' + timer_id); } } captura (e) { registro(OnUpdate E + meta.id + ', Excepción de error:', e); } } |
La clave aquí es que el cron en nuestro manejador sólo se preocupa por los documentos que tienen doc.type de "recurring_event y también un doc.active de true. Además, en este ejemplo hemos creado un rastreo para el método cron lógica de mantenimiento que sólo se registra en el registro de la aplicación si el documento de control tiene un valor para doc.verbose >= 3.
Si sólo ejecuta unas pocas programaciones, puede desactivar el trabajo en el espacio de usuario o "arreglo para 6.5.X cubo creciente ops" comentando cuatro líneas de código en el bloque OnUpdate anterior para "cron_impl_2func_651" como sigue:
1 2 3 4 5 6 |
función OnUpdate(doc, meta) { // fix for 6.5.X growing bucket ops // if (doc.type === "_tmp_vbs") genNoopTimers(doc, meta, 30); // if (!cron_bkt["fix_timer_scan_issue::1"]) { // cron_bkt["fix_timer_scan_issue::1"] = {}; // } |
Necesitamos código para solucionar posibles operaciones de crecimiento de cubos para 6.5.X
A partir de la versión 6.5.X necesitamos un "arreglo para 6.5.X cubo creciente ops"que ocurre en sistemas inactivos con muchos temporizadores programados en el futuro. Este código garantiza que un temporizador de Eventing se ha disparado recientemente en un vBucket (lo que soluciona el problema para el vBucket dado en función de cada función).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
// FIXUP: ADDIN FUNCTON función noopTimer(contexto) { // fix for 6.5.X growing bucket ops pruebe { si (contexto.tipo === "_tmp_vbs" && contexto.vb === 0) { // log("noopTimer timers firing, printing only for vBucket 0"); } } captura (e) { registro("OnUpdate Excepción en callback noopTimer:", e); } } // FIXUP: ADDIN FUNCTON función rearmTimer(contexto) { // fix for 6.5.X growing bucket ops pruebe { si (contexto.tipo === "_tmp_vbs" && contexto.vb === 0) { // Actualizar/tocar todos los documentos en el helper_bucket la función helper entonces // muta todos los 1024 de tipo == vbs_seed (64 en MacOS) para crear un ciclo de recuración. // log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context); // generar una mutación para rearmar la función HELPER: fix_scan_issue // que a su vez hará nuevas mutaciones para esta Función var cur = cron_bkt[contexto.clave]; si (cur && cur.ts_millis === contexto.ts_millis) { // log("rearmTimer update fix_timer_scan_issue::1 in helper_bucket alias only for vBucket 0"); var ahora = nuevo Fecha(); cron_bkt["fix_timer_scan_issue::1"] = { "última_actualización": ahora }; } si no { // NOOP tuvimos múltiples ciclos de temporizador, deja que este se detenga tranquilamente. } } } captura (e) { registro("OnUpdate Excepción en callback rearmTimer:", e); } } // FIXUP: ADDIN FUNCTON función genNoopTimers(doc, meta, segundos) { // fix for 6.5.X growing bucket ops pruebe { // redundante pero a lo seguro si (doc.tipo === "_tmp_vbs") { // Dado que estamos utilizando una función diferente un temporizador en todos nuestros vBuckets hacer immeadiately (puede tardar hasta 15 segundos) // Si utilizáramos la recursividad cross bucket para rearmar todos los temporizadores de forma recurrente añadiríamos un retardo de al menos 40 segundos. crearTiempo(noopTimer, nuevo Fecha(), null, doc); si (doc.vb === 0) { // Actualizar/tocar todos los documentos en el helper_bucket la función helper entonces // muta todos los 1024 de tipo == vbs_seed (64 en MacOS) para crear un ciclo de recuración. // log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context); // generar una mutación para rearmar la función HELPER: fix_scan_issue // que a su vez hará nuevas mutaciones para esta Función // log("genNoopTimers hace que el temporizador se rearme fix_timer_scan_issue::1"); crearTiempo(rearmTimer, nuevo Fecha(nuevo Fecha().getTime() + segundos * 1000), null, doc); } } } captura (e) { registro("Excepción OnUpdate en genNoopTimers:", e); } } |
Necesitamos una utilidad para calcular la siguiente hora en el horario
La siguiente función getNextRecurringDate(hour, min) determinará una hora para ejecutar la acción definida como parte de nuestra programación. Esto no es una implementación completa de cronEn lugar de ello, contiene las características estándar clave para ejecutarse una vez al día, una vez por hora y una vez por minuto. También contiene alguna sintaxis no estándar para proporcionar la capacidad de ejecutar dos veces por minuto o cuatro veces por minuto.
Como se ha descrito anteriormente, la función getNextRecurringDate(hora, min) permite lo siguiente (la tabla se duplica a continuación), siendo las dos últimas no estándar.[4]
hora | min | Los valores pueden ser números o cadenas |
---|---|---|
13 | 32 | Corre a las 13:32 (o 13:32) |
* | 15 | Se ejecuta cada hora a los 15 minutos |
8 | 12 | Corre una vez al día a las 8:32 (o 8:32 am) |
* | * | Ejecutar una vez por minuto |
*2X | *2X | Se ejecuta dos veces por minuto - requiere que tanto la hora como el minuto estén ajustados a "*2X". |
*4X | *4X | Funciona cuatro veces por minuto - requiere que la hora y el minuto estén ajustados a "*2X". |
A continuación se muestra una implementación de la lógica necesaria para determinar la próxima vez que debe activarse un temporizador de Eventing en nuestro programa, en el caso de que la lógica de usuario de nuestro primer ejemplo doCronActionA(doc) no se completa a tiempo, por ejemplo, si se sobrepasa el tiempo real, se seleccionará el siguiente cuanto de la programación. Tenga en cuenta tanto los Temporizadores como sus Funciones Padre. Así, si una Función de Eventos tiene un tiempo de espera de ejecución por defecto de 60 segundos, si es necesario esta configuración puede ser ajustada o aumentada.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
función getNextRecurringDate(hora_str, min_str) { // Nota Javascript Las fechas están en milisegundos var fecha_ahora = nuevo Fecha(); var fecha_ret = nuevo Fecha(); var hora; var min; pruebe { hora = parseInt(hora_str); } captura (e) {} pruebe { min = parseInt(min_str); } captura (e) {} // Nota, esto es sólo una sintaxis parcial simplista de 'crontab' con algunas ligeras extensiones // permite una vez al día, una vez por hora, una vez por minuto. También contiene algunos // sintaxis para proporcionar la capacidad de ejecutar dos veces por minuto o cuatro veces por minuto. si (hora_str === '*4X' && min_str === '*4X') { // una vez cada 15 segundos o cuatro veces por minuto fecha_ret.setMilliseconds(0); fecha_ret.setSeconds(15); mientras que (fecha_ret.getTime() < fecha_ahora.getTime()) { fecha_ret.setSeconds(fecha_ret.getSeconds() + 15); } devolver fecha_ret; } si no si (hora_str === '*2X' && min_str === '*2X') { // una vez cada 30 segundos o dos veces por minuto fecha_ret.setMilliseconds(0); fecha_ret.setSeconds(30); mientras que (fecha_ret.getTime() < fecha_ahora.getTime()) { fecha_ret.setSeconds(fecha_ret.getSeconds() + 30); } devolver fecha_ret; } si no si (hora_str === '*' && min_str === '*') { // una vez por minuto fecha_ret.setMilliseconds(0); fecha_ret.setSeconds(0); fecha_ret.setMinutes(fecha_ret.getMinutes() + 1); } si no si (hora_str !== '*' && isNaN(hora) === falso && min_str === '*') { // sólo una vez por minuto durante una hora determinada fecha_ret.setMilliseconds(0); fecha_ret.setSeconds(0); fecha_ret.setMinutes(fecha_ret.getMinutes() + 1); si (fecha_ret.getTime() < fecha_ahora.getTime()) { fecha_ret.setHours(hora); } si (fecha_ret.getTime() > fecha_ahora.getTime()) { fecha_ret.setDate(fecha_ret.getDate() + 1); fecha_ret.setSeconds(0); fecha_ret.setMinutes(0); fecha_ret.setHours(hora); } } si no si (hora_str === '*' && min_str !== '*' && isNaN(min) === falso) { // una vez por hora en un minuto determinado fecha_ret.setMilliseconds(0); fecha_ret.setSeconds(0); fecha_ret.setMinutes(min); // programación para la próxima hora fecha_ret.setHours(fecha_ret.getHours() + 1); } si no si (isNaN(hora) === falso && isNaN(min) === falso) { // una vez al día durante una hora y un minuto determinados fecha_ret.setMilliseconds(0); fecha_ret.setSeconds(0); fecha_ret.setMinutes(min); fecha_ret.setHours(hora); si (fecha_ret.getTime() < fecha_ahora.getTime()) { // programa para mañana fecha_ret.setDate(fecha_ret.getDate() + 1); } } si no { registro('getNextRecurringDate entrada ilegal hour_str <' + hora_str + '> min_str <' + min_str + '>'); tirar nuevo Error('getNextRecurringDate entrada ilegal hour_str <' + hora_str + '> min_str <' + min_str + '>'); devolver null; } devolver fecha_ret; } |
Necesitamos algunas pequeñas utilidades
La función de utilidad común que simplemente comprueba si nuestro JavaScript existe utilizado por ambos OnUpdate(doc,meta), mostrado arriba, y el temporizador Devolución de llamada(doc), que se muestra más adelante. A continuación verifyFunctionExistsViaEval(curDoc, id) que recibe dos argumentos: un documento de control JSON y la CLAVE de dicho documento.
Esto nos permite saber inmediatamente, en el despliegue, si hubo un problema con una falta de coincidencia de nombres entre el documento de registro de control JSON y el nombre real de la función de lógica de negocio en el código JavaScript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
función verifyFunctionExistsViaEval(curDoc, id) { var resultado = falso; pruebe { // comprobar función si falta esta es inválida devolver resultado resultado = evalúe("typeof" + curDoc.acción + " === 'function';"); si (resultado === falso) { si (curDoc.verbose.programador >= 1) registro("Advertir/Desactivar (sin acción y sin rearme), porque se requiere una "acción" de " + curDoc.acción + "(doc) no existe, id es", id); devolver resultado; } } captura (e) { registro('verifyFunctionExistsViaEval Error exception:', e); } devolver resultado; } |
Tenga en cuenta que si se intenta ejecutar una función inexistente, el usuario final recibirá una advertencia en el registro de la aplicación. cron_impl_2func_651.log para corregir el problema.
2020-04-22T16:20:38.725-07:00 [INFO] "Advertir/Desactivar (Sin Acción y Sin Rearmar), porque la 'acción' requerida de doCronMyNewFunction(doc) no existe, id es" "recurring_event::1"
Esta corrección se puede hacer a través de una Pausa/Reanudar añadiendo la función y luego ajustando el documento de control con el id especificado o CLAVE (a través de un toggle activo a falso y luego verdadero) -o- ajustando el documento de control para apuntar a una función existente en su manejador.
A continuación, la utilidad toNumericFixed(número, precisión) sólo permite un bonito formato compacto de flotantes para nuestros mensajes de registro.
1 2 3 4 |
función toNumericFixed(número, precisión) { var multi = Matemáticas.pow(10, precisión); devolver Matemáticas.redondo((número * multi).toFixed(precisión + 1)) / multi; } |
Por último, la utilidad toLocalISOTime(d) sólo permite un formato compacto de fechas para nuestros mensajes de registro.
1 2 3 4 |
función toLocalISOTime(d) { var tzoffset = (nuevo Fecha()).getTimezoneOffset() * 60000; /desplazamiento en milisegundos devolver (nuevo Fecha(d.getTime() - tzoffset)).toISOString().corte(0, -1); } |
Necesitamos una llamada de retorno del temporizador para ejecutar la lógica de usuario y volver a activar el temporizador
La última función JavaScript de "cron_impl_2func_651" es la llamada de retorno del temporizador, a la que se llama cuando se activa el temporizador programado. La función de devolución de llamada debe ser una función de nivel superior que reciba un único argumento, el contexto.
En este caso en nuestro manejador OnUpdate referenciamos una función JavaScript de Devolución de llamada(doc) con un contexto de doc (nuestro documento de control del planificador activo de type="recurring_event")
En la versión 6.6 podemos crear otro temporizador dentro de un temporizador pero para todas las versiones anteriores necesitaremos lanzar una mutación a una función "helper" (evitamos cuidadosamente la recursión infinita). En la 6.6 la función helper no es necesaria y la lógica se ha simplificado sustancialmente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
función Devolución de llamada(doc) { pruebe { var despedido = nuevo Fecha(); // Comprobar si es necesario un análisis adicional sólo disparamos en un recurring_event que esté activo si (doc.tipo !== "evento_recurrente") devolver; // doc debe tener 'action', 'dynamic {}', verbose {}, dynamic.state si (!doc.acción || !doc.dinámico || !doc.verbose || !doc.dinámico.estado) devolver; // procesa cualquier doc.dynamic.state PERO pendiente si (doc.dinámico.estado === "pendiente") devolver; // ================== // Comprobar si sigue activo // Nos aseguramos de que en KV el 'doc' sigue existiendo y que sigue activo si no solo // retorna saltándose así la acción y no rearmando el temporizador. Tenga en cuenta que `travel-sample` es // alias al mapa 'cron_bkt var mediados de = doc.tipo + '::' + doc.id; // hacer nuestra CLAVE var curDoc = null; pruebe { // leer la versión actual del documento desde KV, por ejemplo curDoc curDoc = cron_bkt[mediados de]; } captura (e) {} // necesario para pre 6.5, nota pura 6.5+ despliegue devuelve null sans excepción var motivo = null; si (!curDoc || curDoc === null) { motivo = "Falta el documento cron"; } si no si (!curDoc.activo) { motivo = "cron document has active = false"; } si no si (!curDoc.dinámico.estado || curDoc.dinámico.estado !== doc.dinámico.estado) { motivo = "cron document wrong dynamic.state expected " + doc.dinámico.estado; } si no si (crc64(doc) !== crc64(curDoc)) { motivo = "documento cron cambiado"; } si (motivo !== null) { si (!curDoc || curDoc === null || curDoc.verbose.programador >= 1) { registro(Llamada de retorno X + mediados de + "ignorar/detener la programación de este temporizador porque " + motivo); } si (!curDoc || curDoc === null || curDoc.verbose.programador >= 4) { registro(Devolución de llamada Y + mediados de + ' temporizador doc', doc); registro(Devolución de llamada Z + mediados de + KV curDoc, curDoc); } devolver; } // ================== // Verifica que la rutina de usuario existe y si es así evalúala // Supongamos que curDoc.action contiene algo como "doCronActionA" y tenemos una función en // este manejador como "doCronActionA(doc)". Abajo usamos curDoc como debería ser el usuario final // capaz de alterar la función JavaScript evaluada. Ejecutaremos dos (2) evals. // Primero comprueba que la función JavaScript existe. La evaluación se produce en un // función de utilidad compartida con Callback si (!verifyFunctionExistsViaEval(curDoc, mediados de)) { // curDoc.action no existía, ya hemos registrado el problema devolver; } // Segundo eval ejecuta y procesa la función de usuario ejecutamos la función definida // con un argumento de curDoc var beg_act = nuevo Fecha(); var resultado = null; evalúe("resultado = " + curDoc.acción + "(curDoc);"); var fin_acto = nuevo Fecha(); var atime_ms = fin_acto.getTime() - beg_act.getTime(); si (curDoc.verbose.programador >= 2) registro(Devolución de llamada R + mediados de + ' acción tomada ' + toNumericFixed((atime_ms / 1000), 3) + ' sec., devuelto ' + resultado); // ================== // Calcula la próxima vez y muta el documento de control para nuestra función de ayuda // que creará otra mutación tal que OnUpdate de esta función recogerá // y generar el temporizador (evita el problema del MB-38554). var hora = curDoc.hora; var min = curDoc.min; var temporizador_fecha = getNextRecurringDate(hora, min); curDoc.dinámico.retardo_previo = toNumericFixed(((despedido.getTime() / 1000) - curDoc.dinámico.siguiente_sched), 3); curDoc.dinámico.anterior_programado = curDoc.dinámico.siguiente_sched; curDoc.dinámico.tiempo_anterior = Matemáticas.redondo(despedido.getTime() / 1000); curDoc.dinámico.hora_anterior = toNumericFixed((atime_ms / 1000), 3); curDoc.dinámico.estado = "pendiente"; curDoc.dinámico.siguiente_sched = Matemáticas.redondo(temporizador_fecha.getTime() / 1000); pruebe { cron_bkt[mediados de] = curDoc; } captura (e) { registro('Ayuda de devolución de llamada: F ' + mediados de + ' FATAL no se pudo actualizar el ciclo cron de KV ' + curDoc.acción); devolver; } si (curDoc.verbose.programador >= 1) { registro(Devolución de llamada A + mediados de + gen mutación #1 a doc para forzar rearme horario en ' + toLocalISOTime(temporizador_fecha)); } si (curDoc.verbose.programador >= 2) { registro(Llamada de retorno B + mediados de + ' horario ' + curDoc.dinámico.anterior_programado + ', actual ' + curDoc.dinámico.tiempo_anterior + ', retrasar ' + curDoc.dinámico.retardo_previo + ', tomó ' + curDoc.dinámico.hora_anterior); } si (curDoc.verbose.programador >= 3) { registro(Llamada de retorno C + mediados de + ' curDoc', curDoc); } } captura (e) { var mediados de = doc.tipo + '::' + doc.id; // hacer nuestra CLAVE registro(Devolución de llamada E + mediados de + ' Excepción de error:', e); } } |
Necesitamos una función de ayuda para desencadenar una nueva mutación
Dado que antes de la versión 6.6 (que aún no se ha publicado) no se puede crear un temporizador desde la llamada de retorno de un temporizador en ejecución, necesitamos una segunda función de eventos (junto con "allow_interbucket_recursion":true) para activar una mutación de forma que podamos generar todos nuestros temporizadores en el punto de entrada OnUpdate(doc,meta) de la función de eventos principal. Hacemos esto de la siguiente manera:
- cron_impl_2func_651 OnUpdate(doc,meta) recibe una mutación, programa un temporizador
- cron_impl_2func_651 Después de una cantidad de tiempo cuando el temporizador madura el Devolución de llamada(doc) se ejecuta la rutina, primero ejecuta la acción de usuario deseada y luego crea una mutación #1 en el documento de control (que no es vista por la Función creadora para evitar la recursividad)
- cron_impl_2func_651_help OnUpdate(doc,meta) recibe una mutación, hace otra mutación #2 en el documento de control esto desencadena 1. anterior en un ciclo sin fin.
Nota, en la versión 6.6 de Couchbase no necesitamos una función de ayuda en absoluto porque se te permite crear un temporizador desde dentro de un temporizador en ejecución. Esto simplifica enormemente la lógica necesaria para hacer un cron sistema[2].
La única función JavaScript en "cron_impl_2func_651_help" OnUpdate(doc,meta) se muestra a continuación.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
función OnUpdate(doc, meta) { // fix for 6.5.X growing bucket ops si (meta.id.empiezaCon("fix_timer_scan_issue:")) upsertOneDocPerBucket(doc, meta); pruebe { // Comprobar que el doc tiene los valores deseados si (!doc.tipo || doc.tipo !== "evento_recurrente" || !doc.activo || doc.activo != verdadero) devolver; // doc debe tener 'action', 'dynamic {}', verbose {}, dynamic.state si (!doc.acción || !doc.dinámico || !doc.verbose || !doc.dinámico.estado) devolver; // Sólo procesa el estado pendiente esto sólo existirá durante un 'breif' tiempo si (doc.dinámico.estado !== "pendiente") devolver; var mediados de = doc.tipo + '::' + doc.id; // hacer nuestra CLAVE var newdoc = null; pruebe { // leer la versión actual del documento desde KV, por ejemplo curDoc newdoc = cron_bkt[mediados de]; } captura (e) {} // necesario para pre 6.5, nota pura 6.5+ despliegue devuelve null sans excepción var motivo = null; si (!newdoc || newdoc == null) { motivo = "Falta el documento cron"; } si no si (!newdoc.activo) { motivo = "cron document has active = false"; } si no si (!newdoc.dinámico.estado || newdoc.dinámico.estado !== doc.dinámico.estado) { motivo = "cron document wrong dynamic.state expected " + doc.dinámico.estado; } si no si (crc64(doc) !== crc64(newdoc)) { motivo = "documento cron cambiado"; } si (motivo != null) { si (!newdoc || newdoc == null || newdoc.verbose.programador >= 1) { registro(Ayuda OnUpdate: X detiene la programación porque ' + motivo + ',', newdoc) devolver; } } newdoc.dinámico.estado = "rearmar"; // cron_bkt[mid] = newdoc; si (!tryBucketKvWriteWithLog('Ayuda OnUpdate: F', mediados de, newdoc)) { // Error al escribir newdoc en cron_bkt[key] el error ha sido registrado // y no hay nada más que podamos hacer. devolver; } si (newdoc.verbose.programador >= 1) { registro(Ayuda 'OnUpdate: A ' + mediados de + ' mutación #2 a doc para forzar rearme horario'); } si (newdoc.verbose.programador >= 3) { registro(Ayuda OnUpdate: B ' + mediados de + ',', newdoc); } } captura (e) { registro(Ayuda OnUpdate: E ' + meta.id + ', Excepción de error:', e); } } función tryBucketKvWriteWithLog(etiqueta, clave, doc) { var éxito = falso; var intenta = 0; mientras que (intenta < 10) { intenta++; pruebe { // crítico que lo de abajo tenga éxito, porque si no el ciclo cron se romperá cron_bkt[clave] = doc; éxito = verdadero; romper; } captura (e) { registro(etiqueta + ' ' + clave + ' WARN no pudo actualizar KV intenta ' + intenta, e); } } si (!éxito) { registro(etiqueta + ' ' + +clave + ' FATAL no se pudo actualizar el ciclo cron de KV, intentó ' + intenta + ', deteniendo ' + curDoc.acción); } devolver éxito; } |
La función helper necesita algunas utilidades
Estas utilidades proporcionan un arreglo para 6.5.X cubo creciente ops asegurándose de que se dispara un temporizador de eventos en cada vBucket en el momento oportuno.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
// FIXUP: ADDIN FUNCTON // fix for 6.5.X growing bucket ops función upsertOneDocPerBucket(doc, meta) { var crcTable = makeCRC32Table(); // hacer un documento por cubo var isVerbose = 0; var esMacOS = falso; // Estaría bien que esto fuera una constante expuesta en Eventing var números = 1024; // por defecto es linux/PC si (esMacOS) { números = 64; } var beg = (nuevo Fecha).getTime(); var resultado = getKeysToCoverAllPartitions(crcTable, "_tmp_vbs:", números); para (var vb=0; vb<números; vb++) { // fuerza bruta para encajar un prefijo de clave en un vBucket var tst = resultado[vb]; si (isVerbose > 1 || (isVerbose == 1) && (vb < 3 || vb > números -4)) { registro("CLAVE: " + tst); } si no { si (vb == 5) consola.registro("\t*\n\t*\n\t*"); } // actualizar los elementos para provocar una mutación para nuestra función PRIMARIA cron_bkt[tst] = { "tipo": "_tmp_vbs", "vb": vb, "ts_millis": beg, "llave": tst }; } var fin = (nuevo Fecha).getTime(); registro("seeding one doc to each vBucket in primary_bucket alias (took " + (fin - beg) + " mililis)"); } // FIXUP: ADDIN FUNCTON // fix for 6.5.X growing bucket ops función showHex(n) { devolver n.toString(16); } // FIXUP: ADDIN FUNCTON // fix for 6.5.X growing bucket ops función makeCRC32Table() { var crcTable = []; var c; para(var n =0; n < 256; n++){ c = n; para(var k =0; k < 8; k++){ c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } crcTable[n] = c; } devolver crcTable; } // FIXUP: ADDIN FUNCTON // fix for 6.5.X growing bucket ops función crc32(crcTable,str) { var crc = 0 ^ (-1); para (var i = 0; i < str.longitud; i++ ) { crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF]; } devolver (crc ^ (-1)) >>> 0; } // FIXUP: ADDIN FUNCTON // fix for 6.5.X growing bucket ops función getKeysToCoverAllPartitions(crcTable,keyPrefix,partitionCount) { var resultado = []; var resto = partitionCount; para (var i = 0; resto > 0; i++) { var clave = keyPrefix + i; var rv = (crc32(crcTable,clave) >> 16) & 0x7fff; var actualPartition = rv & partitionCount - 1; si (!resultado[actualPartition] || resultado[actualPartition] === indefinido) { resultado[actualPartition] = clave; resto--; } } devolver resultado; } |
Ahora vamos a desplegar las dos Funciones Eventing
Hemos revisado mucho código y el diseño del planificador inicial, ahora es el momento de ver cómo funciona todo junto.
Recuerde que en este ejemplo hay tres cubos muestra-viaje (un ejemplo de conjunto de datos por defecto), metadatos(el cubo de metadatos es un bloc de notas para Eventing y puede compartirse con otras funciones de Eventing) y, por último, la función crondata (que contiene nuestros cronogramas). La dirección viaje-muestra tiene un tamaño de 100 MB y los otros dos cubos metadatos y crondata deben tener un tamaño de 200 MB y existir ya según las instrucciones de "Requisitos previos".
- Verifique la configuración actual de su cubo accediendo a la página Consola Web de Couchbase > Cubos página:
Para desplegar la función Eventing "cron_impl_2func_651"puede seguir uno de estos dos métodos:
- Complejidad básica, Método #1 Descargar/Importar
- Complejidad media, Método #2 Añadir función manualmente, Cortar y pegar JavaScript
Método #1 Descargar/Importar
Importar la 1ª función "cron_impl_2func_651"
Descargue la primera función Eventing con todos los ajustes necesarios, haga clic con el botón derecho del ratón en el siguiente enlace y seleccione Guardar enlace como para descargar el archivo cron_impl_2func_651.json en tu sistema de archivos local.
Desde el Consola Web de Couchbase > Eventos haga clic en IMPORTARnavega hasta el archivo cron_impl_2func_651.jsonselecciónelo y ábralo. La página AÑADIR FUNCIÓN aparece el cuadro de diálogo.
En el AÑADIR FUNCIÓN para cada uno de los elementos de la función, facilite la siguiente información. Tenga en cuenta el archivo JSON cron_impl_2func_651.json preconfigurará todos los ajustes correctamente para este ejemplo:
- Para el Cubo de origen y compruebe que está en crondata.
- Para el Cubo de metadatos y compruebe que está en metadatos.
- Compruebe que cron_impl_2func_651 es el nombre de la función que está creando en el campo Nombre de la función cuadro de texto.
- [Paso opcional] Introduzca el texto Un programador cron parte 1en el Descripción cuadro de texto.
- Para el Ajustes utilice los valores por defecto.
- Para el Fijaciones verifique que existen dos enlaces.
- Para la vinculación, el "alias de cubo", especifica cron_bkt como "nombre alias" del cubo, y seleccione
crondata como cubo asociado, y el modo debe ser "lectura y escritura". - Para la vinculación, el "alias de cubo", especifica ts_bkt como "nombre alias" del cubo, y seleccione
viaje-muestra como cubo asociado, y el modo debe ser "lectura y escritura". - La configuración en el cuadro de diálogo debe ser como la siguiente:
- Después de verificar toda la información requerida en el cuadro de diálogo AÑADIR FUNCIÓN, haga clic en Siguiente: Añadir Código. Aparecerá la página cron_impl_2func_651 (con el código JavaScript precargado).
- Para volver a la pantalla de Eventos, pulse el botón '< volver a Concurso completo(debajo del editor) o haga clic en el enlace Eventos ficha.
Importar la 2ª Función "cron_impl_2func_651_help"
Descargue la segunda función Eventing con todos los ajustes necesarios, haga clic con el botón derecho del ratón en el siguiente enlace y seleccione Guardar enlace como para descargar el archivo cron_impl_2func_651_help.json en tu sistema de archivos local.
Desde el Consola Web de Couchbase > Eventos haga clic en IMPORTARnavega hasta el archivo cron_impl_2func_651_help.jsonselecciónelo y ábralo. La página AÑADIR FUNCIÓN aparece el cuadro de diálogo.
En el AÑADIR FUNCIÓN para cada uno de los elementos de la función, facilite la siguiente información. Tenga en cuenta el archivo JSON cron_impl_2func_651_help.json preconfigurará todos los ajustes correctamente para este ejemplo:
- Para el Cubo de origen y compruebe que está en crondata.
- Para el Cubo de metadatos y compruebe que está en metadatos.
- Compruebe que cron_impl_2func_651_help es el nombre de la función que está creando en el campo Nombre de la función cuadro de texto.
- [Paso opcional] Introduzca el texto Un ayudante cron como programador parte 1en el Descripción cuadro de texto.
- Para el Ajustes utilice los valores por defecto.
- Para el Fijaciones verifique que sólo existe un enlace.
- Para la vinculación, el "alias de cubo", especifica cron_bkt como "nombre alias" del cubo, y seleccione
crondata como cubo asociado, y el modo debe ser "lectura y escritura". - La configuración en el cuadro de diálogo debe ser como la siguiente:
- Después de verificar toda la información requerida en el cuadro de diálogo AÑADIR FUNCIÓN, haga clic en Siguiente: Añadir Código. Aparecerá la página cron_impl_2func_651_help (con el código JavaScript precargado).
- Para volver a la pantalla de Eventos, pulse el botón '< volver a Concurso completo(debajo del editor) o haga clic en el enlace Eventos ficha.
Método #2 Añadir función manualmente, cortar y pegar JavaScript
Crear manualmente "cron_impl_2func_651"
Para añadir la primera función de Eventing desde Consola Web de Couchbase > Eventos haga clic en AÑADIR FUNCIÓNpara añadir una nueva función. La dirección AÑADIR FUNCIÓN aparece el cuadro de diálogo.
En el AÑADIR FUNCIÓN para cada elemento de la función, facilite la siguiente información:
- Para el Cubo de origen seleccione crondata.
- Para el Cubo de metadatos seleccione metadatos.
- Visite cron_impl_2func_651 es el nombre de la función que está creando en el campo Nombre de la función cuadro de texto.
- [Paso opcional] Introduzca el texto Un programador cron parte 1en el Descripción cuadro de texto.
- Para el Ajustes utilice los valores por defecto.
- Para el Fijaciones crear dos enlaces:
- Para la vinculación, el "alias de cubo", especifica cron_bkt como "nombre alias" del cubo, y seleccione
crondata como cubo asociado, y el modo debe ser "lectura y escritura". - Para la vinculación, el "alias de cubo", especifica ts_bkt como "nombre alias" del cubo, y seleccione
viaje-muestra como cubo asociado, y el modo debe ser "lectura y escritura". - Una vez configurados los ajustes, el cuadro de diálogo debería tener este aspecto:
- Tras facilitar toda la información requerida en el AÑADIR FUNCIÓN pulse Siguiente Añadir código. En cron_impl_2func_651 aparece el cuadro de diálogo. La página cron_impl_2func_651 contiene inicialmente un bloque de código. Usted sustituirá su cron_impl_2func_651 en este bloque.
- Copie el siguiente código fuente JavaScript de Eventing Function (618 líneas) y péguelo en el bloque de código del marcador de posición de cron_impl_2func_651
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618/*Función "cron_impl_2func_651" también requiere "cron_impl_2func_651_help"Crear un sistema cron básico utilizando Eventing permite que una función recurrente ejecute una actividad a unatiempo especificado cada día, hora, min, 30 seg. y 15 seg. Utilizamos un cubo llamado 'crondata'.aliased to 'cron_bkt' que puede contener uno o más documentos de control de tipo = "recurring_event".Los siguientes usos de los temporizadores no funcionan de forma fiable en las versiones 6.5 y 6.5.1 de Couchbasea) programar un temporizador Eventing dentro de la devolución de llamada de un temporizadorb) sobrescribir un temporizador existente por idAdemás, la posibilidad de cancelar un temporizador no existe en las versiones 6.5 y 6.5.1 de Couchbase.Para este ejemplo, proporcionamos una función de usuario real que construye un documento de caché "estático" recurrentedel bucket `viaje-muestra` mediante una consulta N1QL y guardar el resultado de nuevo en `viaje-muestra` medianteel alais 'ts_bkt'. Esta función JavaScript es doCronActionA(), también proporcionamos dos marcadores de posicióndoCronActionB() y doCronActionC() para experimentación adicional.Prueba Doc:{"type": "recurring_event", // La CLAVE será <>::<>"id":1, //"hour":14, // La hora del día 0-23, *, *2X, *4X a activar"min":54, // El minuto en la hora 0-59, *, *2X, *4X a disparar"action": "doCronActionA", // Qué función ejecutar en el disparador"active":false, // Bandera para armar o deshabilitar este horario"verbose" : {"user_func":2, // Nivel de registro para la lógica de la acción : 0=ninguno, etc. etc."scheduler":3 // Nivel de registro para la lógica cron : 0=ninguno, etc. etc.},"dinámico" : {"state": "arm", // Estados "arm"|"rearm"|"pending" si cualquier valor menos "pending" inicia una programación."next_sched": 0, // Número de segundos desde la fecha de inicio hasta la siguiente programación deseada"prev_sched": 0, // Número de segundos desde la fecha de inicio de la programación anterior"prev_etime": 0, // Número de segundos desde la época para la hora de ejecución real del programa anterior"prev_delay": 0, // Número de segundos que el temporizador se retrasó respecto a la programación."prev_atime" 0 // Número de segundos que ha tardado el usuario "acción}}INSERT INTO `crondata` (KEY,VALUE) VALUES ("recurring_event::1",{"type": "recurring_event","id":1,"hora":14,"min":54,"acción": "doCronActionA","verbose" : {"user_func":2,"programador":3},"activo":false,"dinámico" : {"estado": "brazo","next_sched": 0,"prev_sched": 0,"prev_etime": 0,"prev_delay": 0,"prev_atime": 0}});Nota, puede omitir verbose{} y dynamic{} ya que serán auto-creados por este Eventing principalFunction "cron_impl_2func_651". Si falta verbose{} los niveles de registro serán por defectoverbose" : { "user_func":1, "scheduler":1 }INSERT INTO `crondata` (KEY,VALUE) VALUES ("recurring_event::1",{"type": "recurring_event","id":1,"hora":14,"min":54,"acción": "doCronActionA","activo":false});N1QL : Crear un índice para consultar datos sin especificar clavesCREATE primary INDEX on `crondata` ;N1QL : Verificar o inspeccionar los ajustes en la programaciónSELECT * FROM `crondata` WHERE type="recurring_event";N1QL : Armar o activarUPDATE `crondata` SET active = true WHERE type="recurring_event" AND id=1 ;N1QL : Desarmar o poner inactivoUPDATE `crondata` SET active = false WHERE type="recurring_event" AND id=1 ;N1QL : Ajustar el tiempo de disparoUPDATE `crondata` SET hour = 11, min = 30 WHERE type="recurring_event" AND id=1 ;N1QL : Ajustar el registroUPDATE `crondata` SET verbose.user_func = 1, verbose.scheduler = 0 WHERE type="recurring_event" AND id=1 ;N1QL : Borrar el programaDELETE FROM `crondata` WHERE type="recurring_event" AND id=1 ;El campo de acción es importante y "debe" existir en esta función de eventos.Nombre JavaScript p.ej. MyFunc y debe implementar como en el ejemplo doCronActionA(doc) dondedoc será el elemento actualmente activo de tipo = 'recurring_event' leído del cubo de aliascron_bkt' cuando se dispara el temporizador. La función JavaScript de la acción debe devolver trueo false utilizado para fines de registro. Si la acción no existe es un error y una advertenciase registra y el temporizador se desactiva.En la versión 6.5+ de Couchbase para añadir una nueva función diaria tipo cron basta con pausar el manejador activoinserta tu nueva función doCronActionB(doc) {...} y luego Reanuda el manejador de eventos. El bonitocosa es que si un temporizador iba a ser disparado voluntad la función estaba en pausa NO se perderá, cuando ustedreanudar la función se procesará en la siguiente franja horaria disponible.Cualquier cambio en una estructura de control creará una nueva programación periódica o temporizador y cancelará la estructura de control anterior.programación anterior actual esto incluye cambiar el nivel de verbosidad. El temporizador anteriorSin embargo, cuando se ejecute, realizará una suma de comprobación en la estructura de control actual.de KV con el contexto que se le ha pasado y, si es diferente, la llamada de retorno ignorará la programación anterior.Esta lógica podría modificarse para procesar inmediatamente si el horario ha expirado la búsqueda delcadena "OnUpdate U" en el código siguiente.*/// ==================/* PIDE A LAS FUNCIONES DE USUARIO QUE SE EJECUTEN UNA VEZ AL DÍA, HORA O MINUTO - LO QUE QUIERAS DEBAJO */función doCronActionA(doc) {pruebe {// Comprobar que el doc tiene los valores deseadossi (!doc.tipo || doc.tipo !== "evento_recurrente" || !doc.activo || doc.activo !== verdadero) devolver;si (doc.verbose.usuario_func >= 1)registro(doc.acción + Acción del usuario controlada por ' + doc.tipo + '::' + doc.id);// se trata de una consulta N1QL 6.5 (función no disponible en GA anterior a 6.5)// Crear un iterador N1QL embebido emitiendo una sentencia SELECT para obtener el// recuentos de compañías aéreas por país. Haz un nuevo documento y escríbelo a KV// Utilizaremos el iterador para crear un documento KV que represente los resultados de un// HARD largo incrustado N1QL consulta y escribir de nuevo a KV, la idea es mantener// un cálculo actualizado una vez al día para que pueda leerse "rápidamente// por otras Funciones de Eventos, otros servicios de Couchbase o SDKs.// Considere si tuviéramos 1 millón de documentos en un minuto ¿realmente queremos usar N1QL?// recalcular algo que es casi estático para todo 1 millón de documentos, de// por supuesto que no, así que hacemos un valor intermedio que se puede leer en Eventing// y se utiliza a través de una única lectura KV "ligera".var q_iter = SELECCIONE país,cuente( * ) cntDESDE `viaje-muestraDONDE `tipo` = aerolíneaGRUPO POR país;// bucle a través del conjunto de resultados y actualizar el mapa 'acumular'var acumular = {};var idx = 0;para (var val de q_iter) {si (doc.verbose.usuario_func >= 2)registro(doc.acción + ' N1QL idx ' + idx + País + val.país + " cnt " + val.cnt);acumular[val.país] = val.cnt;idx++;}// cerrar el iterador N1QL incrustadoq_iter.cerrar();// Ahora hagamos un documento KV en caché que represente un N1QL incrustado de longitud HARD// consulta y escribirla de nuevo a KV, necesitamos una CLAVE y un tipo e id y luego// insértalo en el cubo `viaje-muestra`.var cachedoc = {};cachedoc.tipo = "cron_cache";cachedoc.id = "aerolíneas_por_país";cachedoc.fecha = nuevo Fecha();cachedoc.datos = acumular;var ckey = cachedoc.tipo + '::' + cachedoc.id;ts_bkt[ckey] = cachedoc;si (doc.verbose.usuario_func >= 2) {registro(doc.acción + ' upsert to KV with KEY ' + ckey + ' cachedoc ', cachedoc);}} captura (e) {registro(doc.acción + ' Excepción de error:', e);devolver falso;}devolver verdadero;}función doCronActionB(doc) {pruebe {// comprobar que el doc tiene los valores deseadossi (doc.tipo !== "evento_recurrente" || doc.activo !== verdadero) devolver;si (doc.verbose.usuario_func >= 1)registro(doc.acción + Acción del usuario controlada por ' + doc.tipo + '::' + doc.id);// SU LÓGICA AQUÍ} captura (e) {registro(doc.acción + ' Excepción de error:', e);devolver falso;}devolver verdadero;}función doCronActionC(doc) {pruebe {// comprobar que el doc tiene los valores deseadossi (doc.tipo !== "evento_recurrente" || doc.activo !== verdadero) devolver;si (doc.verbose.usuario_func >= 1)registro(doc.acción + Acción del usuario controlada por ' + doc.tipo + '::' + doc.id);// SU LÓGICA AQUÍ} captura (e) {registro(doc.acción + ' Excepción de error:', e);devolver falso;}devolver verdadero;}/* FUNCIONES DE USUARIO FINAL PARA EJECUTAR UNA VEZ AL DÍA, HORA O MINUTO - LO QUE QUIERAS POR ENCIMA */// ==================// FIXUP: ADDIN FUNCTONfunción noopTimer(contexto) {// fix for 6.5.X growing bucket opspruebe {si (contexto.tipo === "_tmp_vbs" && contexto.vb === 0) {// log("noopTimer timers firing, printing only for vBucket 0");}} captura (e) {registro("OnUpdate Excepción en callback noopTimer:", e);}}// FIXUP: ADDIN FUNCTONfunción rearmTimer(contexto) {// fix for 6.5.X growing bucket opspruebe {si (contexto.tipo === "_tmp_vbs" && contexto.vb === 0) {// Actualizar/tocar todos los documentos en el helper_bucket la función helper entonces// muta todos los 1024 de tipo == vbs_seed (64 en MacOS) para crear un ciclo de recuración.// log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context);// generar una mutación para rearmar la función HELPER: fix_scan_issue// que a su vez hará nuevas mutaciones para esta Funciónvar cur = cron_bkt[contexto.clave];si (cur && cur.ts_millis === contexto.ts_millis) {// log("rearmTimer update fix_timer_scan_issue::1 in helper_bucket alias only for vBucket 0");var ahora = nuevo Fecha();cron_bkt["fix_timer_scan_issue::1"] = { "última_actualización": ahora };} si no {// NOOP tuvimos múltiples ciclos de temporizador, deja que este se detenga tranquilamente.}}} captura (e) {registro("OnUpdate Excepción en callback rearmTimer:", e);}}// FIXUP: ADDIN FUNCTONfunción genNoopTimers(doc, meta, segundos) {// fix for 6.5.X growing bucket opspruebe {// redundante pero a lo segurosi (doc.tipo === "_tmp_vbs") {// Dado que estamos utilizando una función diferente un temporizador en todos nuestros vBuckets hacer immeadiately (puede tardar hasta 15 segundos)// Si utilizáramos la recursividad cross bucket para rearmar todos los temporizadores de forma recurrente añadiríamos un retardo de al menos 40 segundos.crearTiempo(noopTimer, nuevo Fecha(), null, doc);si (doc.vb === 0) {// Actualizar/tocar todos los documentos en el helper_bucket la función helper entonces// muta todos los 1024 de tipo == vbs_seed (64 en MacOS) para crear un ciclo de recuración.// log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context);// generar una mutación para rearmar la función HELPER: fix_scan_issue// que a su vez hará nuevas mutaciones para esta Función// log("genNoopTimers hace que el temporizador se rearme fix_timer_scan_issue::1");crearTiempo(rearmTimer, nuevo Fecha(nuevo Fecha().getTime() + segundos * 1000), null, doc);}}} captura (e) {registro("Excepción OnUpdate en genNoopTimers:", e);}}función OnUpdate(doc, meta) {// fix for 6.5.X growing bucket opssi (doc.tipo === "_tmp_vbs") genNoopTimers(doc, meta, 30);si (!cron_bkt["fix_timer_scan_issue::1"]) {cron_bkt["fix_timer_scan_issue::1"] = {};}pruebe {// Comprobar si es necesario un análisis adicional sólo activamos en un recurring_event activosi (doc.tipo !== "evento_recurrente" || doc.activo !== verdadero) devolver;var actualizar_doc = falso;si (!doc.dinámico) {// Añadir si falta doc.dynamic con valores por defectodoc.dinámico = {"estado": "brazo","next_sched": 0,"prev_sched": 0,"prev_etime": 0,"prev_delay": 0,"prev_atime": 0};// necesitamos actualizar el documento una vez que tengamos el siguiente horarioactualizar_doc = verdadero;}si (!doc.verbose) {// Añadir si falta doc.dynamic con valores por defectodoc.verbose = {"user_func": 1,"programador": 1};// necesitamos actualizar el documento una vez que tengamos el siguiente horarioactualizar_doc = verdadero;}// No procesar dynamic.state pendientesi (!doc.dinámico || !doc.dinámico.estado || doc.dinámico.estado === "pendiente") devolver;var mediados de = doc.tipo + "::" + doc.id; // es lo mismo que meta.id o la CLAVEvar hora = doc.hora;var min = doc.min;// Hacer una comprobación eval la función JavaScript existe. La eval se produce en un// función de utilidad compartida con RecurringCallbacksi (!verifyFunctionExistsViaEval(doc, mediados de)) {// doc.action no existía, ya hemos registrado el problemadevolver;}// Obtener el siguiente tiempo de ejecución válidovar temporizador_fecha = getNextRecurringDate(hora, min);var siguiente_sched = Matemáticas.redondo(temporizador_fecha.getTime() / 1000);si (!actualizar_doc && siguiente_sched !== doc.dinámico.siguiente_sched) {// el next_sched debe ser el mismo que el de la aplicación de ayuda, sin embargo// si desinstalamos/desinstalamos o pausamos/reanudamos podríamos tener que reprogramar a la siguiente franja horariaregistro(OnUpdate U + mediados de + calculado next_sched !== doc.dynamic.next_sched, delta ' +(siguiente_sched - doc.dinámico.siguiente_sched) + ', reprogramar');actualizar_doc = verdadero;}si (actualizar_doc) {// esta mutación es recursiva y se suprimirá, nos aseguramos de tener una estructura dinámicadoc.dinámico.siguiente_sched = siguiente_sched;// en lugar de la llamada a una función, para atrapar y reintentar si hay un problema de recursos// cron_bkt[mid] = doc;si (!tryBucketKvWriteWithLog(OnUpdate F, mediados de, doc)) {// Fallo al escribir doc en cron_bkt[key] se ha registrado el error// y no hay nada más que podamos hacer.devolver;}}// Programar un temporizador Eventingvar timer_id = crearTiempo(Devolución de llamada, temporizador_fecha, null, doc);si (doc.verbose.programador >= 1) {registro(OnUpdate A + mediados de + ' rcv mutación (inicial o rearme) programar temporizador en ' +toLocalISOTime(temporizador_fecha));}si (doc.verbose.programador >= 2) {registro(OnUpdate B + mediados de + Se ha creado un temporizador recurrente, timer_id ' + timer_id);}} captura (e) {registro(OnUpdate E + meta.id + ', Excepción de error:', e);}}función getNextRecurringDate(hora_str, min_str) {// Nota Javascript Las fechas están en milisegundosvar fecha_ahora = nuevo Fecha();var fecha_ret = nuevo Fecha();var hora;var min;pruebe {hora = parseInt(hora_str);} captura (e) {}pruebe {min = parseInt(min_str);} captura (e) {}// Nota, esto es sólo una sintaxis parcial simplista de 'crontab' con algunas ligeras extensiones// permite una vez al día, una vez por hora, una vez por minuto. También contiene algunos// sintaxis para proporcionar la capacidad de ejecutar dos veces por minuto o cuatro veces por minuto.si (hora_str === '*4X' && min_str === '*4X') {// una vez cada 15 segundos o cuatro veces por minutofecha_ret.setMilliseconds(0);fecha_ret.setSeconds(15);mientras que (fecha_ret.getTime() < fecha_ahora.getTime()) {fecha_ret.setSeconds(fecha_ret.getSeconds() + 15);}devolver fecha_ret;} si nosi (hora_str === '*2X' && min_str === '*2X') {// una vez cada 30 segundos o dos veces por minutofecha_ret.setMilliseconds(0);fecha_ret.setSeconds(30);mientras que (fecha_ret.getTime() < fecha_ahora.getTime()) {fecha_ret.setSeconds(fecha_ret.getSeconds() + 30);}devolver fecha_ret;} si nosi (hora_str === '*' && min_str === '*') {// una vez por minutofecha_ret.setMilliseconds(0);fecha_ret.setSeconds(0);fecha_ret.setMinutes(fecha_ret.getMinutes() + 1);} si nosi (hora_str !== '*' && isNaN(hora) === falso && min_str === '*') {// sólo una vez por minuto durante una hora determinadafecha_ret.setMilliseconds(0);fecha_ret.setSeconds(0);fecha_ret.setMinutes(fecha_ret.getMinutes() + 1);si (fecha_ret.getTime() < fecha_ahora.getTime()) {fecha_ret.setHours(hora);}si (fecha_ret.getTime() > fecha_ahora.getTime()) {fecha_ret.setDate(fecha_ret.getDate() + 1);fecha_ret.setSeconds(0);fecha_ret.setMinutes(0);fecha_ret.setHours(hora);}} si nosi (hora_str === '*' && min_str !== '*' && isNaN(min) === falso) {// una vez por hora en un minuto determinadofecha_ret.setMilliseconds(0);fecha_ret.setSeconds(0);fecha_ret.setMinutes(min);// programación para la próxima horafecha_ret.setHours(fecha_ret.getHours() + 1);} si nosi (isNaN(hora) === falso && isNaN(min) === falso) {// una vez al día durante una hora y un minuto determinadosfecha_ret.setMilliseconds(0);fecha_ret.setSeconds(0);fecha_ret.setMinutes(min);fecha_ret.setHours(hora);si (fecha_ret.getTime() < fecha_ahora.getTime()) {// programa para mañanafecha_ret.setDate(fecha_ret.getDate() + 1);}} si no {registro('getNextRecurringDate entrada ilegal hour_str <' +hora_str + '> min_str <' + min_str + '>');tirar nuevo Error('getNextRecurringDate entrada ilegal hour_str <' +hora_str + '> min_str <' + min_str + '>');devolver null;}devolver fecha_ret;}función verifyFunctionExistsViaEval(curDoc, id) {var resultado = falso;pruebe {// comprobar función si falta esta es inválida devolver resultadoresultado = evalúe("typeof" + curDoc.acción + " === 'function';");si (resultado === falso) {si (curDoc.verbose.programador >= 1)registro("Advertir/Desactivar (sin acción y sin rearme), porque se requiere una "acción" de " +curDoc.acción + "(doc) no existe, id es", id);devolver resultado;}} captura (e) {registro('verifyFunctionExistsViaEval Error exception:', e);}devolver resultado;}función toNumericFixed(número, precisión) {var multi = Matemáticas.pow(10, precisión);devolver Matemáticas.redondo((número * multi).toFixed(precisión + 1)) / multi;}función toLocalISOTime(d) {var tzoffset = (nuevo Fecha()).getTimezoneOffset() * 60000; /desplazamiento en milisegundosdevolver (nuevo Fecha(d.getTime() - tzoffset)).toISOString().corte(0, -1);}función tryBucketKvWriteWithLog(etiqueta, clave, doc) {var éxito = falso;var intenta = 0;mientras que (intenta < 10) {intenta++;pruebe {// crítico que lo de abajo tenga éxito, porque si no el ciclo cron se romperácron_bkt[clave] = doc;éxito = verdadero;romper;} captura (e) {registro(etiqueta + ' ' + clave + ' WARN no pudo actualizar KV intenta ' + intenta, e);}}si (!éxito) {registro(etiqueta + ' ' + +clave + ' FATAL no se pudo actualizar el ciclo cron de KV, intentó ' + intenta + ', deteniendo ' + curDoc.acción);}devolver éxito;}función Devolución de llamada(doc) {pruebe {var despedido = nuevo Fecha();// Comprobar si es necesario un análisis adicional sólo disparamos en un recurring_event que esté activosi (doc.tipo !== "evento_recurrente") devolver;// doc debe tener 'action', 'dynamic {}', verbose {}, dynamic.statesi (!doc.acción || !doc.dinámico || !doc.verbose || !doc.dinámico.estado) devolver;// procesa cualquier doc.dynamic.state PERO pendientesi (doc.dinámico.estado === "pendiente") devolver;// ==================// Comprobar si sigue activo// Nos aseguramos de que en KV el 'doc' sigue existiendo y que sigue activo si no solo// retorna saltándose así la acción y no rearmando el temporizador. Tenga en cuenta que `travel-sample` es// alias al mapa 'cron_bktvar mediados de = doc.tipo + '::' + doc.id; // hacer nuestra CLAVEvar curDoc = null;pruebe {// leer la versión actual del documento desde KV, por ejemplo curDoccurDoc = cron_bkt[mediados de];} captura (e) {} // necesario para pre 6.5, nota pura 6.5+ despliegue devuelve null sans excepciónvar motivo = null;si (!curDoc || curDoc === null) {motivo = "Falta el documento cron";} si nosi (!curDoc.activo) {motivo = "cron document has active = false";} si nosi (!curDoc.dinámico.estado || curDoc.dinámico.estado !== doc.dinámico.estado) {motivo = "cron document wrong dynamic.state expected " + doc.dinámico.estado;} si nosi (crc64(doc) !== crc64(curDoc)) {motivo = "documento cron cambiado";}si (motivo !== null) {si (!curDoc || curDoc === null || curDoc.verbose.programador >= 1) {registro(Llamada de retorno X + mediados de + "ignorar/detener la programación de este temporizador porque " + motivo);}si (!curDoc || curDoc === null || curDoc.verbose.programador >= 4) {registro(Devolución de llamada Y + mediados de + ' temporizador doc', doc);registro(Devolución de llamada Z + mediados de + KV curDoc, curDoc);}devolver;}// ==================// Verifica que la rutina de usuario existe y si es así evalúala// Supongamos que curDoc.action contiene algo como "doCronActionA" y tenemos una función en// este manejador como "doCronActionA(doc)". Abajo usamos curDoc como debería ser el usuario final// capaz de alterar la función JavaScript evaluada. Ejecutaremos dos (2) evals.// Primero comprueba que la función JavaScript existe. La evaluación se produce en un// función de utilidad compartida con RecurringCallbacksi (!verifyFunctionExistsViaEval(curDoc, mediados de)) {// curDoc.action no existía, ya hemos registrado el problemadevolver;}// Segundo eval ejecuta y procesa la función de usuario ejecutamos la función definida// con un argumento de curDocvar beg_act = nuevo Fecha();var resultado = null;evalúe("resultado = " + curDoc.acción + "(curDoc);");var fin_acto = nuevo Fecha();var atime_ms = fin_acto.getTime() - beg_act.getTime();si (curDoc.verbose.programador >= 2)registro(Devolución de llamada R + mediados de + ' acción tomada ' + toNumericFixed((atime_ms / 1000), 3) +' sec., devuelto ' + resultado);// ==================// Calcula la próxima vez y muta el documento de control para nuestra función de ayuda// que creará otra mutación tal que OnUpdate de esta función recogerá// y generar el temporizador (evita el problema del MB-38554).var hora = curDoc.hora;var min = curDoc.min;var temporizador_fecha = getNextRecurringDate(hora, min);curDoc.dinámico.retardo_previo =toNumericFixed(((despedido.getTime() / 1000) - curDoc.dinámico.siguiente_sched), 3);curDoc.dinámico.anterior_programado = curDoc.dinámico.siguiente_sched;curDoc.dinámico.tiempo_anterior = Matemáticas.redondo(despedido.getTime() / 1000);curDoc.dinámico.hora_anterior = toNumericFixed((atime_ms / 1000), 3);curDoc.dinámico.estado = "pendiente";curDoc.dinámico.siguiente_sched = Matemáticas.redondo(temporizador_fecha.getTime() / 1000);// en lugar de la llamada a una función, para atrapar y reintentar si hay un problema de recursos// cron_bkt[mid] = curDoc;si (!tryBucketKvWriteWithLog(Llamada de retorno F, mediados de, curDoc)) {// Error al escribir curDoc en cron_bkt[key] se ha registrado el error// y no hay nada más que podamos hacer.devolver;}si (curDoc.verbose.programador >= 1) {registro(Devolución de llamada A + mediados de + gen mutación #1 a doc para forzar rearme horario en ' +toLocalISOTime(temporizador_fecha));}si (curDoc.verbose.programador >= 2) {registro(Llamada de retorno B + mediados de + ' horario ' + curDoc.dinámico.anterior_programado +', actual ' + curDoc.dinámico.tiempo_anterior +', retrasar ' + curDoc.dinámico.retardo_previo +', tomó ' + curDoc.dinámico.hora_anterior);}si (curDoc.verbose.programador >= 3) {registro(Llamada de retorno C + mediados de + ' curDoc', curDoc);}} captura (e) {var mediados de = doc.tipo + '::' + doc.id; // hacer nuestra CLAVEregistro(Devolución de llamada E + mediados de + ' Excepción de error:', e);}}
- Después de pegar, aparece la pantalla que se muestra a continuación:
- Haga clic en Guardar.
- Para volver a la pantalla de Eventos, pulse el botón '< volver a Concurso completo(debajo del editor) o haga clic en el enlace Eventos
Crear manualmente "cron_impl_2func_651_help"
Para añadir la segunda función de Eventing desde Consola Web de Couchbase > Eventos haga clic en AÑADIR FUNCIÓNpara añadir una nueva función. La dirección AÑADIR FUNCIÓN aparece el cuadro de diálogo.
En el AÑADIR FUNCIÓN para cada elemento de la función, facilite la siguiente información:
- Para el Cubo de origen seleccione crondata.
- Para el Cubo de metadatos seleccione metadatos.
- Visite cron_impl_2func_651_help es el nombre de la función que está creando en el campo Nombre de la función cuadro de texto.
- [Paso opcional] Introduzca el texto Un ayudante cron como programador parte 1en el Descripción cuadro de texto.
- Para el Ajustes utilice los valores por defecto.
- Para el Fijaciones crear un enlace:
- Para la vinculación, el "alias de cubo", especifica cron_bkt como "nombre alias" del cubo, y seleccione
crondata como cubo asociado, y el modo debe ser "lectura y escritura". - Una vez configurados los ajustes, el cuadro de diálogo debería tener este aspecto:
- Tras facilitar toda la información requerida en el AÑADIR FUNCIÓN pulse Siguiente Añadir código. En cron_impl_2func_651_help aparece el cuadro de diálogo. La página cron_impl_2func_651_help contiene inicialmente un bloque de código. Usted sustituirá su cron_impl_2func_651_help en este bloque.
- Copie el siguiente código fuente JavaScript de Eventing Function (187 líneas) y péguelo en el bloque de código del marcador de posición de cron_impl_2func_651_help
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187/*La función "cron_impl_2func_651_help" también requiere "cron_impl_2func_651"Prueba Doc:{"type": "recurring_event", // La CLAVE será <>::<>"id":1, //"hour":14, // La hora del día 0-23, *, *2X, *4X a activar"min":54, // El minuto en la hora 0-59, *, *2X, *4X a disparar"action": "doCronActionA", // Qué función ejecutar en el disparador"active":false, // Bandera para armar o deshabilitar este horario"verbose" : {"user_func":2, // Nivel de registro para la lógica de la acción : 0=ninguno, etc. etc."scheduler":3 // Nivel de registro para la lógica cron : 0=ninguno, etc. etc.},"dinámico" : {"state": "arm", // Estados "arm"|"rearm"|"pending" si cualquier valor menos "pending" inicia una programación."next_sched": 0, // Número de segundos desde la fecha de inicio hasta la siguiente programación deseada"prev_sched": 0, // Número de segundos desde la fecha de inicio de la programación anterior"prev_etime": 0, // Número de segundos desde la época para la hora de ejecución real del programa anterior"prev_delay": 0, // Número de segundos que el temporizador se retrasó respecto a la programación."prev_atime" 0 // Número de segundos que ha tardado el usuario "acción}}Tenga en cuenta que puede omitir verbose{} y dynamic{}, ya que serán creados automáticamente por la función principal EventingFunction "cron_impl_2func_651". Si falta verbose{} los niveles de registro serán por defectoverbose" : { "user_func":1, "scheduler":1 }*/función tryBucketKvWriteWithLog(etiqueta, clave, doc) {var éxito = falso;var intenta = 0;mientras que (intenta < 10) {intenta++;pruebe {// crítico que lo de abajo tenga éxito, porque si no el ciclo cron se romperácron_bkt[clave] = doc;éxito = verdadero;romper;} captura (e) {registro(etiqueta + ' ' + clave + ' WARN no pudo actualizar KV intenta ' + intenta, e);}}si (!éxito) {registro(etiqueta + ' ' + +clave + ' FATAL no se pudo actualizar el ciclo cron de KV, intentó ' + intenta + ', deteniendo ' + curDoc.acción);}devolver éxito;}función OnUpdate(doc, meta) {// fix for 6.5.X growing bucket opssi (meta.id.empiezaCon("fix_timer_scan_issue:")) upsertOneDocPerBucket(doc, meta);pruebe {// Comprobar que el doc tiene los valores deseadossi (!doc.tipo || doc.tipo !== "evento_recurrente" || !doc.activo || doc.activo != verdadero) devolver;// doc debe tener 'action', 'dynamic {}', verbose {}, dynamic.statesi (!doc.acción || !doc.dinámico || !doc.verbose || !doc.dinámico.estado) devolver;// Sólo procesa el estado pendiente esto sólo existirá durante un 'breif' tiemposi (doc.dinámico.estado !== "pendiente") devolver;var mediados de = doc.tipo + '::' + doc.id; // hacer nuestra CLAVEvar newdoc = null;pruebe {// leer la versión actual del documento desde KV, por ejemplo curDocnewdoc = cron_bkt[mediados de];} captura (e) {} // necesario para pre 6.5, nota pura 6.5+ despliegue devuelve null sans excepciónvar motivo = null;si (!newdoc || newdoc == null) {motivo = "Falta el documento cron";} si nosi (!newdoc.activo) {motivo = "cron document has active = false";} si nosi (!newdoc.dinámico.estado || newdoc.dinámico.estado !== doc.dinámico.estado) {motivo = "cron document wrong dynamic.state expected " + doc.dinámico.estado;} si nosi (crc64(doc) !== crc64(newdoc)) {motivo = "documento cron cambiado";}si (motivo != null) {si (!newdoc || newdoc == null || newdoc.verbose.programador >= 1) {registro(Ayuda OnUpdate: X detiene la programación porque ' + motivo + ',', newdoc)devolver;}}newdoc.dinámico.estado = "rearmar";// cron_bkt[mid] = newdoc;si (!tryBucketKvWriteWithLog('Ayuda OnUpdate: F', mediados de, newdoc)) {// Error al escribir newdoc en cron_bkt[key] el error ha sido registrado// y no hay nada más que podamos hacer.devolver;}si (newdoc.verbose.programador >= 1) {registro(Ayuda 'OnUpdate: A ' + mediados de + ' mutación #2 a doc para forzar rearme horario');}si (newdoc.verbose.programador >= 3) {registro(Ayuda OnUpdate: B ' + mediados de + ',', newdoc);}} captura (e) {registro(Ayuda OnUpdate: E ' + meta.id + ', Excepción de error:', e);}}// FIXUP: ADDIN FUNCTON// fix for 6.5.X growing bucket opsfunción upsertOneDocPerBucket(doc, meta) {var crcTable = makeCRC32Table();// hacer un documento por cubovar isVerbose = 0;var esMacOS = falso; // Estaría bien que esto fuera una constante expuesta en Eventingvar números = 1024; // por defecto es linux/PCsi (esMacOS) {números = 64;}var beg = (nuevo Fecha).getTime();var resultado = getKeysToCoverAllPartitions(crcTable, "_tmp_vbs:", números);para (var vb=0; vb<números; vb++) {// fuerza bruta para encajar un prefijo de clave en un vBucketvar tst = resultado[vb];si (isVerbose > 1 || (isVerbose == 1) && (vb < 3 || vb > números -4)) {registro("CLAVE: " + tst);} si no {si (vb == 5) consola.registro("\t*\n\t*\n\t*");}// actualizar los elementos para provocar una mutación para nuestra función PRIMARIAcron_bkt[tst] = { "tipo": "_tmp_vbs", "vb": vb, "ts_millis": beg, "llave": tst };}var fin = (nuevo Fecha).getTime();registro("seeding one doc to each vBucket in primary_bucket alias (took " + (fin - beg) + " mililis)");}// FIXUP: ADDIN FUNCTON// fix for 6.5.X growing bucket opsfunción showHex(n) {devolver n.toString(16);}// FIXUP: ADDIN FUNCTON// fix for 6.5.X growing bucket opsfunción makeCRC32Table() {var crcTable = [];var c;para(var n =0; n < 256; n++){c = n;para(var k =0; k < 8; k++){c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));}crcTable[n] = c;}devolver crcTable;}// FIXUP: ADDIN FUNCTON// fix for 6.5.X growing bucket opsfunción crc32(crcTable,str) {var crc = 0 ^ (-1);para (var i = 0; i < str.longitud; i++ ) {crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];}devolver (crc ^ (-1)) >>> 0;}// FIXUP: ADDIN FUNCTON// fix for 6.5.X growing bucket opsfunción getKeysToCoverAllPartitions(crcTable,keyPrefix,partitionCount) {var resultado = [];var resto = partitionCount;para (var i = 0; resto > 0; i++) {var clave = keyPrefix + i;var rv = (crc32(crcTable,clave) >> 16) & 0x7fff;var actualPartition = rv & partitionCount - 1;si (!resultado[actualPartition] || resultado[actualPartition] === indefinido) {resultado[actualPartition] = clave;resto--;}}devolver resultado;}
- Después de pegar, aparece la pantalla que se muestra a continuación:
- Haga clic en Guardar.
- Para volver a la pantalla de Eventos, pulse el botón '< volver a Concurso completo(debajo del editor) o haga clic en el enlace Eventos
Despliegue de las dos funciones
Ahora estamos listos para iniciar las funciones de Eventing. Desde el Consola Web de Couchbase > Eventos pantalla:
- Haga clic en el nombre de la función cron_impl_2func_651_help para expandir y exponer los controles de Función.
- Haga clic en Despliegue.
- En el Confirmar función de despliegue seleccione "A partir de ahora" de la opción Límite de alimentación.
Empecemos con la otra función de Eventing. Desde el Consola Web de Couchbase > Eventos pantalla:
- Haga clic en el nombre de la función cron_impl_2func_651 para expandir y exponer los controles de Función.
- Haga clic en Despliegue.
- En el Confirmar función de despliegue seleccione "A partir de ahora" de la opción Límite de alimentación.
Configurar un cron tarea a ejecutar cuatro (4) veces por minuto
En este punto nuestra función Eventing está esperando una mutación específicamente cualquier documento de type="recurring_event" que tenga un campo active=true.
Desde el Consola Web Couchbase > Consulta utilizaremos N1QL para crear una nueva tarea programada en el cubo "muestra-viaje":
- Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
123456789101112131415INSERTAR EN `crondata` (CLAVE,VALOR) VALORES ("evento_recurrente::1",{"tipo": "evento_recurrente","id":1,"hora":"*","min":"0","acción": "doCronActionA","verboso": {"user_func": 2,"programador": 3},"activo": falso});
- Haga clic en Ejecute
Activar nuestro primer cron tarea
El documento de control que hicimos anteriormente no se activó porque especificamos "active":false, además la programación anterior sólo se ejecutará una vez por hora, pero queremos probar las cosas y ver que funcionen en un futuro próximo.
En primer lugar necesitamos un índice para poder manipular nuestros documentos de control en N1QL esto sólo tiene que hacerse una vez
- Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
1CREAR principal ÍNDICE en `crondata` ;
Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultasAhora activaremos la tarea, pero ajustaremos el horario de repetición a cada 15 segundos para ver exactamente cómo se comporta el sistema. Para ello modificaremos el documento de control con la CLAVE recurring_event::1
-
123ACTUALIZACIÓN `crondata`SET activo=TRUE, hora="*4X", min="*4X"DONDE tipo="evento_recurrente" Y id=1 ;
- Haga clic en Ejecute
Utilizamos la sintaxis no estándar de ="*4X" para programar un elemento recurrente cuatro veces por minuto podemos ver nuestra función de trabajo doCronActionA ejecutándose y también la lógica de mantenimiento para programar la función a través de sentencias de registro porque hemos establecido verbose=3.
El programador se ejecuta ahora cuatro veces por minuto. Puede ver la actividad en las estadísticas y en los archivos de registro de la aplicación para las funciones de eventos. cron_impl_2func_651 y cron_impl_2func_651_help.
- Acceder a la Consola Web de Couchbase > Panel de control verás una ráfaga de actividad cada 15 segundos:
- Acceder a la Consola Web de Couchbase > Eventos y haga clic en el botón Registro enlace del desplegado cron_impl_2func_651 Función de registro de eventos. Este cuadro de diálogo Registro de funciones enumera las sentencias de registro en orden inverso (los elementos más nuevos primero). La salida inicial debería ser similar a la siguiente:
2020-05-20T18:34:33.340-07:00 [INFO] "OnUpdate B recurring_event::1 temporizador recurrente fue creado, timer_id 570927555481258455388"
2020-05-20T18:34:33.340-07:00 [INFO] "OnUpdate A recurring_event::1 rcv mutation (initial or rearm) schedule timer at 2020-05-20T18:34:45.000"
2020-05-20T18:34:33.233-07:00 [INFO]"doCronActionA upsert to KV with KEY cron_cache::airlines_by_country cachedoc " {"type": "cron_cache", "id": "airlines_by_country", "date": "2020-05-21T01:34:33.232Z", "data":{"Estados Unidos":127, "Reino Unido":39, "Francia":21}}
2020-05-20T18:34:33.233-07:00 [INFO] "Callback R recurring_event::1 action took 0.013 sec., returned true"
2020-05-20T18:34:33.233-07:00 [INFO] "Callback C recurring_event::1 curDoc" {"action": "doCronActionA", "active":true, "hour": "*4X", "id":1, "min": "*4X", "type": "recurring_event", "verbose":{"scheduler":3, "user_func":2}, "dynamic":{"state": "pending", "next_sched":1590024885, "prev_sched":1590024870, "prev_etime":1590024873, "prev_delay":3.218,”prev_atime”:0.013}}
2020-05-20T18:34:33.233-07:00 [INFO] "Callback B recurring_event::1 sched 1590024870, actual 1590024873, delay 3.218, took 0.013"
2020-05-20T18:34:33.233-07:00 [INFO] "Callback A recurring_event::1 gen mutation #1 to doc to force schedule rearm at 2020-05-20T18:34:45.000"
2020-05-20T18:34:33.232-07:00 [INFO] "doCronActionA N1QL idx 2, country France cnt 21"
2020-05-20T18:34:33.232-07:00 [INFO] "doCronActionA N1QL idx 1, country Reino Unido cnt 39"
2020-05-20T18:34:33.232-07:00 [INFO] "doCronActionA N1QL idx 0, country Estados Unidos cnt 127"
2020-05-20T18:34:33.220-07:00 [INFO] "doCronActionA acción de usuario controlada por recurring_event::1"
2020-05-20T18:34:19.340-07:00 [INFO] "OnUpdate B recurring_event::1 temporizador recurrente fue creado, timer_id 381384185845112994486"
2020-05-20T18:34:19.340-07:00 [INFO] "OnUpdate A recurring_event::1 rcv mutation (initial or rearm) schedule timer at 2020-05-20T18:34:30.000"La línea más antigua en la parte inferior es la mutación que inició el programa (o rearmó el programa), por ejemplo, el mensaje OnUpdate, y vemos las dos primeras ejecuciones completas de la lógica de negocio que codificamos en doCronActionA
- También habrá algunos mensajes relacionados con el "arreglo para 6.5.X cubo creciente ops"pero se registrará en cron_impl_2func_651_help verá mensajes como el siguiente cada 30 segundos aproximadamente:
2020-05-20T18:34::49.185-07:00 [INFO] "seeding one doc to each vBucket in primary_bucket alias (took 221 mililis)"
Vamos a ajustar tanto la frecuencia como la verbosidad de esta tarea concreta. Utilizaremos el estándar cron sintaxis de "*" para ambos hora y min para obtener una programación recurrente de una vez por minuto 4X más lenta que la frecuencia actual. Además, bajaremos el nivel de verbosidad de la lógica del planificador a cero y la función de usuario a 1 para que sólo veamos un mensaje por invocación.
Desde el Consola Web Couchbase > Consulta utilizaremos N1QL para crear una nueva tarea programada en el cubo "muestra-viaje":
- Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
1234ACTUALIZACIÓN `crondata`SET verbose.programador = 0, verbose.usuario_func = 1,activo=verdadero, hora="*", min="*"DONDE tipo="evento_recurrente" Y id=1 ;
- Haga clic en Ejecute
Después de un acceso de 2 o 3 minutos el Consola Web de Couchbase > Eventos y haga clic en el botón Registro enlace del desplegado cron_impl_2func_651 Función Eventing.
- La programación recurrente es ahora de un minuto y mucho menos verbosa. Sólo se emite un mensaje o línea de registro por cada ejecución de función (una vez más en orden temporal inverso).
2020-05-20T18:43:04.231-07:00 [INFO] "doCronActionA acción de usuario controlada por recurring_event::1"
2020-05-20T18:42:08.233-07:00 [INFO] "doCronActionA acción de usuario controlada por recurring_event::1"Sólo se emite un mensaje o línea de registro por cada ejecución de función de usuario programada, es decir. doCronActionA (de nuevo en orden cronológico inverso).
Veamos el trabajo que se realiza
Este código proporciona un marco práctico para ejecutar cualquier función JavaScript en un horario recurrente y nuestra función doCronActionA es upserting (insertar o actualizar) un documento KV de caché calculado una vez por minuto.
Para comprobar los resultados de la función de eventos desplegada, acceda a la pantalla Consola Web de Couchbase > Cubos y haga clic en el enlace Documentos de la página viaje-muestra cubo.
- En el cuadro de texto "N1QL DONDE" pasado el siguiente texto.
1tipo="cron_cache"
- Haga clic en Recuperar documentos
Ahora debería ver un documento de caché cron_cache::airlines_by_country que se actualiza una vez por minuto mediante la función programada doCronActionA.
- Haga clic en el identificador "cron_cache::airlines_by_country"verá su documento en caché que está siendo actualizado por la lógica de negocio de doCronActionA.
Por supuesto, los datos de origen son "estáticos" en este momento, por lo que los recuentos seguirán siendo los mismos.
Veamos el documento de control
Este código proporciona un marco para mantener un poco de estadísticas sobre cada programa en ejecución.
Para comprobar las estadísticas de la función de eventos desplegada, acceda a la pantalla Consola Web de Couchbase > Cubos y haga clic en el enlace Documentos de la página crondata cubo.
- En el cuadro de texto "N1QL DONDE" pasado el siguiente texto.
1tipo="recuring_event"
- Haga clic en Recuperar documentos
Ahora debería ver un documento de su documento de control recurring_event::1 que está controlando la función programada doCronActionA. - Haga clic en el identificador "evento_recurrente::1" verá su documento de control que está siendo actualizado por la lógica del planificador con algunas estadísticas en la dinámica de objetos JSON.
La parte "dinámica" del documento que se añade automáticamente conserva algunas estadísticas de depuración: - prev_sched: es la marca de tiempo UNIX anterior de la última planificación ejecutada
- prev_etime: es la marca de tiempo UNIX real de la última vez que se ejecutó el programa
- prev_delay: es el retraso desde el prev_sched hasta el prev_etime
- prev_atime: es el tiempo que tardó en ejecutarse esta acción, es decir, doCronActionA en ejecutarse.
- next_sched: es la siguiente ejecución programada para esta acción
Estas estadísticas guardadas en el subobjeto dinámico JSON para cada programación son útiles para determinar que su sistema de programación está en buen estado y que la acción que se está ejecutando está terminando a tiempo.
Comprobar que la caché se actualiza cuando cambian los datos
Todo el propósito de doCronActionA es realizar el trabajo a una hora programada o cerca de ella y actualizar un documento de caché con la CLAVE "cron_cache::airlines_by_country".
Hagamos algunas validaciones en el Query Monitor de que nuestra caché se está actualizando 1) mirando el documento de la caché, 2) eliminando algunas aerolíneas del conjunto de documentos de la muestra de viaje, y 3) verificando que el documento de la caché se actualiza mediante la función doCronActionA.
Desde el Consola Web Couchbase > Consulta utilizaremos N1QL para ver y manipular los datos del cubo "muestra-viaje":
- Corta y pega la siguiente sentencia N1QL en el archivo
- Editor de consultas
12SELECCIONE datos DESDE `viaje-muestraDONDE `tipo` = 'cron_cache' Y id== aerolíneas_por_país; - Haga clic en Ejecute
En la vista JSON del Query Workbench deberías ver:
123456789[{"datos": {"Francia: 21,"Reino Unido": 39,"Estados Unidos": 127}}] - Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
12BORRAR DESDE `viaje-muestraDONDE `tipo` = aerolínea Y Indicativo COMO U% - Haga clic en Ejecute
En la vista JSON del Query Workbench deberías ver (acabamos de borrar algunos datos)
123{"resultados": []} - Espere un poco más de un minuto
- Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
12SELECCIONE datos DESDE `viaje-muestraDONDE `tipo` = 'cron_cache' Y id== aerolíneas_por_país; - Haga clic en Ejecute
En la vista JSON del Query Workbench debería ver que cuatro (4) compañías aéreas ya no están presentes
123456789[{"datos": {"Francia: 21,"Reino Unido": 39,"Estados Unidos": 123}}]
Iniciar una segunda tarea programada
Este código proporciona un marco práctico para ejecutar de 1 a N funciones JavaScript en horarios recurrentes.
Dejaremos que la función doCronActionA seguirán funcionando en un horario de un minuto, pero ahora habilitaremos doCronActionB en un horario de 30 segundos (dos veces por minuto). Esta función es una shell vacía y sólo registrará que fue invocada.
Desde el Consola Web Couchbase > Consulta utilizaremos N1QL para ver y manipular datos en el directorio 'crondata' cubo:
- Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
123456789101112131415INSERTAR EN `crondata` (CLAVE,VALOR) VALORES ("evento_recurrente::2",{"tipo":"evento_recurrente","id":2,"hora":"*2X","min":"*2X","acción":"doCronActionB","verboso": {"user_func": 1,"programador": 0},"activo": verdadero}); - Haga clic en Ejecute
En este punto está ejecutando dos (2) tareas diferentes cada una en dos (2) horarios diferentes para verificar espere dos o tres minutos e inspeccione los archivos de registro de nuevo
- Acceder a la Consola Web de Couchbase > Eventos y haga clic en el botón Registro enlace del desplegado cron_impl_2func_651 Función Eventing.
Sólo se emite un mensaje o línea de registro por cada ejecución de función (una vez más en orden temporal inverso). Vemos que doCronActionA dispara una vez por minuto mientras doCronActionB dispara el doble, por ejemplo, una vez cada 30 segundos.2020-05-20T19:16:05.259-07:00 [INFO] "doCronActionA acción de usuario controlada por recurring_event::1"
2020-05-20T19:16:05.255-07:00 [INFO] "doCronActionB acción de usuario controlada por recurring_event::2"
2020-05-20T19:15:37.253-07:00 [INFO] "doCronActionB acción de usuario controlada por recurring_event::2"
2020-05-20T19:15:09.250-07:00 [INFO] "doCronActionA acción de usuario controlada por recurring_event::1"
2020-05-20T19:15:09.249-07:00 [INFO] "doCronActionB acción de usuario controlada por recurring_event::2"
2020-05-20T19:14:34.255-07:00 [INFO] "doCronActionB acción de usuario controlada por recurring_event::2"
[OPCIONAL] Pausa / Editar JavaScript / Reanudar
Esencialmente hemos terminado con esta entrega, siéntete libre de experimentar y modificar cosas y experimentar por ejemplo:
Desde el Consola Web de Couchbase > Eventos pantalla:
- Haga clic en el nombre de la función cron_impl_2func_651 para expandir y exponer los controles de Función.
- Haga clic en Pausa.
- En el Confirmar función de pausa seleccione "Función de pausa".
- Pulse "Editar JavaScript"
- Si se siente seguro, modifique el doCronActionB para realizar algunas operaciones KV o integrarse con cURL.
Si sólo quieres ver un cambio añade algo simple a la función prueba algo como lo de abajo:
1234567891011121314151617función doCronActionB(doc) {pruebe {// comprobar que el doc tiene los valores deseadossi (doc.tipo !== "evento_recurrente" || doc.activo !== verdadero) devolver;si (doc.verbose.usuario_func >= 1)registro(doc.acción + Acción del usuario controlada por ' + doc.tipo + '::' + doc.id);// SU LÓGICA AQUÍvar a = 1 + 7;registro('esta es mi lógica, a = 1 +7 = ' + a);} captura (e) {registro(doc.acción + ' Excepción de error:', e);devolver falso;}devolver verdadero;} - Haga clic en Guardar.
- Para volver a la pantalla de Eventos, pulse el botón '< volver a Concurso completo(debajo del editor) o haga clic en el enlace Eventos
- Haga clic en Currículum.
- En el Confirmar función de reanudación seleccione "Reanudar la función".
- Espere aproximadamente un minuto y para la función cron_impl_2func_651 desplegar
- Haga clic en el botón Registro enlace del desplegado cron_impl_2func_651 Función Eventing.
2020-05-20T19:20:41.343-07:00 [INFO] "esta es mi lógica, a = 1 +7 = 8"
2020-05-20T19:20:41.343-07:00 [INFO] "doCronActionB acción de usuario controlada por recurring_event::2"
Limpieza
La limpieza consiste en desinstalar y eliminar la función y, a continuación, eliminar los dos buckets creados. Con esto concluye el Ejemplo.
Eliminar funciones
Desde el Consola Web de Couchbase > Eventos pantalla:
- Haga clic en el nombre de la función cron_impl_2func_651 para expandir y exponer los controles de Función.
- Haga clic en Despliegue.
- En el Confirmar Función de Despliegue seleccione "Función Despliegue".
- Esperar a la función cron_impl_2func_651 para deshacer el despliegue.
- Haga clic en Borrar.
- En el Confirmar función de borrado seleccione "Borrar función".
Desde el Consola Web de Couchbase > Eventos pantalla:
- Haga clic en el nombre de la función cron_impl_2func_651_help para expandir y exponer los controles de Función.
- Haga clic en Despliegue.
- En el Confirmar Función de Despliegue seleccione "Función Despliegue".
- Esperar a la función cron_impl_2func_651_help para deshacer el despliegue.
- Haga clic en Borrar.
- En el Confirmar función de borrado seleccione "Borrar función".
Retirar cubos
Siguiente Suelta los cubos 'metadatos' 'crondatayviaje-muestra(siempre se pueden volver a crear).
Desde el Consola Web de Couchbase > Cubos y haga clic en el enlace Documentos de la página viaje-muestra cubo.
- Haga clic en en el nombre del cubo "metadatos" para expandir y exponer los controles
- Haga clic en Borrar
- En el Confirmar Borrar Cubo seleccione "Borrar cubo".
- Haga clic en en el nombre del cubo "crondata" para expandir y exponer los controles
- Haga clic en Borrar
- En el Confirmar Borrar Cubo seleccione "Borrar cubo".
- Haga clic en en el nombre del cubo "viaje-muestra" para expandir y exponer los controles
- Haga clic en Borrar
- En el Confirmar Borrar Cubo seleccione "Borrar cubo".
Reflexiones finales
Espero que hayas encontrado este tutorial educativo y hayas desarrollado una mayor apreciación del Servicio de Eventos Couchbase en su conjunto.
Anteriormente señalé que Eventing está diseñado para procesar mutaciones de alta velocidad (en millones por segundo) desde el flujo DCP del bucket de origen asociado a la función. Esta función de Eventing o planificador sólo necesita reaccionar a cambios mínimos en los documentos del planificador.
He iniciado 5.000 programaciones con este código sin cambios, simplemente añadiendo documentos de control. Incluso he ejecutado 120.000 programaciones cada minuto sólo para probar esta implementación (sí, 120.000 es una cantidad bastante ridícula de programaciones independientes). cron horarios a ejecutar). Además puse 120.000 sc3hedules 2 días en el futuro para asegurarme de que no teníamos problemas de recursos degenerados.
Al verse obligado a utilizar dos Funciones Vespertinas para crear el sistema de programación, la función principal cron_impl_2func_651 y un simple ayudante cron_impl_2func_651_help no era tan elegante como esperaba. Además, tener que trabajar alrededor de una fuga op cubo a través de disparar un temporizador en cada vBucket fue decepcionante por decir lo menos. Afortunadamente, debido a este esfuerzo se implementaron cambios y en la próxima versión 6.6.0 podré hacer un planificador más limpio utilizando una única Evening Function. Las mejoras clave en 6.6 son 1) la capacidad de generar un nuevo temporizador desde dentro de un callback de temporizador y 2) la capacidad de cancelar o sobrescribir un temporizador existente por referencia, y 3) la eliminación del uso creciente de recursos en sistemas inactivos con temporizadores programados en el futuro.
Utilicé un cubo independiente 'crondata' para mantener el horario o los documentos de control (no cuento el uso de 'metadatosya que es un bloc de notas del sistema necesario para Eventing y reservado para Eventing en sí) para ofrecer la máxima flexibilidad a la hora de manipular datos en otros buckets. Si hubiera colocado los documentos de control en otro bucket, no podría realizar operaciones N1QL en ese bucket (ya que es un bucket de origen para la función Eventing) y me habría limitado únicamente a operaciones KV para manipular datos en el bucket colocado.
Te reto a que pruebes otros casos de uso de cron y también a que pienses en otras formas de aprovechar un servicio de programación:
- Comprobación de un recuento de elementos en un gran conjunto de datos durante las "horas valle" y realización de una purga incremental.
- Realización del enriquecimiento programado de documentos a través de N1QL.
- Recalcular periódicamente las carteras de valores.
- Gestión de TTL o tiempos de expiración a través de N1QL en un horario recurrente, consulte Cómo gestionar documentos Time-To-Live (TTL) con Couchbase N1QL.
- Integración con puntos finales REST externos de forma repetida, consulte Uso de cURL con el servicio Eventing: Actualización.
- Actualizar el JavaScript de cron_impl_2func_651 para añadir un nuevo campo "prev_astatus" al objeto dinámico JSON para guardar el indicador de resultado verdadero/falso devuelto por la acción de usuario ejecutada anteriormente.
Actualizaciones
Este blog se actualizó el 24 de julio de 2020 para añadir una solución para las versiones 6.5.x que tienen un número creciente de operaciones de cubo de metadatos que eventualmente pueden bloquear las mutaciones para una función de Eventing dada al crear temporizadores en el futuro (como en una hora+) en un sistema por lo demás inactivo.
Próximos pasos
En unas semanas "Implementación de un cron portátil robusto como programador a través de Couchbase Eventing (Parte 2)"donde exploraremos la ejecución de una secuencia de sentencias N1QL dinámicas controladas por la base de datos sin necesidad de editar la función de eventos o definir un script de "acción" codificado dentro de la función de eventos.
Recursos
- Descargar: Descargar Couchbase Server 6.5.1
- Función Eventing: cron_impl_2func_651.json
- Función Eventing Helper: cron_impl_2func_651_help.json
Referencias
- Documentación de Couchbase Eventing:
https://docs.couchbase.com/server/current/eventing/eventing-overview.html - Couchbase Server 6.5 Novedades:
https://docs.couchbase.com/server/6.5/introduction/whats-new.html - Blogs de Couchbase sobre Eventing:
https://www.couchbase.com/blog/tag/eventing/
Nos encantaría saber qué te han parecido las características de la versión 6.5 y cómo beneficiarán a tu negocio en el futuro. Por favor, comparte tu opinión a través de los comentarios o en el Couchbase foro.
Notas a pie de página
[1] La implementación del temporizador en el servicio Eventing está diseñada para manejar grandes cantidades de temporizadores distribuidos en millones a alta velocidad. Un solo nodo Eventing puede manejar más de 100K temporizadores por segundo y la única promesa es ejecutar los temporizadores tan pronto como sea posible, por ejemplo, no hay temporizadores perdidos. Tenga en cuenta que el intervalo de escaneo actual es de siete (7) segundos para recoger los temporizadores que están listos para disparar, por lo que debe esperar algunos retrasos. Para más detalles sobre la programación de temporizadores consulte Temporizadores: Precisión de reloj de pared en la documentación de Couchbase.
[2] Ajustando allow_interbucket_recursion a verdadero estás eliminando las protecciones que se colocaron en el servidor Couchbase para proteger contra la lógica Eventing accidental que puede iniciar bucles recursivos infinitos. No hay nada malo con esto pero es fácil cometer un error cuando se aprovecha la recursividad. En las versiones 6.6 de Couchbase ajustando la lógica Eventing puede ser colapsada de dos (2) Funciones Eventing en una (1) Función Eventing simplificada sin necesidad de ajustar la función allow_interbucket_recursion ajuste.
[3] Existen dos limitaciones importantes. En primer lugar, si un documento se modifica varias veces en poco tiempo, las llamadas pueden unirse en un único evento debido a la deduplicación. En segundo lugar, no es posible discernir entre las operaciones de creación y las de actualización. Para las propuestas de un cron función ninguna de las dos limitaciones presenta un problema.
[4] Por qué no implementé la semántica exacta de crontab, podría haberlo hecho pero la cantidad de código es excesiva - te remito a explorar el https://github.com/kelektiv/node-cron junto con sus dependencias moment y moment-timezone (todos ellos paquetes muy grandes). El getNextRecurringDate(hour_str, min_str) puede no ser tan flexible pero es simple y cubre nuestro caso de uso.
¡Muy interesado en las actualizaciones 6.6.x para utilizar una sola función! Me encanta esta implementación para un programador tipo cron.
¡Acabo de darme cuenta de que esto está disponible en GitHub! ¡Voy a echar un vistazo a esto! Recomiendo actualizar esta entrada del blog con el enlace :) ¡Gracias!
Hola Alex,
Pronto actualizaré el "cron-like scheduler" con una versión 6.6.0 y una versión 6.6.1 (utilizando accesores de cubo avanzados).
Sin embargo, lo más probable es que en un blog de seguimiento (Parte 2) - pero voy de enlace cruzado ellos, contento de haber encontrado mi 6.6.0 prototipo en GitHub.