Esta é a primeira de uma série de várias partes para aproveitar o Serviço de eventos do Couchbase para executar várias tarefas agendadas em intervalos recorrentes específicos em um cron completamente dentro do banco de dados, sem necessidade de infraestrutura adicional, por meio de uma única Eventing Function de uso geral.
Nesta parte, vamos nos concentrar na execução de rotinas de usuário fixas, funções JavaScript definidas dentro de uma Eventing Function.
Mais adiante, em artigos subsequentes, estenderemos o cron como Eventing Function para agendar e executar instruções N1QL dinâmicas orientadas por banco de dados e, por fim, exploraremos o agendamento de funções JavaScript dinâmicas orientadas por banco de dados.
Histórico
O Couchbase Eventing Service fornece uma estrutura para escrever suas próprias rotinas, funções JavaScript simples, para processar alterações em documentos. Esse serviço fornece toda a infraestrutura necessária para criar funções escalonáveis e robustas baseadas na nuvem, permitindo que você se concentre no desenvolvimento de lógica comercial pura para interagir quase em tempo real com as alterações nos dados. Suas funções podem acessar o serviço de dados do Couchbase (KV), o serviço de consulta do Couchbase (N1QL) e os pontos de extremidade REST externos ao sistema Couchbase.

O JSON modelo de dados no Couchbase veio de JavaScriptPor isso, é natural que o Eventing Service exponha a capacidade de escrever código JavaScript para analisar e manipular documentos JSON em qualquer tipo de evento de alteração, incluindo inserções, atualizações, mesclagens e exclusões (denominadas mutações).
As Eventing Functions normalmente permitem que você implemente e execute fragmentos de código personalizados que reagem a milhares e até milhões de mutações por segundo em seus documentos. Vários casos de uso típicos são documentado para desenvolver Eventing Functions de alta velocidade e em escala que respondem a mutações de documentos do Couchbase.

Em vez disso, este artigo se concentrará em um caso de uso de velocidade muito baixa do Eventing Service, criando um crontab distribuído confiável "no banco de dados", permitindo que você execute funções JavaScript que interagem com os serviços do Couchbase em uma programação periódica regular.
Agendamento da lógica de negócios para execução em uma data ou hora específica
Cron, com o nome de "CronosA palavra grega "tempo" é um dos utilitários mais úteis em um sistema Linux. No Linux, o cron é acionado por um arquivo crontab (tabela cron), um arquivo de configuração que especifica os comandos do shell a serem executados periodicamente em uma determinada programação.
Uma desvantagem da execução cron é que ele não foi projetado para ser um serviço distribuído; ele é executado em uma única caixa e, portanto, apresenta um único ponto de falha. Se o sistema ficar off-line por várias horas, todas as tarefas agendadas serão perdidas.
Sim, há alguns distribuídos cron implementações como o Cloud Service do Google, as tarefas agendadas do AWS e o Azure Functions / Time Triggers. Mas as ofertas de cada fornecedor de nuvem têm seus próprios idiomas e não são diretamente portáteis.
Além disso, a metodologia de configuração e controle precisa ser protegida, por exemplo, se você controlar um sistema distribuído cron por meio de uma API REST sobre HTTP/S, você precisa levar isso em conta no seu plano de segurança.
Usar o próprio Couchbase para executar comandos periódicos
Com uma pequena quantidade de código e planejamento, você pode aproveitar o serviço Eventing do Couchbase para fornecer cron para suas operações ou manutenções programadas no banco de dados. A criação do agendador no banco de dados permite que você obtenha os seguintes benefícios:
- Portabilidade entre provedores de nuvem: se você hospedar novamente o cluster do Couchbase, seu agendador não será afetado.
- Capacidade de suporte: se você utilizar o Couchbase, terá um único fornecedor para fornecer suporte e outros serviços.
- Distribuído, sem ponto único de falha e todos os serviços do Couchbase suportam réplicas distribuídas.
- Execução garantida: sua tarefa é executada mesmo após a recuperação de uma falha de nó.
Programação do Couchbase, temporizadores, o molho secreto
Os temporizadores são construções do Couchbase Eventing Service por meio das quais os desenvolvedores podem especificar uma rotina (lógica de negócios) a ser acionada em um momento futuro. Usaremos essa funcionalidade para implementar um serviço puramente configurável do Couchbase crontab que lhe permite acionar tarefas repetitivas como parte de seus fluxos de trabalho, seja para executar uma simples consulta N1QL ou para criar um mecanismo de regras complexo.
Em todos os projetos subsequentes, limitaremos nossa cron implementações em uma resolução de 15 segundos ou mais. Temos essa limitação porque, embora os temporizadores sejam dimensionados para milhões e tenham garantia de disparo e execução, eles não têm precisão de relógio de parede e atualmente têm um atraso de estado estável limitado a menos de 14 segundos [1].
É claro que, se você precisar de um cronograma mais apertado, ou seja, menos de 15 segundos, deverá simplesmente processar a própria mutação na lógica Eventing sem usar uma construção de cronômetro para agendar uma chamada de volta no futuro.
No momento em que este texto foi escrito, a versão atual do Couchbase é a 6.5.1, que duas limitações que precisamos contornar ao criar um cron sistema.
- Nas versões 5.5.x, 6.0.x e 6.5.x, uma função que é invocada por um retorno de chamada do temporizador não pode criar um novo temporizador de forma confiável (uma solução alternativa no espaço do usuário pode ser feita por meio de uma segunda função cooperativa).
- Nas versões 6.5.x, a criação de temporizadores no futuro (como em uma hora ou mais) em um sistema ocioso pode resultar em um número crescente de operações de balde de metadados que podem, eventualmente, bloquear mutações para uma determinada função de evento (na versão 6.5.X, uma solução alternativa no espaço do usuário pode ser realizada por meio de uma segunda função cooperativa). A gravidade é regida por:
- O número de vBuckets que mantêm um timer ativo. Portanto, se houver apenas alguns cronômetros no futuro, o problema poderá não ser percebido ou se materializar. Esse é o caso de apenas algumas programações do cron, mas, para completar, caso você adicione a funcionalidade de data, coloquei uma correção para esse problema no código fornecido neste artigo.
- Se um timer de Eventing foi disparado recentemente em um vBucket (o que elimina o problema para o vBucket em questão com base em cada função). Portanto, os sistemas com muita atividade de temporizador de curto prazo não terão esse problema, mesmo que os temporizadores estejam programados para um futuro distante.
Felizmente, na versão 6.6.0, os dois problemas ou restrições acima foram eliminados e um agendador pode ser criado em uma única Eventing Function unificada e simples.

