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