Pré-requisitos
Neste artigo, usaremos a versão mais recente do GA, ou seja, a versão 6.5.1 do Couchbase (talvez seja necessário fazer algumas alterações nas Eventing Functions descritas para versões anteriores do Couchbase). O exemplo deste artigo será executado com base no conjunto de dados de amostra de viagem fornecido com o servidor Couchbase.
DICA PRO: Somente para usuários avançados, Se você estiver familiarizado com o Couchbase Eventing e também com nossas ferramentas CLI / REST, poderá pular a maior parte deste blog e baixar um arquivo ZIP para configurar e executar rapidamente o sistema de agendamento apresentado abaixo. Clique com o botão direito do mouse no link a seguir e escolha Salvar link como para fazer o download do arquivo cron_impl_2func_CLI.zip, mova-o para um nó Eventing, extraia o arquivo ZIP e consulte o arquivo README.txt extraído.
No entanto, se você não estiver familiarizado com o Couchbase ou com o serviço Eventing, consulte o GET STARTED e um exemplo de Eventing, especificamente o seguinte:
- Configure um servidor Couchbase 6.5.1 funcional de acordo com as instruções em Comece aqui!
- Certifique-se de que você pode executar uma consulta N1QL no amostra de viagem de acordo com as instruções em Execute sua primeira consulta N1QL.
- Entenda como implantar uma função básica de Eventing de acordo com as instruções do Arquivamento de documentos que também usa o amostra de viagem conjunto de dados.
- Certifique-se de que você tenha o amostra de viagem na visualização de Compartimentos da interface do usuário.
- Certifique-se de que você tenha um balde chamado metadados na visualização Buckets da interface do usuário, ele deve ter o tamanho mínimo de 200 MB.
- Na visualização Buckets da interface do usuário, crie um bucket chamado dados cadastrais com o tamanho mínimo de 200 MB. Para obter etapas detalhadas sobre como criar buckets, consulte Criar um balde.
- Conjunto allow_interbucket_recursion para verdadeiro a fim de permitir que duas (2) funções de Eventing alterem o mesmo documento KV [2].
1curl -X POST -u "$CB_USERNAME:$CB_PASSWORD" 'http://localhost:8091/_p/event/api/v1/config' -d '{ "allow_interbucket_recursion":true }'
Implementação #1, programação do tipo "cron" com código rígido
Para nossa primeira implementação, por exemplo, a Parte 1 da série, projetaremos uma estrutura de controle simples que é apenas um documento KV JSON e também duas (2) Eventing Functions que responderão e agirão de acordo com as informações da estrutura de controle.
Abaixo está um projeto de um documento JSON, ou estrutura de controle, que nos permitirá ter vários "eventos" agendados. Cada evento agendado terá seu próprio documento de controle com uma CHAVE exclusiva, como recurring_event::1, recurring_event::1, ... recurring_event::N. A própria estrutura JSON contém informações para "reconstituir a chave", pois nosso sistema de agendamento responderá a alterações ou atualizações (mutações) nos documentos de controle, como alternar o estado "ativo" para ativar ou desativar a ação ou alterar o campo "verbose", que controla a quantidade e o estilo do registro.
A seguir, um exemplo de documento de controle com KEY evento_recorrente::1 que executará a função JavaScript doCronActionA às 14:54 (2:30 pm) todos os dias.
| Registro de controle JSON | Descrição |
|---|---|
| { | |
| "type": "recurring_event", | A CHAVE será <>::<> |
| "id":1, | |
| "hour":14, | A hora do dia 0-23, *, *2X, *4X para acionar |
| "min":54, | O minuto na hora 0-59, *, *2X, *4X para acionar |
| "action": "doCronActionA", | Função JavaScript a ser executada quando o cronômetro for acionado |
| "active":true, | Sinalizador para ativar ou desativar essa programação |
| "verbose": { | Controle de registro [OPCIONAL |
| "user_func":2, | Nível de registro para a lógica de ação: 0=nenhum, etc. etc. |
| "scheduler":3 | Nível de registro para a lógica cron: 0=nenhum, etc. etc. |
| }, | |
| "dynamic": { | [DYNAMIC] controle e estatísticas do sistema |
| "estado": "braço", | "arm"|"rearm"|"pending" qualquer valor != "pending" inicia uma programação |
| "next_sched": 0, | Número de segundos desde a época até a próxima programação desejada |
| "prev_sched": 0, | Número de segundos desde a época para a programação anterior |
| "prev_etime": 0, | Número de segundos desde a época para a hora de execução real da programação anterior |
| "prev_delay": 0, | Número de segundos em que o cronômetro foi atrasado em relação à programação |
| "prev_atime": 0 | Número de segundos usados pelo usuário 'action' |
| } | |
| } |
Como o Linux tradicional crontab você pode definir hour e min como números inteiros legais, e também pode definir hora para "*" para processar todas as horas ou definir min para "*" para processar todos os minutos.
Embora não apoiemos o crontab sintaxe, oferecemos suporte a duas configurações não padrão, como segue, se você definir ambos hora e min se você definir ambos como "*4X", executaremos e rearmaremos quatro (4) vezes por minuto e, se você definir ambos como "*2X", executaremos e rearmaremos duas (2) vezes por minuto. Abaixo está uma tabela de programações compatíveis com a descrição:
| hora | min | Os valores podem ser números ou cadeias de caracteres |
|---|---|---|
| 13 | 32 | Corra às 13:32 (ou 13:32) |
| * | 15 | Executar a cada hora aos 15 minutos |
| 8 | 12 | Corra uma vez por dia às 8:32 (ou 8:32 da manhã) |
| * | * | Executar uma vez por minuto |
| *2X | *2X | Funciona duas vezes por minuto - requer que a hora e o minuto estejam definidos como "*2X" |
| *4X | *4X | Funciona quatro vezes por minuto - requer que a hora e o minuto estejam definidos como "*2X" |
Eventualmente, usaremos o Query Workbench para inserir o cron documentos de controle, todos os quais devem ter uma CHAVE exclusiva de evento_recorrente::# para um horário de execução programado de 14:54 (14:54), para a ação doCronActionA, poderíamos usar a seguinte instrução N1QL.
Não se preocupe em executar nenhum comando N1QL neste momento, executaremos as instruções N1QL mais tarde, depois que tivermos criado e implementado nossa Eventing Function.
É possível criar um registro de controle (ou registros) no bucket amostra de viageme, em seguida, liste-o, arme-o, desarme-o, ajuste a programação que ele segue, altere o nível de verbosidade do registro em log ou exclua-o da seguinte forma:
| Ação | Declaração N1QL |
|---|---|
| Criar uma programação | INSERT INTO amostra de viagem (KEY,VALUE) VALORES ("recurring_event::1", { "type": "recurring_event", "id":1, "hour": "14″, "min": "54″, "action": "doCronActionA", "active":true } ); |
| Criar um índice para consultar dados sem especificar chaves | CREATE primary INDEX on dados cadastrais ; |
| Mostrar todas as programações ordenadas por id | SELECT * FROM dados cadastrais WHERE type="recurring_event" order by id ; |
| Mostrar programação específica | SELECT * FROM dados cadastrais WHERE type="recurring_event" AND id=1 ; |
| Armar ou definir ativo | ATUALIZAÇÃO dados cadastrais SET active = true WHERE type="recurring_event" AND id=1 ; |
| Desarmar ou definir como inativo | ATUALIZAÇÃO dados cadastrais SET active = false WHERE type="recurring_event" AND id=1 ; |
| Ajuste do tempo de disparo | ATUALIZAÇÃO dados cadastrais SET hour = 11, min = 30 WHERE type="recurring_event" AND id=1 ; |
| Ajustar o registro da "ação" | ATUALIZAÇÃO dados cadastrais SET verbose.user_data = 0 WHERE type="recurring_event" AND id=1 ; |
| Ajustar o registro da lógica do agendador | ATUALIZAÇÃO dados cadastrais SET verbose.scheduler = 0 WHERE type="recurring_event" AND id=1 ; |
| Excluir a programação | DELETE FROM dados cadastrais WHERE type="recurring_event" AND id=1 ; |
Suponha que tenhamos quatro (4) programações ativas. A execução do primeiro comando N1QL acima listará todas elas, por exemplo
|
1 2 |
SELECT active, action, hour, min, type, id, verbose.user_func, verbose.scheduler FROM `crondata` where type="recurring_event" order by id ; |
Retornaria algo como o seguinte resultado (exibição de tabela no Query Workbench):
| ativo | ação | hora | id | min | programador | tipo | user_func |
|---|---|---|---|---|---|---|---|
| verdadeiro | "doCronActionA" | 14 | 1 | 54 | 1 | "recurring_event" (evento recorrente) | 2 |
| verdadeiro | "doCronActionB" | * | 2 | * | 1 | "recurring_event" (evento recorrente) | 1 |
| verdadeiro | "doCronActionC" | *2X | 3 | *2X | 4 | "recurring_event" (evento recorrente) | 4 |
| verdadeiro | "doCronActionD" | * | 4 | 0 | 0 | "recurring_event" (evento recorrente) | 1 |
Na tabela acima, temos quatro ações: a primeira é executada uma vez por dia, a segunda é executada a cada minuto, a terceira a cada 30 segundos e a quarta é executada uma vez por hora. Em uma próxima edição desta série, adicionaremos o recurso "dia da semana".
O objeto aninhado do registro de controle JSON "detalhado", se não for fornecido, terá como padrão { "user_func":1, "scheduler":1 } indicando um nível de registro baixo ou conciso para a função de ação e também para a lógica de agendamento. Um valor de 0 suprimirá todas as mensagens de registro, ou seja, doCronActionD, enquanto valores maiores serão mais detalhados, ou seja, conforme definido em doCronActionC.
O objeto aninhado do registro de controle JSON "dinâmico" se normalmente nunca for fornecido e terá como padrão {"state": "arm", "next_sched": 0, "prev_sched": 0, "prev_etime": 0, "prev_delay": 0, "prev_atime": 0 } esse é um bloco de rascunho para a programação lógica de Eventing em execução e também fornece estatísticas úteis sobre os tempos de execução, portanto, deve ser tratado como somente leitura.
Neste ponto, temos um projeto de controle de alto nível, mas precisamos de lógica para processar nossas estruturas de controle. É aqui que o Eventing Service do Couchbase, especificamente uma Eventing Function, entra em ação.
As funções de eventos
Esse projeto requer duas (2) funções de eventos: uma função JavaScript principal "cron_impl_2func_651" e uma pequena função JavaScript auxiliar "cron_impl_2func_651_help". Discutiremos cada seção das funções JavaScript que compõem o código JavaScript combinado da implementação inicial, com quase 610 linhas (sendo que cerca de 44% das linhas são comentários e espaços em branco)
Não se preocupe em recortar e colar agoraSe você preferir, poderá usar o link para baixar (para importação) as duas Eventing Functions necessárias e todas as configurações necessárias em dois arquivos denominados "cron_impl_2func_651.json" "cron_impl_2func_651_help.json" e também, se preferir, as duas funções unificadas completas que podem ser recortadas e coladas diretamente.
Nossa função de evento principal "cron_impl_2func_651" será composta de nove (9) funções JavaScript
- Três (3) funções de lógica comercial (duas das quais estão vazias).
- doCronActionA(doc) - um exemplo de ação de usuário N1QL a ser executado
- doCronActionB(doc) - um shell de ação do usuário vazio para experimentos
- doCronActionC(doc) - um shell de ação do usuário vazio para experimentos
- Um (1) ponto de entrada para Eventing.
- OnUpdate(doc, meta) - o ponto de entrada padrão do Eventing para inserções ou atualizações
- Um (1) cron analisador de sintaxe para gerar a próxima programação.
- getNextRecurringDate(hour_str, min_str) - lógica do cron para encontrar a próxima data agendada
- Três (3) funções de suporte para verificar se a lógica comercial existe ou formatar resultados.
- verifyFunctionExistsViaEval(curDoc, id) - certifique-se de que temos uma função para executar
- toNumericFixed(number, precision) - formata um float em um estilo compacto
- toLocalISOTime(d) - formata uma data em um estilo compacto
- Uma (1) função de retorno de chamada quando os cronômetros são executados.
- Callback(doc) - uma função de retorno de chamada para cronômetros programados
Nossa Helper Eventing Function "cron_impl_2func_651_help" será composta por uma (1) função JavaScript
- Um (1) ponto de entrada para Eventing.
- OnUpdate(doc, meta) - o ponto de entrada padrão do Eventing para inserções ou atualizações
Nas seções seguintes, examinaremos cada uma das funções JavaScript acima.
Precisamos de uma função JavaScript, por exemplo, a lógica comercial a ser executada em uma programação periódica.
A primeira coisa que queremos é uma rotina ou função que tenha nossa lógica de negócios que será executada com base em nossas regras do crontab. Chamaremos o método JavaScript doCronActionA(doc)no entanto, ele pode ser chamado de qualquer coisa, por exemplo doPeriodicLedgerBalance(doc), Os únicos requisitos para nossas funções de "ação" que implementam nossa lógica comercial programada são os seguintes:
- Tem um parâmetro: doc, um documento de controle, conforme descrito acima, do tipo="recurring_event".
- O nome real do JavaScript corresponde ao campo "action" no documento de controle.
- Devoluções verdadeiro sobre sucesso e falso em caso de falha
- Utiliza doc.verbose.user_func para controlar o registro; se 0, ele é silencioso; se 1, ele emite uma única linha; se 2, ele emite qualquer informação de registro necessária para depurar a função etc. etc.
Escreveremos nossa função doCronActionA(doc)para executar uma consulta N1QL incorporada) para combinar contagens de companhias aéreas por país e, em seguida, criar um único documento KV de dados calculados.
|
1 |
SELECT country, count( * ) AS cnt FROM `travel-sample` WHERE `type` = 'airline' GROUP BY country; |
Em meu sistema de teste, um pequeno nó único nãoMDS (executando todos os serviços do Couchbase), o N1QL acima leva cerca de 20 ms. (para fins de clareza, fingir que é supercomplexo leva 10 segundos para ser concluído).
A ideia aqui é que o documento KV final calculado e resumido possa ser carregado rapidamente por 100 mil (ou um milhão) de mutações Eventing por segundo sem a sobrecarga adicional de comunicação com os nós do serviço Query e do processamento de instruções N1QL em cada mutação.
Deve ser óbvio que o objetivo dessa lógica comercial específica, doCronActionA(doc)O objetivo do cache semi-estático é criar um cache semi-estático que seja atualizado periodicamente em um cronograma.
Tudo o que estamos realmente fazendo (e é bastante rápido) é obter uma contagem de companhias aéreas por país a partir do conjunto de documentos de amostra de viagem. À medida que usamos o N1QL, criamos um documento e, por fim, o escrevemos no KV como um documento resumido. O ponto principal a ser destacado aqui é que não queremos repetir o mesmo trabalho para milhões de mutações cada, especialmente porque alguns cálculos podem levar 10 segundos de tempo de computação do serviço de consulta cada vez que iniciamos uma consulta N1QL incorporada a partir de uma função Eventing.
Abaixo, mostramos a função JavaScript que queremos executar uma vez por dia (ou talvez uma vez por hora, etc.). Observe que o nome da função corresponde ao nome no campo de ação da estrutura de controle. Para obter mais detalhes sobre a terminologia de Eventing e as construções de linguagem, consulte os documentos e exemplos do Couchbase em Serviço de eventos: 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 |
função doCronActionA(doc) { tentar { // Verificar se o documento tem os valores desejados se (!doc.tipo || doc.tipo !== "recurring_event" (evento recorrente) || !doc.ativo || doc.ativo !== verdadeiro) retorno; se (doc.detalhado.user_func >= 1) registro(doc.ação + ' ação do usuário controlada por ' + doc.tipo + '::' + doc.id); // esta é uma consulta N1QL da versão 6.5 (recurso não disponível no GA anterior à versão 6.5) // Crie um iterador N1QL incorporado emitindo uma instrução SELECT para obter o // contagens de companhias aéreas por país. Crie um novo documento e escreva-o para KV // Usaremos o iterador para criar um documento KV que represente os resultados de um // Consulta N1QL longa e incorporada do HARD e escrevê-la de volta no KV, a ideia é manter // um cálculo atualizado uma vez por dia, de modo que possa ser lido "rapidamente // por outras Eventing Functions, outros serviços ou SDKs do Couchbase. // Considere se tivéssemos 1 milhão de documentos em um minuto, será que realmente queremos usar o N1QL? // para recalcular algo que é quase estático para todos os 1 milhão de documentos, de // Claro que não, então criamos um valor intermediário que pode ser lido em Eventing // e usado por meio de uma única leitura KV "leve". var q_iter = SELECIONAR país, contagem( * ) cnt DE `amostra de viagem` ONDE `tipo` = "companhia aérea GRUPO BY país; // percorrer o conjunto de resultados e atualizar o mapa 'accumulate' var acumular = {}; var idx = 0; para (var val de q_iter) { se (doc.detalhado.user_func >= 2) registro(doc.ação + ' N1QL idx ' + idx + ', país ' + val.país + " cnt " + val.cnt); acumular[val.país] = val.cnt; idx++; } // fechar o iterador N1QL incorporado q_iter.próximo(); // Agora vamos criar um documento KV em cache representando um N1QL incorporado de comprimento HARD // consulta e escrevê-la de volta no KV, precisamos de uma KEY, um tipo e um id e, em seguida // inseri-lo no balde `travel-sample`. var cache = {}; cache.tipo = "cron_cache"; cache.id = "airlines_by_country" (companhias aéreas por país); cache.data = novo Data(); cache.dados = acumular; var ckey = cache.tipo + '::' + cache.id; ts_bkt[ckey] = cache; se (doc.detalhado.user_func >= 2) { registro(doc.ação + ' upsert para KV com KEY ' + ckey + ' cachedoc ', cache); } } captura (e) { registro(doc.ação + ' Exceção de erro:', e); retorno falso; } retorno verdadeiro; } |
A função acima simplesmente 1) consulta o bucket de amostra de viagens para extrair dados; nesse caso, a contagem de companhias aéreas para cada país; 2) cria um novo documento KV e uma nova chave e os grava no bucket de amostra de viagens para uso posterior.
Além disso, como parte deste exemplo, criamos um registro que responde a uma configuração de verbosidade numérica que a) registra uma única linha se o documento de controle tiver um valor para doc.verbose.user_func == 1 ou b) emite mais informações se o valor de doc.verbose.user_func >= 2.
Essa é uma estrutura genérica que pode executar um (1) cron ação ou até mesmo mil (1000) de cron ações. Por isso, forneci dois shells de função "vazios" adicionais - como já foi dito, eles poderiam ter qualquer nome.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
função doCronActionB(doc) { tentar { // verificar se o documento tem os valores desejados se (doc.tipo !== "recurring_event" (evento recorrente) || doc.ativo !== verdadeiro) retorno; se (doc.detalhado.user_func >= 1) registro(doc.ação + ' ação do usuário controlada por ' + doc.tipo + '::' + doc.id); // SUA LÓGICA AQUI } captura (e) { registro(doc.ação + ' Exceção de erro:', e); retorno falso; } retorno verdadeiro; } |
e
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
função doCronActionC(doc) { tentar { // verificar se o documento tem os valores desejados se (doc.tipo !== "recurring_event" (evento recorrente) || doc.ativo !== verdadeiro) retorno; se (doc.detalhado.user_func >= 1) registro(doc.ação + ' ação do usuário controlada por ' + doc.tipo + '::' + doc.id); // SUA LÓGICA AQUI } captura (e) { registro(doc.ação + ' Exceção de erro:', e); retorno falso; } retorno verdadeiro; } |
Essas funções acima, doCronActionB e doCronActionC, são triviais, pois simplesmente registram informações no log do aplicativo Eventing da função Eventing. Consulte Funções de registro em log para obter mais detalhes. É claro que você precisa de um documento de controle do tipo = "recurring_event" com active = true e uma ação como action = "doCronActionB" para realmente ativá-los e executá-los.
Precisamos de um ponto de entrada ou manipulador de eventos
A partir da versão 6.5, dois pontos de entrada ou manipuladores são compatíveis com o Eventing Service OnUpdate(doc, meta) e OnDelete(meta) estamos interessados apenas no OnUpdate(doc,meta) para este exemplo.
O OnUpdate(doc,meta) é chamado quando qualquer documento no bucket de origem é criado ou modificado (mutado) e filtra imediatamente os documentos que não interessam. [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 |
função Sobre a atualização(doc, meta) { // correção para operações de balde crescente na versão 6.5.X se (doc.tipo === "_tmp_vbs") genNoopTimers(doc, meta, 30); se (!cron_bkt["fix_timer_scan_issue::1"]) { cron_bkt["fix_timer_scan_issue::1"] = {}; } tentar { // Verificar se é necessária uma análise mais aprofundada; só acionamos em um recurring_event ativo se (doc.tipo !== "recurring_event" (evento recorrente) || doc.ativo !== verdadeiro) retorno; var update_doc = falso; se (!doc.dinâmico) { // Adicionar se estiver faltando doc.dynamic com padrões doc.dinâmico = { "estado": "braço", "next_sched": 0, "prev_sched": 0, "prev_etime": 0, "prev_delay": 0, "prev_atime": 0 }; // precisamos atualizar o documento quando tivermos a próxima programação update_doc = verdadeiro; } se (!doc.detalhado) { // Adicionar se estiver faltando doc.dynamic com padrões doc.detalhado = { "user_func": 1, "agendador": 1 }; // precisamos atualizar o documento quando tivermos a próxima programação update_doc = verdadeiro; } // Não processe dynamic.state pending se (!doc.dinâmico || !doc.dinâmico.estado || doc.dinâmico.estado === "pendente") retorno; var médio = doc.tipo + "::" + doc.id; // isso é o mesmo que meta.id ou a KEY var hora = doc.hora; var min = doc.min; // Faça uma verificação de avaliação da existência da função JavaScript. A avaliação ocorre em uma função comum // função utilitária compartilhada com RecurringCallback se (!verifyFunctionExistsViaEval(doc, médio)) { // doc.action não existia, já registramos o problema retorno; } // Obter o próximo tempo de execução válido var data_timer = getNextRecurringDate(hora, min); var próximo_agendamento = Matemática.rodada(data_timer.getTime() / 1000); se (!update_doc && próximo_agendamento !== doc.dinâmico.próximo_agendamento) { // o next_sched deve ser o mesmo que a configuração do aplicativo auxiliar, no entanto // se desfizermos a implantação/implantação ou pausarmos/retomarmos, talvez tenhamos que reprogramar para o próximo intervalo de tempo registro('OnUpdate U ' + médio + ' calculated next_sched !== doc.dynamic.next_sched, delta ' + (próximo_agendamento - doc.dinâmico.próximo_agendamento) + ', reagendar'); update_doc = verdadeiro; } se (update_doc) { // essa mutação é recursiva e será suprimida, garantindo que tenhamos uma estrutura dinâmica doc.dinâmico.próximo_agendamento = próximo_agendamento; // em vez de chamar uma função, para capturar e tentar novamente se houver um problema de recurso // cron_bkt[mid] = doc; se (!tryBucketKvWriteWithLog('OnUpdate F', médio, doc)) { // Falha ao gravar o documento no cron_bkt[key] o erro foi registrado // e não há mais nada que possamos fazer. retorno; } } // Agendar um cronômetro de eventos var timer_id = createTimer(Retorno de chamada, data_timer, nulo, doc); se (doc.detalhado.programador >= 1) { registro('OnUpdate A ' + médio + ' mutação de rcv (inicial ou rearmada) cronômetro de programação em ' + toLocalISOTime(data_timer)); } se (doc.detalhado.programador >= 2) { registro('OnUpdate B ' + médio + ' o cronômetro recorrente foi criado, timer_id ' + timer_id); } } captura (e) { registro('OnUpdate E ' + meta.id + ', Exceção de erro:', e); } } |
A chave aqui é que o cron A lógica do nosso manipulador só se preocupa com os documentos que têm doc.type de "recurring_event" e também um doc.active de true. Além disso, neste exemplo, criamos um rastreamento para o cron lógica de manutenção que só é registrada no log do aplicativo se o documento de controle tiver um valor para doc.verbose >= 3.
Se você executar apenas algumas programações, poderá desativar o trabalho no espaço do usuário ou "Correção para operações de balde de crescimento 6.5.X" comentando quatro linhas de código no bloco OnUpdate acima para "cron_impl_2func_651" da seguinte forma:
|
1 2 3 4 5 6 |
função Sobre a atualização(doc, meta) { // correção para operações de balde crescente na versão 6.5.X // se (doc.type === "_tmp_vbs") genNoopTimers(doc, meta, 30); // se (!cron_bkt["fix_timer_scan_issue::1"]) { // cron_bkt["fix_timer_scan_issue::1"] = {}; // } |
Precisamos de código para contornar possíveis operações de aumento de balde para a versão 6.5.X
A partir da versão 6.5.X, precisamos de um "Correção para operações de balde de crescimento 6.5.X", o que acontece em sistemas ociosos com muitos cronômetros programados para o futuro. Esse código garante que um timer de Eventing tenha sido disparado recentemente em um vBucket (o que elimina o problema do vBucket em questão com base em cada função).
|
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 |
// CORREÇÃO: FUNÇÃO ADDIN função noopTimer(contexto) { // correção para operações de balde crescente na versão 6.5.X tentar { se (contexto.tipo === "_tmp_vbs" && contexto.vb === 0) { // log("timers do noopTimer disparando, imprimindo somente para o vBucket 0"); } } captura (e) { registro("Exceção de OnUpdate no retorno de chamada noopTimer:", e); } } // CORREÇÃO: FUNÇÃO ADDIN função rearmTimer(contexto) { // correção para operações de balde crescente na versão 6.5.X tentar { se (contexto.tipo === "_tmp_vbs" && contexto.vb === 0) { // Atualizar/tocar todos os documentos no helper_bucket A função auxiliar irá então // mutar todos os 1024 do tipo == vbs_seed (64 no MacOS) para criar um ciclo recorrente. // log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context); // gerar uma mutação para rearmar a função HELPER: fix_scan_issue // que, por sua vez, criará novas mutações para essa Função var cur = cron_bkt[contexto.chave]; se (cur && cur.ts_millis === contexto.ts_millis) { // log("rearmTimer update fix_timer_scan_issue::1 in helper_bucket alias only for vBucket 0"); var agora = novo Data(); cron_bkt["fix_timer_scan_issue::1"] = { "last_update": agora }; } mais { // NOOP tivemos vários ciclos de cronômetro, apenas deixe esse parar silenciosamente. } } } captura (e) { registro("Exceção de OnUpdate no retorno de chamada rearmTimer:", e); } } // CORREÇÃO: FUNÇÃO ADDIN função genNoopTimers(doc, meta, segundos) { // correção para operações de balde crescente na versão 6.5.X tentar { // redundante, mas é seguro se (doc.tipo === "_tmp_vbs") { // Como estamos usando uma função diferente, um cronômetro em todos os nossos vBuckets funciona imediatamente (pode levar até 15 segundos) // Se usássemos a recursão cross bucket para rearmar todos os timers de forma recorrente, adicionaríamos um atraso de pelo menos 40 segundos. createTimer(noopTimer, novo Data(), nulo, doc); se (doc.vb === 0) { // Atualizar/tocar todos os documentos no helper_bucket A função auxiliar irá então // mutar todos os 1024 do tipo == vbs_seed (64 no MacOS) para criar um ciclo recorrente. // log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context); // gerar uma mutação para rearmar a função HELPER: fix_scan_issue // que, por sua vez, criará novas mutações para essa Função // log("genNoopTimers make timer to rearm fix_timer_scan_issue::1"); createTimer(rearmTimer, novo Data(novo Data().getTime() + segundos * 1000), nulo, doc); } } } captura (e) { registro("Exceção OnUpdate em genNoopTimers:", e); } } |
Precisamos de um utilitário para calcular o próximo horário na programação
A próxima função getNextRecurringDate(hour, min) determinará um horário para executar a ação, conforme definido como parte de nossa programação. Essa não é uma implementação completa de cronEm vez disso, ele contém os principais recursos padrão para execução uma vez por dia, uma vez por hora, uma vez por minuto. Ele também contém uma sintaxe não padrão para permitir a execução duas vezes por minuto ou quatro vezes por minuto.
Conforme descrito anteriormente, a função getNextRecurringDate(hour, min) permite o seguinte (a tabela está duplicada abaixo), sendo que os dois últimos não são padrão.[4]
| hora | min | Os valores podem ser números ou cadeias de caracteres |
|---|---|---|
| 13 | 32 | Corra às 13:32 (ou 13:32) |
| * | 15 | Executar a cada hora aos 15 minutos |
| 8 | 12 | Corra uma vez por dia às 8:32 (ou 8:32 da manhã) |
| * | * | Executar uma vez por minuto |
| *2X | *2X | Funciona duas vezes por minuto - requer que a hora e o minuto estejam definidos como "*2X" |
| *4X | *4X | Funciona quatro vezes por minuto - requer que a hora e o minuto estejam definidos como "*2X" |
Abaixo está uma implementação da lógica necessária para determinar o próximo momento para acionar um temporizador Eventing em nossa agenda, caso a lógica do usuário em nosso primeiro exemplo doCronActionA(doc) não for concluído em tempo hábil, por exemplo, excesso de tempo real, a próxima quantidade da programação será selecionada. Observe os temporizadores e suas funções principais. Portanto, se uma Eventing Function tiver um tempo limite de execução padrão de 60 segundos, se necessário, essa configuração poderá ser ajustada ou 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 |
função getNextRecurringDate(hour_str, min_str) { // Observe que as datas de Javascript estão em milissegundos var data_agora = novo Data(); var data_ret = novo Data(); var hora; var min; tentar { hora = parseInt(hour_str); } captura (e) {} tentar { min = parseInt(min_str); } captura (e) {} // Observe que essa é apenas uma sintaxe parcial simplista do 'crontab' com algumas pequenas extensões // permite uma vez por dia, uma vez por hora, uma vez por minuto. Ele também contém alguns // sintaxe para fornecer a capacidade de executar duas vezes por minuto ou quatro vezes por minuto. se (hour_str === '*4X' && min_str === '*4X') { // uma vez a cada 15 segundos ou quatro vezes por minuto data_ret.setMilliseconds(0); data_ret.setSeconds(15); enquanto (data_ret.getTime() < data_agora.getTime()) { data_ret.setSeconds(data_ret.getSeconds() + 15); } retorno data_ret; } mais se (hour_str === '*2X' && min_str === '*2X') { // uma vez a cada 30 segundos ou duas vezes por minuto data_ret.setMilliseconds(0); data_ret.setSeconds(30); enquanto (data_ret.getTime() < data_agora.getTime()) { data_ret.setSeconds(data_ret.getSeconds() + 30); } retorno data_ret; } mais se (hour_str === '*' && min_str === '*') { // uma vez por minuto data_ret.setMilliseconds(0); data_ret.setSeconds(0); data_ret.setMinutes(data_ret.getMinutes() + 1); } mais se (hour_str !== '*' && isNaN(hora) === falso && min_str === '*') { // uma vez por minuto apenas em uma determinada hora data_ret.setMilliseconds(0); data_ret.setSeconds(0); data_ret.setMinutes(data_ret.getMinutes() + 1); se (data_ret.getTime() < data_agora.getTime()) { data_ret.setHours(hora); } se (data_ret.getTime() > data_agora.getTime()) { data_ret.setDate(data_ret.getDate() + 1); data_ret.setSeconds(0); data_ret.setMinutes(0); data_ret.setHours(hora); } } mais se (hour_str === '*' && min_str !== '*' && isNaN(min) === falso) { // uma vez por hora em um determinado minuto data_ret.setMilliseconds(0); data_ret.setSeconds(0); data_ret.setMinutes(min); // programação para a próxima hora data_ret.setHours(data_ret.getHours() + 1); } mais se (isNaN(hora) === falso && isNaN(min) === falso) { // uma vez por dia em uma determinada hora e um determinado minuto data_ret.setMilliseconds(0); data_ret.setSeconds(0); data_ret.setMinutes(min); data_ret.setHours(hora); se (data_ret.getTime() < data_agora.getTime()) { // programação para amanhã data_ret.setDate(data_ret.getDate() + 1); } } mais { registro('getNextRecurringDate entrada ilegal hour_str <' + hour_str + '> min_str <' + min_str + '>'); lançar novo Erro('getNextRecurringDate entrada ilegal hour_str <' + hour_str + '> min_str <' + min_str + '>'); retorno nulo; } retorno data_ret; } |
Precisamos de alguns pequenos serviços públicos
A função utilitária comum que simplesmente verifica se nosso JavaScript existe é usada por ambos OnUpdate(doc,meta), mostrado acima, e o cronômetro Callback(doc), mostrado mais tarde. Abaixo está verifyFunctionExistsViaEval(curDoc, id) que recebe dois argumentos: um documento de controle JSON e a KEY para esse documento.
Isso nos permite saber imediatamente, na implantação, se houve um problema com uma incompatibilidade de nomes entre o documento de registro de controle JSON e o nome real da função de lógica comercial no código JavaScript.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
função verifyFunctionExistsViaEval(curDoc, id) { var resultado = falso; tentar { // verificar a função se estiver faltando isso é inválido retornar resultado resultado = avaliação("typeof " + curDoc.ação + " === 'function';"); se (resultado === falso) { se (curDoc.detalhado.programador >= 1) registro("Warn/Disable (No Action and No Re-Arm), porque a 'ação' exigida de " + curDoc.ação + "(doc) não existe, id é", id); retorno resultado; } } captura (e) { registro('verifyFunctionExistsViaEval Exceção de erro:', e); } retorno resultado; } |
Observe que, se houver uma tentativa de executar uma função inexistente, o usuário final receberá um aviso no log do aplicativo cron_impl_2func_651.log para corrigir o problema.
2020-04-22T16:20:38.725-07:00 [INFO] "Avisar/Desabilitar (Sem Ação e Sem Rearmamento), porque a 'ação' necessária de doCronMyNewFunction(doc) não existe, o id é" "recurring_event::1"
Essa correção pode ser feita por meio de uma pausa/retomada que adiciona a função e, em seguida, ajusta o documento de controle com o ID ou KEY especificado (por meio de uma alternância ativa para falso e depois para verdadeiro) ou ajusta o documento de controle para apontar para uma função existente em seu manipulador.
Em seguida, o utilitário toNumericFixed(number, precision) permite apenas uma formatação compacta e agradável de floats para nossas mensagens de registro.
|
1 2 3 4 |
função toNumericFixed(número, precisão) { var multi = Matemática.pow(10, precisão); retorno Matemática.rodada((número * multi).toFixed(precisão + 1)) / multi; } |
Por fim, o utilitário toLocalISOTime(d) permite apenas uma boa formatação compacta de datas para nossas mensagens de registro.
|
1 2 3 4 |
função toLocalISOTime(d) { var tzoffset = (novo Data()).getTimezoneOffset() * 60000; //offset em milissegundos retorno (novo Data(d.getTime() - tzoffset)).toISOString().fatia(0, -1); } |
Precisamos de uma chamada de retorno do temporizador para executar a lógica do usuário e rearmar o temporizador
A função JavaScript final em "cron_impl_2func_651" é o retorno de chamada do Timer, que é chamado quando o timer programado é acionado. A função de retorno de chamada deve ser uma função de nível superior que recebe um único argumento, o contexto.
Nesse caso, em nosso manipulador OnUpdate, fizemos referência a uma função JavaScript de Callback(doc) com um contexto de doc (nosso documento de controle do agendador ativo de type="recurring_event")
Na versão 6.6, podemos criar outro cronômetro dentro de um cronômetro, mas em todas as versões anteriores precisaremos acionar uma mutação para uma função "auxiliar" (evitamos cuidadosamente a recursão infinita). Na versão 6.6, a função auxiliar não é necessária e a lógica foi substancialmente simplificada.
|
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 |
função Retorno de chamada(doc) { tentar { var fired_at = novo Data(); // Verificar se é necessária uma análise mais aprofundada. Só acionamos em um recurring_event que esteja ativo se (doc.tipo !== "recurring_event" (evento recorrente)) retorno; // O documento deve ter 'action', 'dynamic {}', verbose {}, dynamic.state se (!doc.ação || !doc.dinâmico || !doc.detalhado || !doc.dinâmico.estado) retorno; // processar qualquer doc.dynamic.state BUT pendente se (doc.dinâmico.estado === "pendente") retorno; // ================== // Verificar se ainda está ativo // Certificamo-nos de que no KV o 'doc' ainda existe e que ele ainda está ativo, caso contrário, apenas // retorna, pulando a ação e não rearmando o cronômetro. Observe que `travel-sample` é // aliased para o mapa 'cron_bkt var médio = doc.tipo + '::' + doc.id; // criar nossa KEY var curDoc = nulo; tentar { // ler a versão atual do documento do KV, por exemplo, curDoc curDoc = cron_bkt[médio]; } captura (e) {} // necessário para versões anteriores à 6.5, observe que a implementação pura da versão 6.5+ retorna nulo sem exceção var razão = nulo; se (!curDoc || curDoc === nulo) { razão = "O documento cron está faltando"; } mais se (!curDoc.ativo) { razão = "O documento cron tem active = false"; } mais se (!curDoc.dinâmico.estado || curDoc.dinâmico.estado !== doc.dinâmico.estado) { razão = "cron document wrong dynamic.state expected" + doc.dinâmico.estado; } mais se (crc64(doc) !== crc64(curDoc)) { razão = "documento cron alterado"; } se (razão !== nulo) { se (!curDoc || curDoc === nulo || curDoc.detalhado.programador >= 1) { registro('Callback X ' + médio + " ignorar/parar a programação desse cronômetro porque " + razão); } se (!curDoc || curDoc === nulo || curDoc.detalhado.programador >= 4) { registro('Callback Y ' + médio + ' timer doc', doc); registro('Callback Z ' + médio + ' KV curDoc', curDoc); } retorno; } // ================== // Verifique se a rotina do usuário existe e, se existir, avalie-a // Suponha que curDoc.action contenha algo como "doCronActionA" e que tenhamos uma função em // esse manipulador como "doCronActionA(doc)". Abaixo, usamos curDoc como o usuário final deve ser // capaz de alterar a função JavaScript avaliada. Executaremos duas (2) avaliações. // A primeira avaliação verifica se a função JavaScript existe. A avaliação ocorre em uma função comum // função utilitária compartilhada com Callback se (!verifyFunctionExistsViaEval(curDoc, médio)) { // curDoc.action não existia, já registramos o problema retorno; } // Segundo eval executa e processa a função do usuário, executamos a função definida // com um argumento de curDoc var beg_act = novo Data(); var resultado = nulo; avaliação("result = " + curDoc.ação + "(curDoc);"); var end_act = novo Data(); var atime_ms = end_act.getTime() - beg_act.getTime(); se (curDoc.detalhado.programador >= 2) registro('Callback R ' + médio + ' ação tomada ' + toNumericFixed((atime_ms / 1000), 3) + ' seção, retornou ' + resultado); // ================== // Calcular a próxima vez e alterar o documento de controle para nossa função auxiliar // que criará outra mutação de modo que OnUpdate dessa função escolherá // e gerar o cronômetro (evita o problema do MB-38554). var hora = curDoc.hora; var min = curDoc.min; var data_timer = getNextRecurringDate(hora, min); curDoc.dinâmico.atraso_prev = toNumericFixed(((fired_at.getTime() / 1000) - curDoc.dinâmico.próximo_agendamento), 3); curDoc.dinâmico.anterior_agendado = curDoc.dinâmico.próximo_agendamento; curDoc.dinâmico.tempo_prev = Matemática.rodada(fired_at.getTime() / 1000); curDoc.dinâmico.hora_anterior = toNumericFixed((atime_ms / 1000), 3); curDoc.dinâmico.estado = "pendente"; curDoc.dinâmico.próximo_agendamento = Matemática.rodada(data_timer.getTime() / 1000); tentar { cron_bkt[médio] = curDoc; } captura (e) { registro('Ajuda de retorno de chamada: F ' + médio + ' FATAL could not update KV cron cycle ' + curDoc.ação); retorno; } se (curDoc.detalhado.programador >= 1) { registro('Callback A ' + médio + ' mutação gen #1 para doc para forçar o rearmamento do cronograma em ' + toLocalISOTime(data_timer)); } se (curDoc.detalhado.programador >= 2) { registro('Callback B ' + médio + ' programação ' + curDoc.dinâmico.anterior_agendado + ', real ' + curDoc.dinâmico.tempo_prev + ', delay ' + curDoc.dinâmico.atraso_prev + ', pegou ' + curDoc.dinâmico.hora_anterior); } se (curDoc.detalhado.programador >= 3) { registro('Callback C ' + médio + ' curDoc', curDoc); } } captura (e) { var médio = doc.tipo + '::' + doc.id; // criar nossa KEY registro('Callback E ' + médio + ' Exceção de erro:', e); } } |
Precisamos de uma função auxiliar para acionar uma nova mutação
Como antes da versão 6.6 (que ainda não foi lançada) não é possível criar um temporizador a partir de um retorno de chamada de um temporizador em execução, precisamos de uma segunda Eventing Function (juntamente com "allow_interbucket_recursion":true) para acionar uma mutação de modo que possamos gerar todos os nossos temporizadores no ponto de entrada OnUpdate(doc,meta) da Eventing Function principal. Fazemos isso da seguinte forma:
- cron_impl_2func_651 OnUpdate(doc,meta) recebe uma mutação, programa um cronômetro
- cron_impl_2func_651 Após um determinado período de tempo, quando o cronômetro amadurece, o Callback(doc) A rotina é executada, primeiro executa a ação do usuário desejada e, em seguida, cria uma mutação #1 no documento de controle (que não é vista pela função de criação para evitar recursão)
- cron_impl_2func_651_help OnUpdate(doc,meta) recebe uma mutação, faz outra mutação #2 no documento de controle, o que aciona 1. acima em um ciclo sem fim.
Observe que, na versão 6.6 do Couchbase, não precisamos de uma função auxiliar, pois é permitido criar um timer de dentro de um timer em execução. Isso simplifica muito a lógica necessária para criar um cron sistema[2].
A única função JavaScript em "cron_impl_2func_651_help" OnUpdate(doc,meta) é mostrado abaixo.
|
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 |
função Sobre a atualização(doc, meta) { // correção para operações de balde crescente na versão 6.5.X se (meta.id.startsWith("fix_timer_scan_issue:")) upsertOneDocPerBucket(doc, meta); tentar { // Verificar se o documento tem os valores desejados se (!doc.tipo || doc.tipo !== "recurring_event" (evento recorrente) || !doc.ativo || doc.ativo != verdadeiro) retorno; // O documento deve ter 'action', 'dynamic {}', verbose {}, dynamic.state se (!doc.ação || !doc.dinâmico || !doc.detalhado || !doc.dinâmico.estado) retorno; // Somente o estado do processo pendente só existirá por um "breve" período se (doc.dinâmico.estado !== "pendente") retorno; var médio = doc.tipo + '::' + doc.id; // criar nossa KEY var novo documento = nulo; tentar { // ler a versão atual do documento do KV, por exemplo, curDoc novo documento = cron_bkt[médio]; } captura (e) {} // necessário para versões anteriores à 6.5, observe que a implementação pura da versão 6.5+ retorna nulo sem exceção var razão = nulo; se (!novo documento || novo documento == nulo) { razão = "O documento cron está faltando"; } mais se (!novo documento.ativo) { razão = "O documento cron tem active = false"; } mais se (!novo documento.dinâmico.estado || novo documento.dinâmico.estado !== doc.dinâmico.estado) { razão = "cron document wrong dynamic.state expected" + doc.dinâmico.estado; } mais se (crc64(doc) !== crc64(novo documento)) { razão = "documento cron alterado"; } se (razão != nulo) { se (!novo documento || novo documento == nulo || novo documento.detalhado.programador >= 1) { registro('Ajuda do OnUpdate: X interrompendo a programação porque ' + razão + ',', novo documento) retorno; } } novo documento.dinâmico.estado = "rearmar"; // cron_bkt[mid] = newdoc; se (!tryBucketKvWriteWithLog('Ajuda do OnUpdate: F', médio, novo documento)) { // Falha ao gravar newdoc em cron_bkt[key] o erro foi registrado // e não há mais nada que possamos fazer. retorno; } se (novo documento.detalhado.programador >= 1) { registro(Ajuda do 'OnUpdate': A ' + médio + ' mutação #2 para doc para forçar o rearmamento do cronograma'); } se (novo documento.detalhado.programador >= 3) { registro('Ajuda do OnUpdate: B ' + médio + ',', novo documento); } } captura (e) { registro('Ajuda do OnUpdate: E ' + meta.id + ', Exceção de erro:', e); } } função tryBucketKvWriteWithLog(etiqueta, chave, doc) { var sucesso = falso; var tentativas = 0; enquanto (tentativas < 10) { tentativas++; tentar { // é fundamental que a ação abaixo seja bem-sucedida, pois, se não for, o ciclo cron será interrompido cron_bkt[chave] = doc; sucesso = verdadeiro; quebra; } captura (e) { registro(etiqueta + ' ' + chave + ' WARN falhou ao atualizar as tentativas de KV ' + tentativas, e); } } se (!sucesso) { registro(etiqueta + ' ' + +chave + ' FATAL não conseguiu atualizar o ciclo cron do KV, tentou ' + tentativas + ', interrompendo ' + curDoc.ação); } retorno sucesso; } |
A função auxiliar precisa de alguns utilitários
Esses utilitários fornecem um Correção para operações de balde de crescimento 6.5.X assegurando que um temporizador Eventing seja acionado em cada vBucket em tempo hábil.
|
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 |
// CORREÇÃO: FUNÇÃO ADDIN // correção para operações de balde crescente na versão 6.5.X função upsertOneDocPerBucket(doc, meta) { var crcTable = makeCRC32Table(); // criar um documento por balde var isVerbose = 0; var isMacOS = falso; // seria bom se essa fosse uma constante exposta em Eventing var números = 1024; // o padrão é linux/PC se (isMacOS) { números = 64; } var implorar = (novo Data).getTime(); var resultado = getKeysToCoverAllPartitions(crcTable, "_tmp_vbs:", números); para (var vb=0; vb<números; vb++) { // força bruta para encaixar um prefixo de chave em um vBucket var tst = resultado[vb]; se (isVerbose > 1 || (isVerbose == 1) && (vb < 3 || vb > números -4)) { registro("KEY: " + tst); } mais { se (vb == 5) console.registro("\t*\n\t*\n\t*"); } // atualizar os itens para acionar uma mutação para nossa função PRIMARY cron_bkt[tst] = { "tipo": "_tmp_vbs", "vb": vb, "ts_millis": implorar, "chave": tst }; } var final = (novo Data).getTime(); registro("semeando um documento para cada vBucket no alias primary_bucket (levou " + (final - implorar) + " mililis)"); } // CORREÇÃO: FUNÇÃO ADDIN // correção para operações de balde crescente na versão 6.5.X função showHex(n) { retorno n.toString(16); } // CORREÇÃO: FUNÇÃO ADDIN // correção para operações de balde crescente na versão 6.5.X função 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; } retorno crcTable; } // CORREÇÃO: FUNÇÃO ADDIN // correção para operações de balde crescente na versão 6.5.X função crc32(crcTable,str) { var crc = 0 ^ (-1); para (var i = 0; i < str.comprimento; i++ ) { crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF]; } retorno (crc ^ (-1)) >>> 0; } // CORREÇÃO: FUNÇÃO ADDIN // correção para operações de balde crescente na versão 6.5.X função getKeysToCoverAllPartitions(crcTable,keyPrefix,partitionCount) { var resultado = []; var restantes = partitionCount; para (var i = 0; restantes > 0; i++) { var chave = keyPrefix + i; var rv = (crc32(crcTable,chave) >> 16) & 0x7fff; var actualPartition = rv & partitionCount - 1; se (!resultado[actualPartition] || resultado[actualPartition] === indefinido) { resultado[actualPartition] = chave; restantes--; } } retorno resultado; } |
Agora, vamos implementar as duas Eventing Functions
Revisamos muitos códigos e o design do agendador inicial, agora é hora de ver tudo funcionando em conjunto.
Lembre-se de que, neste exemplo, há três compartimentos amostra de viagem (um conjunto de dados padrão de amostra), metadados(o bucket de metadados é um bloco de notas para Eventing e pode ser compartilhado com outras funções de Eventing) e, por fim, o dados cadastrais (que contém nossas programações cron). O amostra de viagem tem um tamanho de 100 MB e os outros dois baldes metadados e dados cadastrais devem ter um tamanho de 200 MB e já existir de acordo com as instruções em "Pré-requisitos".
- Verifique a configuração atual do seu bucket acessando o Console da Web do Couchbase > Buckets página:

Para implementar a função Eventing "cron_impl_2func_651", você pode seguir um destes dois métodos:
- Complexidade básica, Método #1 Download/Importação
- Complexidade média, método #2 Adicionar função manualmente, recortar e colar JavaScript
Método #1 Download/Importação
Importar a primeira função "cron_impl_2func_651"
Faça o download da primeira Eventing Function com todas as configurações necessárias, clique com o botão direito do mouse no link a seguir e selecione Salvar link como para fazer o download do arquivo cron_impl_2func_651.json em seu sistema de arquivos local.
Do Console da Web do Couchbase > Eventos página, clique em IMPORTAÇÃO, navegue até o arquivo cron_impl_2func_651.json, selecione-o e abra-o. A ADICIONAR FUNÇÃO é exibida.
No ADICIONAR FUNÇÃO para elementos individuais da função, forneça as informações abaixo. Observe o arquivo JSON cron_impl_2func_651.json pré-configurará todas as configurações corretamente para este exemplo:
- Para o Balde de origem verifique se ele está definido como dados cadastrais.
- Para o Balde de metadados verifique se ele está definido como metadados.
- Verifique se cron_impl_2func_651 é o nome da função que você está criando no Nome da função caixa de texto.
- [Etapa opcional] Digite o texto Um agendador do tipo cron - parte 1, no Descrição caixa de texto.
- Para o Configurações use os valores padrão.
- Para o Amarrações verifique se existem dois vínculos.
- Para a vinculação, o "bucket alias" especifica cron_bkt como o "nome de alias" do bucket e selecione
dados cadastrais como o bucket associado, e o modo deve ser "leitura e gravação". - Para a vinculação, o "bucket alias" especifica ts_bkt como o "nome de alias" do bucket e selecione
amostra de viagem como o bucket associado, e o modo deve ser "leitura e gravação". - Suas configurações na caixa de diálogo devem ser parecidas com as seguintes:

- Depois de verificar todas as informações necessárias na caixa de diálogo ADD FUNCTION, clique em Next: Add Code (Adicionar código). O código cron_impl_2func_651 é exibida (com o código JavaScript pré-carregado).

- Para retornar à tela Eventing, clique no botão '< voltar para Eventing' (abaixo do editor) ou clique no link Eventos guia.
Importar a segunda função "cron_impl_2func_651_help"
Faça o download da segunda Eventing Function com todas as configurações necessárias, clique com o botão direito do mouse no link a seguir e selecione Salvar link como para fazer o download do arquivo cron_impl_2func_651_help.json em seu sistema de arquivos local.
Do Console da Web do Couchbase > Eventos página, clique em IMPORTAÇÃO, navegue até o arquivo cron_impl_2func_651_help.json, selecione-o e abra-o. A ADICIONAR FUNÇÃO é exibida.
No ADICIONAR FUNÇÃO para elementos individuais da função, forneça as informações abaixo. Observe o arquivo JSON cron_impl_2func_651_help.json pré-configurará todas as configurações corretamente para este exemplo:
- Para o Balde de origem verifique se ele está definido como dados cadastrais.
- Para o Balde de metadados verifique se ele está definido como metadados.
- Verifique se cron_impl_2func_651_help é o nome da função que você está criando no Nome da função caixa de texto.
- [Etapa opcional] Digite o texto Um auxiliar de agendamento do tipo cron - parte 1, no Descrição caixa de texto.
- Para o Configurações use os valores padrão.
- Para o Amarrações verifique se existe apenas uma ligação.
- Para a vinculação, o "bucket alias" especifica cron_bkt como o "nome de alias" do bucket e selecione
dados cadastrais como o bucket associado, e o modo deve ser "leitura e gravação". - Suas configurações na caixa de diálogo devem ser parecidas com as seguintes:

- Depois de verificar todas as informações necessárias na caixa de diálogo ADD FUNCTION, clique em Next: Add Code (Adicionar código). O código cron_impl_2func_651_help é exibida (com o código JavaScript pré-carregado).

- Para retornar à tela Eventing, clique no botão '< voltar para Eventing' (abaixo do editor) ou clique no link Eventos guia.
Método #2 Adicionar manualmente a função, recortar e colar JavaScript
Criar manualmente "cron_impl_2func_651"
Para adicionar a primeira função Eventing da lista Console da Web do Couchbase > Eventos página, clique em ADICIONAR FUNÇÃOpara adicionar uma nova função. A função ADICIONAR FUNÇÃO é exibida.
No ADICIONAR FUNÇÃO para elementos de função individuais, forneça as informações abaixo:
- Para o Balde de origem definido como dados cadastrais.
- Para o Balde de metadados definido como metadados.
- Fazer cron_impl_2func_651 é o nome da função que você está criando no Nome da função caixa de texto.
- [Etapa opcional] Digite o texto Um agendador do tipo cron - parte 1, no Descrição caixa de texto.
- Para o Configurações use os valores padrão.
- Para o Amarrações crie dois vínculos:
- Para a vinculação, o "bucket alias" especifica cron_bkt como o "nome de alias" do bucket e selecione
dados cadastrais como o bucket associado, e o modo deve ser "leitura e gravação". - Para a vinculação, o "bucket alias" especifica ts_bkt como o "nome de alias" do bucket e selecione
amostra de viagem como o bucket associado, e o modo deve ser "leitura e gravação". - Depois de definir suas configurações, sua caixa de diálogo deverá ter a seguinte aparência:

- Depois de fornecer todas as informações necessárias no formulário ADICIONAR FUNÇÃO clique em Próximo: Adicionar código. O cron_impl_2func_651 é exibida. A caixa de diálogo cron_impl_2func_651 contém inicialmente um bloco de código de espaço reservado. Você substituirá seu cron_impl_2func_651 nesse bloco.

- Copie o código-fonte JavaScript da Eventing Function a seguir (618 linhas) e cole-o no bloco de código de espaço reservado de cron_impl_2func_651
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618/*A função "cron_impl_2func_651" também requer "cron_impl_2func_651_help"Criar um sistema cron básico usando Eventing permite que uma função recorrente execute uma atividade em umhorário especificado todos os dias, hora, minuto, 30 segundos e 15 segundos. Usamos um bucket chamado "crondatacom o alias de 'cron_bkt', que pode conter um ou mais documentos de controle do tipo = "recurring_event".Os seguintes usos de cronômetros não funcionam de forma confiável nas versões 6.5 e 6.5.1 do Couchbasea) agendamento de um temporizador Eventing em uma chamada de retorno de temporizadorb) sobrescrever um cronômetro existente por idAlém disso, a capacidade de cancelar um cronômetro não existe nas versões 6.5 e 6.5.1 do CouchbasePara este exemplo, fornecemos uma função de usuário real que cria um documento de cache "estático" recorrentedo bucket `travel-sample` por meio de uma consulta N1QL e salvar o resultado de volta no `travel-sample` por meio deos alais 'ts_bkt'. Essa função JavaScript é doCronActionA(), e também fornecemos dois espaços reservadosdoCronActionB() e doCronActionC() para experimentos adicionais.Documento de teste:{"type": "recurring_event", // A CHAVE será <>::<>"id":1, //"hour":14, // A hora do dia 0-23, *, *2X, *4X a ser acionada"min":54, // O minuto na hora 0-59, *, *2X, *4X para acionar"action": "doCronActionA", // Qual função executar no acionador"active":false, // Sinalizador para ativar ou desativar essa programação"verbose" : {"user_func":2, // Nível de registro para a lógica de ação: 0=nenhum, etc. etc."scheduler":3 // Nível de registro para a lógica cron: 0=nenhum, etc. etc.},"dynamic" : {"state": "arm", // Os estados "arm"|"rearm"|"pending" se qualquer valor, exceto "pending", iniciar uma programação"next_sched": 0, // Número de segundos desde a época até a próxima programação desejada"prev_sched": 0, // Número de segundos desde a época para a programação anterior"prev_etime": 0, // Número de segundos desde a época para a hora de execução real da programação anterior"prev_delay": 0, // Número de segundos que o cronômetro foi atrasado em relação à programação"prev_atime": 0 // Número de segundos usados pela 'ação' do usuário}}INSERT INTO `crondata` (KEY,VALUE) VALUES ("recurring_event::1",{"type": "recurring_event","id":1,"hour":14,"min":54,"action": "doCronActionA","verbose" : {"user_func":2,"scheduler":3},"active":false,"dynamic" : {"state" (estado): "arm","next_sched": 0,"prev_sched": 0,"prev_etime": 0,"prev_delay": 0,"prev_atime": 0}});Observe que você pode omitir verbose{} e dynamic{}, pois eles serão criados automaticamente por esse Eventing principalFunction "cron_impl_2func_651". Se verbose{} estiver ausente, os níveis de registro serão padronizados paraverbose" : { "user_func":1, "scheduler":1 }INSERT INTO `crondata` (KEY,VALUE) VALUES ("recurring_event::1",{"type": "recurring_event","id":1,"hour":14,"min":54,"action": "doCronActionA","active":false});N1QL: criar um índice para consultar dados sem especificar chavesCREATE primary INDEX on `crondata` ;N1QL: Verificar ou inspecionar as configurações na programaçãoSELECT * FROM `crondata` WHERE type="recurring_event";N1QL: Armar ou definir ativoUPDATE `crondata` SET active = true WHERE type="recurring_event" AND id=1 ;N1QL : Desarmar ou definir como inativoUPDATE `crondata` SET active = false WHERE type="recurring_event" AND id=1 ;N1QL: Ajuste do tempo de acionamentoUPDATE `crondata` SET hour = 11, min = 30 WHERE type="recurring_event" AND id=1 ;N1QL : Ajustar o registro em logUPDATE `crondata` SET verbose.user_func = 1, verbose.scheduler = 0 WHERE type="recurring_event" AND id=1 ;N1QL : Excluir a programaçãoDELETE FROM `crondata` WHERE type="recurring_event" AND id=1 ;O campo de ação é importante, ele "deve" existir nessa nota da Eventing Function e pode ser qualquer umNome do JavaScript, por exemplo, MyFunc e você deve implementar como no exemplo doCronActionA(doc) ondedoc será o item ativo no momento do tipo = 'recurring_event' lido do bucket de aliases'cron_bkt' quando o cronômetro for disparado. A função JavaScript de ação deve retornar trueou falso usado para fins de registro. Se a ação não existir, será um erro e um avisoé registrado e o temporizador é desativado.Na versão 6.5+ do Couchbase, para adicionar uma nova função diária do tipo cron, basta pausar o manipulador ativoinsira sua nova função doCronActionB(doc) {...} e, em seguida, retome o manipulador de eventos. O bomO fato é que, se um cronômetro for disparado e a função for pausada, ele NÃO será perdido, quando vocêretomar a função, ela será processada no próximo intervalo de tempo disponível.Qualquer alteração em uma estrutura de controle criará um novo cronograma ou timer recorrente e cancelará oprogramação anterior atual, o que inclui a alteração do nível de verbosidade. O cronômetro anteriorcontinuar a ser executado, mas, quando executado, ele fará uma verificação da estrutura de controle atualdo KV em relação ao contexto passado e, se for diferente, o retorno de chamada ignorará a programação antiga.Essa lógica pode ser alterada para processar imediatamente se a programação tiver expirado.string "OnUpdate U" no código abaixo.*/// ==================/* IMPLORAR QUE AS FUNÇÕES DO USUÁRIO SEJAM EXECUTADAS UMA VEZ POR DIA, HORA OU MINUTO - O QUE VOCÊ QUISER ABAIXO */função doCronActionA(doc) {tentar {// Verificar se o documento tem os valores desejadosse (!doc.tipo || doc.tipo !== "recurring_event" (evento recorrente) || !doc.ativo || doc.ativo !== verdadeiro) retorno;se (doc.detalhado.user_func >= 1)registro(doc.ação + ' ação do usuário controlada por ' + doc.tipo + '::' + doc.id);// esta é uma consulta N1QL da versão 6.5 (recurso não disponível no GA anterior à versão 6.5)// Crie um iterador N1QL incorporado emitindo uma instrução SELECT para obter o// contagens de companhias aéreas por país. Crie um novo documento e escreva-o para KV// Usaremos o iterador para criar um documento KV que represente os resultados de um// Consulta N1QL longa e incorporada do HARD e escrevê-la de volta no KV, a ideia é manter// um cálculo atualizado uma vez por dia, de modo que possa ser lido "rapidamente// por outras Eventing Functions, outros serviços ou SDKs do Couchbase.// Considere se tivéssemos 1 milhão de documentos em um minuto, será que realmente queremos usar o N1QL?// para recalcular algo que é quase estático para todos os 1 milhão de documentos, de// Claro que não, então criamos um valor intermediário que pode ser lido em Eventing// e usado por meio de uma única leitura KV "leve".var q_iter = SELECIONAR país,contagem( * ) cntDE `amostra de viagem`ONDE `tipo` = "companhia aéreaGRUPO BY país;// percorrer o conjunto de resultados e atualizar o mapa 'accumulate'var acumular = {};var idx = 0;para (var val de q_iter) {se (doc.detalhado.user_func >= 2)registro(doc.ação + ' N1QL idx ' + idx + ', país ' + val.país + " cnt " + val.cnt);acumular[val.país] = val.cnt;idx++;}// fechar o iterador N1QL incorporadoq_iter.próximo();// Agora vamos criar um documento KV em cache representando um N1QL incorporado de comprimento HARD// consulta e escrevê-la de volta no KV, precisamos de uma KEY, um tipo e um id e, em seguida// inseri-lo no balde `travel-sample`.var cache = {};cache.tipo = "cron_cache";cache.id = "airlines_by_country" (companhias aéreas por país);cache.data = novo Data();cache.dados = acumular;var ckey = cache.tipo + '::' + cache.id;ts_bkt[ckey] = cache;se (doc.detalhado.user_func >= 2) {registro(doc.ação + ' upsert para KV com KEY ' + ckey + ' cachedoc ', cache);}} captura (e) {registro(doc.ação + ' Exceção de erro:', e);retorno falso;}retorno verdadeiro;}função doCronActionB(doc) {tentar {// verificar se o documento tem os valores desejadosse (doc.tipo !== "recurring_event" (evento recorrente) || doc.ativo !== verdadeiro) retorno;se (doc.detalhado.user_func >= 1)registro(doc.ação + ' ação do usuário controlada por ' + doc.tipo + '::' + doc.id);// SUA LÓGICA AQUI} captura (e) {registro(doc.ação + ' Exceção de erro:', e);retorno falso;}retorno verdadeiro;}função doCronActionC(doc) {tentar {// verificar se o documento tem os valores desejadosse (doc.tipo !== "recurring_event" (evento recorrente) || doc.ativo !== verdadeiro) retorno;se (doc.detalhado.user_func >= 1)registro(doc.ação + ' ação do usuário controlada por ' + doc.tipo + '::' + doc.id);// SUA LÓGICA AQUI} captura (e) {registro(doc.ação + ' Exceção de erro:', e);retorno falso;}retorno verdadeiro;}/* FUNÇÕES DO USUÁRIO FINAL A SEREM EXECUTADAS UMA VEZ POR DIA, HORA OU MINUTO - O QUE VOCÊ QUISER ACIMA */// ==================// CORREÇÃO: FUNÇÃO ADDINfunção noopTimer(contexto) {// correção para operações de balde crescente na versão 6.5.Xtentar {se (contexto.tipo === "_tmp_vbs" && contexto.vb === 0) {// log("timers do noopTimer disparando, imprimindo somente para o vBucket 0");}} captura (e) {registro("Exceção de OnUpdate no retorno de chamada noopTimer:", e);}}// CORREÇÃO: FUNÇÃO ADDINfunção rearmTimer(contexto) {// correção para operações de balde crescente na versão 6.5.Xtentar {se (contexto.tipo === "_tmp_vbs" && contexto.vb === 0) {// Atualizar/tocar todos os documentos no helper_bucket A função auxiliar irá então// mutar todos os 1024 do tipo == vbs_seed (64 no MacOS) para criar um ciclo recorrente.// log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context);// gerar uma mutação para rearmar a função HELPER: fix_scan_issue// que, por sua vez, criará novas mutações para essa Funçãovar cur = cron_bkt[contexto.chave];se (cur && cur.ts_millis === contexto.ts_millis) {// log("rearmTimer update fix_timer_scan_issue::1 in helper_bucket alias only for vBucket 0");var agora = novo Data();cron_bkt["fix_timer_scan_issue::1"] = { "last_update": agora };} mais {// NOOP tivemos vários ciclos de cronômetro, apenas deixe esse parar silenciosamente.}}} captura (e) {registro("Exceção de OnUpdate no retorno de chamada rearmTimer:", e);}}// CORREÇÃO: FUNÇÃO ADDINfunção genNoopTimers(doc, meta, segundos) {// correção para operações de balde crescente na versão 6.5.Xtentar {// redundante, mas é segurose (doc.tipo === "_tmp_vbs") {// Como estamos usando uma função diferente, um cronômetro em todos os nossos vBuckets funciona imediatamente (pode levar até 15 segundos)// Se usássemos a recursão cross bucket para rearmar todos os timers de forma recorrente, adicionaríamos um atraso de pelo menos 40 segundos.createTimer(noopTimer, novo Data(), nulo, doc);se (doc.vb === 0) {// Atualizar/tocar todos os documentos no helper_bucket A função auxiliar irá então// mutar todos os 1024 do tipo == vbs_seed (64 no MacOS) para criar um ciclo recorrente.// log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context);// gerar uma mutação para rearmar a função HELPER: fix_scan_issue// que, por sua vez, criará novas mutações para essa Função// log("genNoopTimers make timer to rearm fix_timer_scan_issue::1");createTimer(rearmTimer, novo Data(novo Data().getTime() + segundos * 1000), nulo, doc);}}} captura (e) {registro("Exceção OnUpdate em genNoopTimers:", e);}}função Sobre a atualização(doc, meta) {// correção para operações de balde crescente na versão 6.5.Xse (doc.tipo === "_tmp_vbs") genNoopTimers(doc, meta, 30);se (!cron_bkt["fix_timer_scan_issue::1"]) {cron_bkt["fix_timer_scan_issue::1"] = {};}tentar {// Verificar se é necessária uma análise mais aprofundada; só acionamos em um recurring_event ativose (doc.tipo !== "recurring_event" (evento recorrente) || doc.ativo !== verdadeiro) retorno;var update_doc = falso;se (!doc.dinâmico) {// Adicionar se estiver faltando doc.dynamic com padrõesdoc.dinâmico = {"estado": "braço","next_sched": 0,"prev_sched": 0,"prev_etime": 0,"prev_delay": 0,"prev_atime": 0};// precisamos atualizar o documento quando tivermos a próxima programaçãoupdate_doc = verdadeiro;}se (!doc.detalhado) {// Adicionar se estiver faltando doc.dynamic com padrõesdoc.detalhado = {"user_func": 1,"agendador": 1};// precisamos atualizar o documento quando tivermos a próxima programaçãoupdate_doc = verdadeiro;}// Não processe dynamic.state pendingse (!doc.dinâmico || !doc.dinâmico.estado || doc.dinâmico.estado === "pendente") retorno;var médio = doc.tipo + "::" + doc.id; // isso é o mesmo que meta.id ou a KEYvar hora = doc.hora;var min = doc.min;// Faça uma verificação de avaliação da existência da função JavaScript. A avaliação ocorre em uma função comum// função utilitária compartilhada com RecurringCallbackse (!verifyFunctionExistsViaEval(doc, médio)) {// doc.action não existia, já registramos o problemaretorno;}// Obter o próximo tempo de execução válidovar data_timer = getNextRecurringDate(hora, min);var próximo_agendamento = Matemática.rodada(data_timer.getTime() / 1000);se (!update_doc && próximo_agendamento !== doc.dinâmico.próximo_agendamento) {// o next_sched deve ser o mesmo que a configuração do aplicativo auxiliar, no entanto// se desfizermos a implantação/implantação ou pausarmos/retomarmos, talvez tenhamos que reprogramar para o próximo intervalo de temporegistro('OnUpdate U ' + médio + ' calculated next_sched !== doc.dynamic.next_sched, delta ' +(próximo_agendamento - doc.dinâmico.próximo_agendamento) + ', reagendar');update_doc = verdadeiro;}se (update_doc) {// essa mutação é recursiva e será suprimida, garantindo que tenhamos uma estrutura dinâmicadoc.dinâmico.próximo_agendamento = próximo_agendamento;// em vez de chamar uma função, para capturar e tentar novamente se houver um problema de recurso// cron_bkt[mid] = doc;se (!tryBucketKvWriteWithLog('OnUpdate F', médio, doc)) {// Falha ao gravar o documento no cron_bkt[key] o erro foi registrado// e não há mais nada que possamos fazer.retorno;}}// Agendar um cronômetro de eventosvar timer_id = createTimer(Retorno de chamada, data_timer, nulo, doc);se (doc.detalhado.programador >= 1) {registro('OnUpdate A ' + médio + ' mutação de rcv (inicial ou rearmada) cronômetro de programação em ' +toLocalISOTime(data_timer));}se (doc.detalhado.programador >= 2) {registro('OnUpdate B ' + médio + ' o cronômetro recorrente foi criado, timer_id ' + timer_id);}} captura (e) {registro('OnUpdate E ' + meta.id + ', Exceção de erro:', e);}}função getNextRecurringDate(hour_str, min_str) {// Observe que as datas de Javascript estão em milissegundosvar data_agora = novo Data();var data_ret = novo Data();var hora;var min;tentar {hora = parseInt(hour_str);} captura (e) {}tentar {min = parseInt(min_str);} captura (e) {}// Observe que essa é apenas uma sintaxe parcial simplista do 'crontab' com algumas pequenas extensões// permite uma vez por dia, uma vez por hora, uma vez por minuto. Ele também contém alguns// sintaxe para fornecer a capacidade de executar duas vezes por minuto ou quatro vezes por minuto.se (hour_str === '*4X' && min_str === '*4X') {// uma vez a cada 15 segundos ou quatro vezes por minutodata_ret.setMilliseconds(0);data_ret.setSeconds(15);enquanto (data_ret.getTime() < data_agora.getTime()) {data_ret.setSeconds(data_ret.getSeconds() + 15);}retorno data_ret;} maisse (hour_str === '*2X' && min_str === '*2X') {// uma vez a cada 30 segundos ou duas vezes por minutodata_ret.setMilliseconds(0);data_ret.setSeconds(30);enquanto (data_ret.getTime() < data_agora.getTime()) {data_ret.setSeconds(data_ret.getSeconds() + 30);}retorno data_ret;} maisse (hour_str === '*' && min_str === '*') {// uma vez por minutodata_ret.setMilliseconds(0);data_ret.setSeconds(0);data_ret.setMinutes(data_ret.getMinutes() + 1);} maisse (hour_str !== '*' && isNaN(hora) === falso && min_str === '*') {// uma vez por minuto apenas em uma determinada horadata_ret.setMilliseconds(0);data_ret.setSeconds(0);data_ret.setMinutes(data_ret.getMinutes() + 1);se (data_ret.getTime() < data_agora.getTime()) {data_ret.setHours(hora);}se (data_ret.getTime() > data_agora.getTime()) {data_ret.setDate(data_ret.getDate() + 1);data_ret.setSeconds(0);data_ret.setMinutes(0);data_ret.setHours(hora);}} maisse (hour_str === '*' && min_str !== '*' && isNaN(min) === falso) {// uma vez por hora em um determinado minutodata_ret.setMilliseconds(0);data_ret.setSeconds(0);data_ret.setMinutes(min);// programação para a próxima horadata_ret.setHours(data_ret.getHours() + 1);} maisse (isNaN(hora) === falso && isNaN(min) === falso) {// uma vez por dia em uma determinada hora e um determinado minutodata_ret.setMilliseconds(0);data_ret.setSeconds(0);data_ret.setMinutes(min);data_ret.setHours(hora);se (data_ret.getTime() < data_agora.getTime()) {// programação para amanhãdata_ret.setDate(data_ret.getDate() + 1);}} mais {registro('getNextRecurringDate entrada ilegal hour_str <' +hour_str + '> min_str <' + min_str + '>');lançar novo Erro('getNextRecurringDate entrada ilegal hour_str <' +hour_str + '> min_str <' + min_str + '>');retorno nulo;}retorno data_ret;}função verifyFunctionExistsViaEval(curDoc, id) {var resultado = falso;tentar {// verificar a função se estiver faltando isso é inválido retornar resultadoresultado = avaliação("typeof " + curDoc.ação + " === 'function';");se (resultado === falso) {se (curDoc.detalhado.programador >= 1)registro("Warn/Disable (No Action and No Re-Arm), porque a 'ação' exigida de " +curDoc.ação + "(doc) não existe, id é", id);retorno resultado;}} captura (e) {registro('verifyFunctionExistsViaEval Exceção de erro:', e);}retorno resultado;}função toNumericFixed(número, precisão) {var multi = Matemática.pow(10, precisão);retorno Matemática.rodada((número * multi).toFixed(precisão + 1)) / multi;}função toLocalISOTime(d) {var tzoffset = (novo Data()).getTimezoneOffset() * 60000; //offset em milissegundosretorno (novo Data(d.getTime() - tzoffset)).toISOString().fatia(0, -1);}função tryBucketKvWriteWithLog(etiqueta, chave, doc) {var sucesso = falso;var tentativas = 0;enquanto (tentativas < 10) {tentativas++;tentar {// é fundamental que a ação abaixo seja bem-sucedida, pois, se não for, o ciclo cron será interrompidocron_bkt[chave] = doc;sucesso = verdadeiro;quebra;} captura (e) {registro(etiqueta + ' ' + chave + ' WARN falhou ao atualizar as tentativas de KV ' + tentativas, e);}}se (!sucesso) {registro(etiqueta + ' ' + +chave + ' FATAL não conseguiu atualizar o ciclo cron do KV, tentou ' + tentativas + ', interrompendo ' + curDoc.ação);}retorno sucesso;}função Retorno de chamada(doc) {tentar {var fired_at = novo Data();// Verificar se é necessária uma análise mais aprofundada. Só acionamos em um recurring_event que esteja ativose (doc.tipo !== "recurring_event" (evento recorrente)) retorno;// O documento deve ter 'action', 'dynamic {}', verbose {}, dynamic.statese (!doc.ação || !doc.dinâmico || !doc.detalhado || !doc.dinâmico.estado) retorno;// processar qualquer doc.dynamic.state BUT pendentese (doc.dinâmico.estado === "pendente") retorno;// ==================// Verificar se ainda está ativo// Certificamo-nos de que no KV o 'doc' ainda existe e que ele ainda está ativo, caso contrário, apenas// retorna, pulando a ação e não rearmando o cronômetro. Observe que `travel-sample` é// aliased para o mapa 'cron_bktvar médio = doc.tipo + '::' + doc.id; // criar nossa KEYvar curDoc = nulo;tentar {// ler a versão atual do documento do KV, por exemplo, curDoccurDoc = cron_bkt[médio];} captura (e) {} // necessário para versões anteriores à 6.5, observe que a implementação pura da versão 6.5+ retorna nulo sem exceçãovar razão = nulo;se (!curDoc || curDoc === nulo) {razão = "O documento cron está faltando";} maisse (!curDoc.ativo) {razão = "O documento cron tem active = false";} maisse (!curDoc.dinâmico.estado || curDoc.dinâmico.estado !== doc.dinâmico.estado) {razão = "cron document wrong dynamic.state expected" + doc.dinâmico.estado;} maisse (crc64(doc) !== crc64(curDoc)) {razão = "documento cron alterado";}se (razão !== nulo) {se (!curDoc || curDoc === nulo || curDoc.detalhado.programador >= 1) {registro('Callback X ' + médio + " ignorar/parar a programação desse cronômetro porque " + razão);}se (!curDoc || curDoc === nulo || curDoc.detalhado.programador >= 4) {registro('Callback Y ' + médio + ' timer doc', doc);registro('Callback Z ' + médio + ' KV curDoc', curDoc);}retorno;}// ==================// Verifique se a rotina do usuário existe e, se existir, avalie-a// Suponha que curDoc.action contenha algo como "doCronActionA" e que tenhamos uma função em// esse manipulador como "doCronActionA(doc)". Abaixo, usamos curDoc como o usuário final deve ser// capaz de alterar a função JavaScript avaliada. Executaremos duas (2) avaliações.// A primeira avaliação verifica se a função JavaScript existe. A avaliação ocorre em uma função comum// função utilitária compartilhada com RecurringCallbackse (!verifyFunctionExistsViaEval(curDoc, médio)) {// curDoc.action não existia, já registramos o problemaretorno;}// Segundo eval executa e processa a função do usuário, executamos a função definida// com um argumento de curDocvar beg_act = novo Data();var resultado = nulo;avaliação("result = " + curDoc.ação + "(curDoc);");var end_act = novo Data();var atime_ms = end_act.getTime() - beg_act.getTime();se (curDoc.detalhado.programador >= 2)registro('Callback R ' + médio + ' ação tomada ' + toNumericFixed((atime_ms / 1000), 3) +' seção, retornou ' + resultado);// ==================// Calcular a próxima vez e alterar o documento de controle para nossa função auxiliar// que criará outra mutação de modo que OnUpdate dessa função escolherá// e gerar o cronômetro (evita o problema do MB-38554).var hora = curDoc.hora;var min = curDoc.min;var data_timer = getNextRecurringDate(hora, min);curDoc.dinâmico.atraso_prev =toNumericFixed(((fired_at.getTime() / 1000) - curDoc.dinâmico.próximo_agendamento), 3);curDoc.dinâmico.anterior_agendado = curDoc.dinâmico.próximo_agendamento;curDoc.dinâmico.tempo_prev = Matemática.rodada(fired_at.getTime() / 1000);curDoc.dinâmico.hora_anterior = toNumericFixed((atime_ms / 1000), 3);curDoc.dinâmico.estado = "pendente";curDoc.dinâmico.próximo_agendamento = Matemática.rodada(data_timer.getTime() / 1000);// em vez de chamar uma função, para capturar e tentar novamente se houver um problema de recurso// cron_bkt[mid] = curDoc;se (!tryBucketKvWriteWithLog('Callback F', médio, curDoc)) {// Falha ao gravar curDoc em cron_bkt[key] o erro foi registrado// e não há mais nada que possamos fazer.retorno;}se (curDoc.detalhado.programador >= 1) {registro('Callback A ' + médio + ' mutação gen #1 para doc para forçar o rearmamento do cronograma em ' +toLocalISOTime(data_timer));}se (curDoc.detalhado.programador >= 2) {registro('Callback B ' + médio + ' programação ' + curDoc.dinâmico.anterior_agendado +', real ' + curDoc.dinâmico.tempo_prev +', delay ' + curDoc.dinâmico.atraso_prev +', pegou ' + curDoc.dinâmico.hora_anterior);}se (curDoc.detalhado.programador >= 3) {registro('Callback C ' + médio + ' curDoc', curDoc);}} captura (e) {var médio = doc.tipo + '::' + doc.id; // criar nossa KEYregistro('Callback E ' + médio + ' Exceção de erro:', e);}}
- Após a colagem, a tela é exibida como mostrado abaixo:

- Clique em Salvar.
- Para retornar à tela Eventing, clique no botão '< voltar para Eventing' (abaixo do editor) ou clique no link Eventos
Criar manualmente "cron_impl_2func_651_help"
Para adicionar a segunda função Eventing do Console da Web do Couchbase > Eventos página, clique em ADICIONAR FUNÇÃOpara adicionar uma nova função. A função ADICIONAR FUNÇÃO é exibida.
No ADICIONAR FUNÇÃO para elementos de função individuais, forneça as informações abaixo:
- Para o Balde de origem definido como dados cadastrais.
- Para o Balde de metadados definido como metadados.
- Fazer cron_impl_2func_651_help é o nome da função que você está criando no Nome da função caixa de texto.
- [Etapa opcional] Digite o texto Um auxiliar de agendamento do tipo cron - parte 1, no Descrição caixa de texto.
- Para o Configurações use os valores padrão.
- Para o Amarrações crie uma ligação:
- Para a vinculação, o "bucket alias" especifica cron_bkt como o "nome de alias" do bucket e selecione
dados cadastrais como o bucket associado, e o modo deve ser "leitura e gravação". - Depois de definir suas configurações, sua caixa de diálogo deverá ter a seguinte aparência:

- Depois de fornecer todas as informações necessárias no formulário ADICIONAR FUNÇÃO clique em Próximo: Adicionar código. O cron_impl_2func_651_help é exibida. A caixa de diálogo cron_impl_2func_651_help contém inicialmente um bloco de código de espaço reservado. Você substituirá seu cron_impl_2func_651_help nesse bloco.

- Copie o seguinte código-fonte JavaScript da Eventing Function (187 linhas) e cole-o no bloco de código de espaço reservado de cron_impl_2func_651_help
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187/*A função "cron_impl_2func_651_help" também requer "cron_impl_2func_651"Documento de teste:{"type": "recurring_event", // A CHAVE será <>::<>"id":1, //"hour":14, // A hora do dia 0-23, *, *2X, *4X a ser acionada"min":54, // O minuto na hora 0-59, *, *2X, *4X para acionar"action": "doCronActionA", // Qual função executar no acionador"active":false, // Sinalizador para ativar ou desativar essa programação"verbose" : {"user_func":2, // Nível de registro para a lógica de ação: 0=nenhum, etc. etc."scheduler":3 // Nível de registro para a lógica cron: 0=nenhum, etc. etc.},"dynamic" : {"state": "arm", // Os estados "arm"|"rearm"|"pending" se qualquer valor, exceto "pending", iniciar uma programação"next_sched": 0, // Número de segundos desde a época até a próxima programação desejada"prev_sched": 0, // Número de segundos desde a época para a programação anterior"prev_etime": 0, // Número de segundos desde a época para a hora de execução real da programação anterior"prev_delay": 0, // Número de segundos que o cronômetro foi atrasado em relação à programação"prev_atime": 0 // Número de segundos usados pela 'ação' do usuário}}Observe que você pode omitir verbose{} e dynamic{}, pois eles serão criados automaticamente pelo Eventing principalFunction "cron_impl_2func_651". Se verbose{} estiver ausente, os níveis de registro serão padronizados paraverbose" : { "user_func":1, "scheduler":1 }*/função tryBucketKvWriteWithLog(etiqueta, chave, doc) {var sucesso = falso;var tentativas = 0;enquanto (tentativas < 10) {tentativas++;tentar {// é fundamental que a ação abaixo seja bem-sucedida, pois, se não for, o ciclo cron será interrompidocron_bkt[chave] = doc;sucesso = verdadeiro;quebra;} captura (e) {registro(etiqueta + ' ' + chave + ' WARN falhou ao atualizar as tentativas de KV ' + tentativas, e);}}se (!sucesso) {registro(etiqueta + ' ' + +chave + ' FATAL não conseguiu atualizar o ciclo cron do KV, tentou ' + tentativas + ', interrompendo ' + curDoc.ação);}retorno sucesso;}função Sobre a atualização(doc, meta) {// correção para operações de balde crescente na versão 6.5.Xse (meta.id.startsWith("fix_timer_scan_issue:")) upsertOneDocPerBucket(doc, meta);tentar {// Verificar se o documento tem os valores desejadosse (!doc.tipo || doc.tipo !== "recurring_event" (evento recorrente) || !doc.ativo || doc.ativo != verdadeiro) retorno;// O documento deve ter 'action', 'dynamic {}', verbose {}, dynamic.statese (!doc.ação || !doc.dinâmico || !doc.detalhado || !doc.dinâmico.estado) retorno;// Somente o estado do processo pendente só existirá por um "breve" períodose (doc.dinâmico.estado !== "pendente") retorno;var médio = doc.tipo + '::' + doc.id; // criar nossa KEYvar novo documento = nulo;tentar {// ler a versão atual do documento do KV, por exemplo, curDocnovo documento = cron_bkt[médio];} captura (e) {} // necessário para versões anteriores à 6.5, observe que a implementação pura da versão 6.5+ retorna nulo sem exceçãovar razão = nulo;se (!novo documento || novo documento == nulo) {razão = "O documento cron está faltando";} maisse (!novo documento.ativo) {razão = "O documento cron tem active = false";} maisse (!novo documento.dinâmico.estado || novo documento.dinâmico.estado !== doc.dinâmico.estado) {razão = "cron document wrong dynamic.state expected" + doc.dinâmico.estado;} maisse (crc64(doc) !== crc64(novo documento)) {razão = "documento cron alterado";}se (razão != nulo) {se (!novo documento || novo documento == nulo || novo documento.detalhado.programador >= 1) {registro('Ajuda do OnUpdate: X interrompendo a programação porque ' + razão + ',', novo documento)retorno;}}novo documento.dinâmico.estado = "rearmar";// cron_bkt[mid] = newdoc;se (!tryBucketKvWriteWithLog('Ajuda do OnUpdate: F', médio, novo documento)) {// Falha ao gravar newdoc em cron_bkt[key] o erro foi registrado// e não há mais nada que possamos fazer.retorno;}se (novo documento.detalhado.programador >= 1) {registro(Ajuda do 'OnUpdate': A ' + médio + ' mutação #2 para doc para forçar o rearmamento do cronograma');}se (novo documento.detalhado.programador >= 3) {registro('Ajuda do OnUpdate: B ' + médio + ',', novo documento);}} captura (e) {registro('Ajuda do OnUpdate: E ' + meta.id + ', Exceção de erro:', e);}}// CORREÇÃO: FUNÇÃO ADDIN// correção para operações de balde crescente na versão 6.5.Xfunção upsertOneDocPerBucket(doc, meta) {var crcTable = makeCRC32Table();// criar um documento por baldevar isVerbose = 0;var isMacOS = falso; // seria bom se essa fosse uma constante exposta em Eventingvar números = 1024; // o padrão é linux/PCse (isMacOS) {números = 64;}var implorar = (novo Data).getTime();var resultado = getKeysToCoverAllPartitions(crcTable, "_tmp_vbs:", números);para (var vb=0; vb<números; vb++) {// força bruta para encaixar um prefixo de chave em um vBucketvar tst = resultado[vb];se (isVerbose > 1 || (isVerbose == 1) && (vb < 3 || vb > números -4)) {registro("KEY: " + tst);} mais {se (vb == 5) console.registro("\t*\n\t*\n\t*");}// atualizar os itens para acionar uma mutação para nossa função PRIMARYcron_bkt[tst] = { "tipo": "_tmp_vbs", "vb": vb, "ts_millis": implorar, "chave": tst };}var final = (novo Data).getTime();registro("semeando um documento para cada vBucket no alias primary_bucket (levou " + (final - implorar) + " mililis)");}// CORREÇÃO: FUNÇÃO ADDIN// correção para operações de balde crescente na versão 6.5.Xfunção showHex(n) {retorno n.toString(16);}// CORREÇÃO: FUNÇÃO ADDIN// correção para operações de balde crescente na versão 6.5.Xfunção 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;}retorno crcTable;}// CORREÇÃO: FUNÇÃO ADDIN// correção para operações de balde crescente na versão 6.5.Xfunção crc32(crcTable,str) {var crc = 0 ^ (-1);para (var i = 0; i < str.comprimento; i++ ) {crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];}retorno (crc ^ (-1)) >>> 0;}// CORREÇÃO: FUNÇÃO ADDIN// correção para operações de balde crescente na versão 6.5.Xfunção getKeysToCoverAllPartitions(crcTable,keyPrefix,partitionCount) {var resultado = [];var restantes = partitionCount;para (var i = 0; restantes > 0; i++) {var chave = keyPrefix + i;var rv = (crc32(crcTable,chave) >> 16) & 0x7fff;var actualPartition = rv & partitionCount - 1;se (!resultado[actualPartition] || resultado[actualPartition] === indefinido) {resultado[actualPartition] = chave;restantes--;}}retorno resultado;}
- Após a colagem, a tela é exibida como mostrado abaixo:

- Clique em Salvar.
- Para retornar à tela Eventing, clique no botão '< voltar para Eventing' (abaixo do editor) ou clique no link Eventos
Implementar as duas funções
Agora, estamos prontos para iniciar as funções de Eventing. A partir do Console da Web do Couchbase > Eventos tela:
- Clique no nome da função cron_impl_2func_651_help para expandir e expor os controles de função.

- Clique em Implementar.
- No Confirmar a função de implantação selecione "A partir de agora" na opção Limite de alimentação.

Vamos começar com a outra função Eventing. A partir do Console da Web do Couchbase > Eventos tela:
- Clique no nome da função cron_impl_2func_651 para expandir e expor os controles de função.

- Clique em Implementar.
- No Confirmar a função de implantação selecione "A partir de agora" na opção Limite de alimentação.

Configurar um cron tarefa a ser executada quatro (4) vezes por minuto
Neste ponto, nossa função Eventing está aguardando uma mutação especificamente em qualquer documento do tipo="recurring_event" que tenha um campo active=true.
Do Console da Web do Couchbase > Consulta usaremos o N1QL para criar uma nova tarefa agendada no bucket "travel-sample":
- Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultas
123456789101112131415INSERIR PARA `dados cadastrais` (CHAVE,VALOR) VALORES ("recurring_event::1",{"tipo": "recurring_event" (evento recorrente),"id":1,"hora":"*","min":"0","ação": "doCronActionA","verbose": {"user_func": 2,"agendador": 3},"ativo": falso});
- Clique em Executar

Ativar nosso primeiro cron tarefa
O documento de controle que fizemos anteriormente não foi ativado porque especificamos "active":false; além disso, o cronograma acima será executado apenas uma vez por hora, mas queremos testar as coisas e vê-las funcionar em um futuro próximo.
Primeiro, precisamos de um índice para poder manipular nossos documentos de controle no N1QL; isso só precisa ser feito uma vez
- Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultas
1CRIAR primário ÍNDICE em `crondata` ;
Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultasAgora, ativaremos a tarefa, mas ajustaremos a programação de repetição para cada 15 segundos para ver exatamente como o sistema está se comportando. Fazemos isso modificando o documento de controle com KEY recurring_event::1
-
123ATUALIZAÇÃO `crondata`CONJUNTO ativo=VERDADEIRO, hora="*4X", min="*4X"ONDE tipo="recurring_event" (evento recorrente) E id=1 ;
- Clique em Executar

Usamos a sintaxe não padrão de ="*4X" para agendar um item recorrente quatro vezes por minuto e podemos ver nossa função de trabalho doCronActionA executando e também a lógica de manutenção para agendar a função por meio de instruções de registro, pois definimos verbose=3.
O agendador agora está sendo executado quatro vezes por minuto. É possível ver a atividade nas estatísticas e nos arquivos de log do aplicativo para as Eventing Functions cron_impl_2func_651 e cron_impl_2func_651_help.
- Acesse o Console da Web do Couchbase > Painel você verá uma explosão de atividade a cada 15 segundos:

- Acesse o Console da Web do Couchbase > Eventos e clique no botão Registro link do implantado cron_impl_2func_651 Função de registro de eventos. Essa caixa de diálogo Registro de funções lista as instruções de registro em ordem inversa (itens mais recentes primeiro). A saída inicial deve ser semelhante à seguinte:
2020-05-20T18:34:33.340-07:00 [INFO] "OnUpdate B recurring_event::1 recurring timer was created, 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":{"United States":127, "United Kingdom":39, "France":21}}
2020-05-20T18:34:33.233-07:00 [INFO] "A ação Callback R recurring_event::1 levou 0,013 seg., retornou 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 United Kingdom cnt 39"
2020-05-20T18:34:33.232-07:00 [INFO] "doCronActionA N1QL idx 0, country United States cnt 127"
2020-05-20T18:34:33.220-07:00 [INFO] "doCronActionA ação do usuário controlada por recurring_event::1"
2020-05-20T18:34:19.340-07:00 [INFO] "OnUpdate B recurring_event::1 recurring timer was created, 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"A linha mais antiga na parte inferior é a mutação que iniciou a programação (ou rearmou a programação), por exemplo, a mensagem OnUpdate, e vemos as duas primeiras execuções completas da lógica comercial que codificamos em doCronActionA
- Também haverá algumas mensagens relacionadas ao "Correção para operações de balde de crescimento 6.5.X", mas isso será registrado em cron_impl_2func_651_help você verá mensagens como as seguintes a cada 30 segundos:
2020-05-20T18:34::49.185-07:00 [INFO] "Seeding one doc to each vBucket in primary_bucket alias (took 221 mililis)"
Vamos ajustar a frequência e a verbosidade dessa tarefa específica. Usaremos o padrão cron sintaxe de '*' para ambos hora e min para obter uma programação recorrente de uma vez por minuto, 4 vezes mais lenta do que a frequência atualmente em execução. Além disso, reduziremos o nível de verbosidade da lógica do agendador para zero e a função do usuário para 1, para que vejamos apenas uma mensagem por invocação.
Do Console da Web do Couchbase > Consulta usaremos o N1QL para criar uma nova tarefa agendada no bucket "travel-sample":
- Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultas
1234ATUALIZAÇÃO `dados cadastrais`CONJUNTO detalhado.programador = 0, detalhado.user_func = 1,ativo=verdadeiro, hora="*", min="*"ONDE tipo="recurring_event" (evento recorrente) E id=1 ;
- Clique em Executar
Após 2 ou 3 minutos de acesso, o Console da Web do Couchbase > Eventos e clique no botão Registro link do implantado cron_impl_2func_651 Função de eventos.
- O cronograma recorrente agora é de um minuto e muito menos detalhado. Apenas uma mensagem ou linha de registro é emitida para cada execução de função (mais uma vez na ordem inversa de tempo).
2020-05-20T18:43:04.231-07:00 [INFO] "doCronActionA ação do usuário controlada por recurring_event::1"
2020-05-20T18:42:08.233-07:00 [INFO] "doCronActionA ação do usuário controlada por recurring_event::1"Apenas uma mensagem ou linha de log é emitida para cada execução de função de usuário programada, ou seja doCronActionA (mais uma vez na ordem inversa do tempo).
Vamos dar uma olhada no trabalho que está sendo realizado
Esse código fornece uma estrutura prática para executar qualquer função JavaScript em um cronograma recorrente e nossa função doCronActionA está fazendo upserting (inserção ou atualização) de um documento KV de cache calculado uma vez por minuto.
Para verificar os resultados da função de eventos implementada, acesse o Console da Web do Couchbase > Buckets e clique no link Documents (Documentos) da página amostra de viagem balde.
- Na caixa de texto "N1QL WHERE" após o texto a seguir.
1tipo="cron_cache"
- Clique em Recuperar documentos
Agora você deve ver um documento, um documento de cache cron_cache::airlines_by_country que está sendo atualizado uma vez por minuto pela função agendada doCronActionA.

- Clique na identificação "cron_cache::airlines_by_country", você verá o documento em cache que está sendo atualizado pela lógica comercial do doCronActionA.

Espere um minuto e edite o documento novamente e você verá o campo "data" sendo atualizado. É claro que os dados de origem estão "estáticos" no momento, portanto, as contagens permanecerão as mesmas.
Vejamos o documento de controle
Esse código fornece uma estrutura para manter algumas estatísticas sobre cada programação em execução.
Para verificar as estatísticas da função de eventos implementada, acesse a seção Console da Web do Couchbase > Buckets e clique no link Documents (Documentos) da página dados cadastrais balde.
- Na caixa de texto "N1QL WHERE" após o texto a seguir.
1tipo="recuring_event"
- Clique em Recuperar documentos
Agora você deve ver um documento de controle recurring_event::1 que está acionando a função agendada doCronActionA.
- Clique na identificação "evento_recorrente::1", você verá o documento de controle que está sendo atualizado pela lógica do agendador com algumas estatísticas na dinâmica do objeto JSON.

A parte "dinâmica" do documento que é adicionada automaticamente mantém algumas estatísticas de depuração: - prev_sched: é o carimbo de data/hora UNIX anterior da agenda que foi executada pela última vez
- prev_etime: é o registro de data e hora real do UNIX quando a agenda foi executada pela última vez
- prev_delay: é o atraso do prev_sched para o prev_etime
- prev_atime: é o tempo necessário para executar essa ação, ou seja, para executar doCronActionA.
- next_sched: é a próxima execução programada para essa ação
Essas estatísticas mantidas no subobjeto dinâmico JSON para cada agendamento são úteis para determinar se o seu sistema de agendamento está saudável e se a ação que está sendo executada está sendo concluída em tempo hábil.
Verifique se o cache é atualizado nas alterações de dados
Todo o objetivo do doCronActionA é executar o trabalho em um horário programado ou próximo a ele e atualizar um documento de cache com a CHAVE "cron_cache::airlines_by_country".
Vamos fazer algumas validações no Query Monitor para verificar se nosso cache está sendo atualizado: 1) examinando o documento do cache, 2) excluindo algumas companhias aéreas do conjunto de documentos de amostra de viagem e 3) verificando se o documento do cache é atualizado pela função doCronActionA.
Do Console da Web do Couchbase > Consulta usaremos o N1QL para visualizar e manipular os dados no bucket "travel-sample":
- Recorte e cole a seguinte instrução N1QL no arquivo
- Editor de consultas
12SELECIONAR dados DE `amostra de viagem`ONDE `tipo` = 'cron_cache' E id== 'airlines_by_country' (companhias aéreas por país); - Clique em Executar
Na exibição JSON do Query Workbench, você deverá ver:
123456789[{"dados": {"França": 21,"Reino Unido": 39,"Estados Unidos": 127}}] - Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultas
12DELETE DE `amostra de viagem`ONDE `tipo` = "companhia aérea E indicativo GOSTO 'U%' - Clique em Executar
Na exibição JSON do Query Workbench, você deverá ver (acabamos de excluir alguns dados)
123{"resultados": []} - Aguarde um pouco mais de um minuto
- Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultas
12SELECIONAR dados DE `amostra de viagem`ONDE `tipo` = 'cron_cache' E id== 'airlines_by_country' (companhias aéreas por país); - Clique em Executar
Na exibição JSON do Query Workbench, você verá que quatro (4) linhas aéreas não estão mais presentes
123456789[{"dados": {"França": 21,"Reino Unido": 39,"Estados Unidos": 123}}]
Iniciar uma segunda tarefa agendada
Esse código fornece uma estrutura prática para a execução de 1 a N funções JavaScript em programações recorrentes.
Permitiremos que a função doCronActionA continuarão a ser executados em um cronograma de um minuto, mas agora habilitaremos doCronActionB em um cronograma de 30 segundos (duas vezes por minuto). Essa função é um shell vazio e registrará apenas que foi invocada.
Do Console da Web do Couchbase > Consulta usaremos o N1QL para exibir e manipular dados na página 'dados cadastrais' balde:
- Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultas
123456789101112131415INSERIR PARA `crondata` (CHAVE,VALOR) VALORES ("recurring_event::2",{"tipo":"recurring_event" (evento recorrente),"id":2,"hora":"*2X","min":"*2X","ação":"doCronActionB","verbose": {"user_func": 1,"agendador": 0},"ativo": verdadeiro}); - Clique em Executar
Nesse momento, você está executando duas (2) tarefas diferentes, cada uma delas em duas (2) programações diferentes para verificar. Aguarde de dois a três minutos e inspecione os arquivos de registro novamente
- Acesse o Console da Web do Couchbase > Eventos e clique no botão Registro link do implantado cron_impl_2func_651 Função de eventos.
Apenas uma mensagem ou linha de registro é emitida para cada execução de função (mais uma vez na ordem inversa de tempo). Vemos que doCronActionA dispara uma vez por minuto enquanto doCronActionB dispara duas vezes mais, por exemplo, uma vez a cada 30 segundos.2020-05-20T19:16:05.259-07:00 [INFO] "doCronActionA ação do usuário controlada por recurring_event::1"
2020-05-20T19:16:05.255-07:00 [INFO] "Ação do usuário doCronActionB controlada por recurring_event::2"
2020-05-20T19:15:37.253-07:00 [INFO] "Ação do usuário doCronActionB controlada por recurring_event::2"
2020-05-20T19:15:09.250-07:00 [INFO] "doCronActionA ação do usuário controlada por recurring_event::1"
2020-05-20T19:15:09.249-07:00 [INFO] "Ação do usuário doCronActionB controlada por recurring_event::2"
2020-05-20T19:14:34.255-07:00 [INFO] "Ação do usuário doCronActionB controlada por recurring_event::2"
[OPCIONAL] Pausa / Editar JavaScript / Retomar
Basicamente, terminamos esta parte; sinta-se à vontade para experimentar e modificar as coisas e fazer experimentos, por exemplo:
Do Console da Web do Couchbase > Eventos tela:
- Clique no nome da função cron_impl_2func_651 para expandir e expor os controles de função.
- Clique em Pausa.
- No Confirmar a função de pausa selecione "Função de pausa".
- Clique em "Editar JavaScript"
- Se estiver se sentindo confiante, modifique o doCronActionB para executar algumas operações de KV ou integrar-se ao cURL.
Se você quiser apenas ver uma alteração, adicione algo simples à função e tente algo como o seguinte:
1234567891011121314151617função doCronActionB(doc) {tentar {// verificar se o documento tem os valores desejadosse (doc.tipo !== "recurring_event" (evento recorrente) || doc.ativo !== verdadeiro) retorno;se (doc.detalhado.user_func >= 1)registro(doc.ação + ' ação do usuário controlada por ' + doc.tipo + '::' + doc.id);// SUA LÓGICA AQUIvar a = 1 + 7;registro('esta é minha lógica, a = 1 +7 = ' + a);} captura (e) {registro(doc.ação + ' Exceção de erro:', e);retorno falso;}retorno verdadeiro;} - Clique em Salvar.
- Para retornar à tela Eventing, clique no botão '< voltar para Eventing' (abaixo do editor) ou clique no link Eventos
- Clique em Currículo.
- No Confirmar função de retomada selecione "Retomar a função".
- Aguarde cerca de um minuto e a função cron_impl_2func_651 para implementar
- Clique no botão Registro link do implantado cron_impl_2func_651 Função de eventos.
2020-05-20T19:20:41.343-07:00 [INFO] "esta é a minha lógica, a = 1 +7 = 8"
2020-05-20T19:20:41.343-07:00 [INFO] "Ação do usuário doCronActionB controlada por recurring_event::2"
Limpeza
A limpeza envolve o cancelamento da implantação e a exclusão da função e, em seguida, a remoção dos dois buckets que você criou. Isso conclui o exemplo.
Remover funções
Do Console da Web do Couchbase > Eventos tela:
- Clique no nome da função cron_impl_2func_651 para expandir e expor os controles de função.
- Clique em Desimplantação.
- No Confirmar a função de cancelar a implantação selecione "Função Undeploy".
- Aguarde a função cron_impl_2func_651 para cancelar a implantação.
- Clique em Excluir.
- No Confirmar a função de exclusão selecione "Excluir função".
Do Console da Web do Couchbase > Eventos tela:
- Clique no nome da função cron_impl_2func_651_help para expandir e expor os controles de função.
- Clique em Desimplantação.
- No Confirmar a função de cancelar a implantação selecione "Função Undeploy".
- Aguarde a função cron_impl_2func_651_help para cancelar a implantação.
- Clique em Excluir.
- No Confirmar a função de exclusão selecione "Excluir função".
Remover baldes
Próximo: "Solte os baldes".metadados' 'dados cadastrais', e 'amostra de viagem' (eles sempre podem ser recriados).
Do Console da Web do Couchbase > Buckets e clique no link Documents (Documentos) da página amostra de viagem balde.
- Clique em no nome do balde "metadados" para expandir e expor os controles
- Clique em Excluir
- No Confirmar Excluir compartimento selecione "Excluir balde".
- Clique em no nome do balde "dados cadastrais" para expandir e expor os controles
- Clique em Excluir
- No Confirmar Excluir compartimento selecione "Excluir balde".
- Clique em no nome do balde "amostra de viagem" para expandir e expor os controles
- Clique em Excluir
- No Confirmar Excluir compartimento selecione "Excluir balde".
Considerações finais
Espero que você tenha achado este passo a passo educativo e que tenha adquirido mais conhecimento sobre o Couchbase Eventing Service como um todo.
Anteriormente, observei que o Eventing foi projetado para processar mutações de alta velocidade (em milhões por segundo) a partir do fluxo DCP do bucket de origem associado à função. Essa função ou agendador de eventos só precisa reagir a alterações mínimas nos documentos do agendador.
Iniciei 5.000 programações com esse código inalterado apenas adicionando documentos de controle. Cheguei a executar 120.000 programações a cada minuto apenas para testar essa implementação (sim, 120.000 é uma quantidade absurda de programações independentes). cron programações a serem executadas). Além disso, coloquei 120.000 sc3hedules 2 dias no futuro para garantir que não tivéssemos problemas de recursos degenerados.
Sendo forçado a usar duas Evening Functions para criar o sistema de agendamento, a função principal cron_impl_2func_651 e um ajudante simples cron_impl_2func_651_help não foi tão elegante quanto eu esperava. Além disso, ter de contornar um vazamento de opções de balde disparando um cronômetro em cada vBucket foi, no mínimo, decepcionante. Felizmente, devido a esse esforço, foram implementadas mudanças e, na próxima versão 6.6.0, poderei criar um agendador mais limpo usando uma única Evening Function. Os principais aprimoramentos na versão 6.6 são: 1) a capacidade de gerar um novo cronômetro a partir de uma chamada de retorno de cronômetro; 2) a capacidade de cancelar ou substituir um cronômetro existente por referência; e 3) a eliminação do uso crescente de recursos em sistemas ociosos com cronômetros agendados no futuro.
Utilizei um balde autônomo 'dados cadastrais' para manter o cronograma ou os documentos de controle (não considero o uso de 'metadados', pois é um bloco de notas do sistema necessário para a Eventing e reservado para a própria Eventing) para oferecer a maior flexibilidade na manipulação de dados em outros blocos. Se eu tivesse colocado os documentos de controle em outro compartimento, não seria capaz de realizar operações N1QL nesse compartimento (já que ele é um compartimento de origem para a função Eventing) e estaria limitado apenas a operações KV para manipular dados no compartimento colocado.
Eu o desafio a experimentar outros casos de uso do cron e também a pensar em outras maneiras de aproveitar um serviço de agendamento:
- Verificação de uma contagem de itens em um grande conjunto de dados durante "horários fora de pico" e realização de limpeza incremental
- Realização de enriquecimento programado de documentos via N1QL.
- Recalcular carteiras de ações em um cronograma regular.
- Gerenciando TTL ou tempos de expiração via N1QL em uma programação recorrente, consulte Como gerenciar documentos TTL (Time-To-Live) com o Couchbase N1QL.
- Integrando com pontos de extremidade REST externos em um cronograma repetitivo, consulte Usando cURL com o Eventing Service: Atualização.
- Atualize o JavaScript de cron_impl_2func_651 para adicionar um novo campo "prev_astatus" ao objeto dinâmico JSON para salvar o sinalizador de resultado verdadeiro/falso retornado da ação do usuário executada anteriormente.
Atualizações
Este blog foi atualizado em 24 de julho de 2020 para adicionar uma solução alternativa para as versões 6.5.x que têm um número crescente de operações de balde de metadados que podem eventualmente bloquear mutações para uma determinada função Eventing ao criar temporizadores no futuro (como em uma hora ou mais) em um sistema ocioso.
Próximas etapas
Em algumas semanas "Implementação de um agendador robusto e portátil do tipo cron por meio do Couchbase Eventing (Parte 2)" será lançado, no qual exploraremos a execução de uma sequência de instruções N1QL dinâmicas orientadas pelo banco de dados sem a necessidade de editar a Eventing Function ou de definir um script de "ação" codificado dentro da Eventing Function.
Recursos
- Download: Download do Couchbase Server 6.5.1
- Função de eventos: cron_impl_2func_651.json
- Ajudante de eventos Função: cron_impl_2func_651_help.json
Referências
- Documentação do Couchbase Eventing:
https://docs.couchbase.com/server/current/eventing/eventing-overview.html - Couchbase Server 6.5 O que há de novo:
https://docs.couchbase.com/server/6.5/introduction/whats-new.html - Blogs do Couchbase sobre eventos:
https://www.couchbase.com/blog/tag/eventing/
Gostaríamos muito de saber se você gostou dos recursos da versão 6.5 e como ela beneficiará seus negócios daqui para frente. Compartilhe seu feedback nos comentários ou na seção Couchbase fórum.
Notas de rodapé
[1] A implementação do timer no Eventing Service foi projetada para lidar com um grande número de timers distribuídos na casa dos milhões em alta velocidade. Um único nó Eventing pode lidar com mais de 100 mil temporizadores por segundo e a única promessa é executar os temporizadores o mais rápido possível, ou seja, sem perda de temporizadores. Considere que o intervalo de varredura atual é de sete (7) segundos para coletar os temporizadores que estão prontos para disparar e, portanto, você deve esperar alguns atrasos. Para obter mais detalhes sobre o agendamento de temporizadores, consulte Temporizadores: Precisão do relógio de parede na documentação do Couchbase.
[2] Por meio de ajustes allow_interbucket_recursion para verdadeiro você está removendo as proteções que foram colocadas no servidor Couchbase para proteger contra a lógica de Eventing acidental, que pode iniciar loops recursivos infinitos. Não há nada de errado nisso, mas é fácil cometer um erro ao aproveitar a recursão. Nas versões 6.6 do Couchbase, o ajuste da lógica Eventing pode ser reduzido de duas (2) Eventing Functions para uma (1) Eventing Function simplificada, sem a necessidade de ajustar o allow_interbucket_recursion configuração.
[3] Existem duas grandes limitações. Primeiro, se um documento for modificado várias vezes em um curto período, as chamadas poderão ser agrupadas em um único evento devido à deduplicação. Segundo, não é possível discernir entre as operações de criação e atualização. Para as propostas de um cron nenhuma das limitações apresenta um problema.
[4] Por que eu não implementei a semântica exata do crontab? Eu poderia ter implementado, mas a quantidade de código é excessiva. https://github.com/kelektiv/node-cron juntamente com suas dependências de moment e moment-timezone (todos pacotes muito grandes). O getNextRecurringDate(hour_str, min_str) pode não ser tão flexível, mas é simples e abrange nosso caso de uso.
Estou muito interessado nas atualizações da versão 6.6.x para usar uma única função! Adoro essa implementação para um agendador do tipo cron.
Acabei de perceber que isso está disponível no GitHub! Vou dar uma olhada nisso! Recomendo atualizar esta postagem do blog com o link :) Obrigado!
Oi Alex,
Em breve, atualizarei o "agendador do tipo cron" com uma versão 6.6.0 e uma versão 6.6.1 (usando acessores avançados de bucket).
No entanto, provavelmente em um blog posterior (Parte 2), mas farei um link cruzado entre eles. Fico feliz que tenha encontrado meu protótipo 6.6.0 no GitHub.