Celery: Recurso: a batida deve evitar invocações simultâneas

Criado em 17 nov. 2010  ·  48Comentários  ·  Fonte: celery/celery

Exigir que o usuário garanta que apenas uma instância de celerybeat exista em seu cluster cria um fardo de implementação substancial (seja criando um único ponto de falha ou encorajando os usuários a lançar seu próprio mutex distribuído).

O celerybeat deve fornecer um mecanismo para evitar a simultaneidade inadvertida ou a documentação deve sugerir uma abordagem de prática recomendada.

Celerybeat

Comentários muito úteis

@ ankur11 batimento único garante que apenas uma instância de batimento de aipo esteja em execução, mas não sincroniza o estado da programação entre as instâncias.

Se eu usasse o agendador padrão com uma tarefa periódica destinada a ser executada a cada 15 minutos e tivesse um failover com batimento único 14 minutos após a última vez que a tarefa foi executada, a tarefa não seria executada até 15 minutos após a nova batida de aipo instância iniciada, resultando em um intervalo de 29 minutos.

Para compartilhar o estado da programação entre as instâncias, precisei usar um programador alternativo . django-celery-beat é a alternativa mencionada nos documentos do Celery, mas minha preferência era usar o Redis como back-end para a sincronização do cronograma, uma vez que eu já estava usando o Redis como back-end do Celery.

O Redbeat inclui o estado de programação compartilhada apoiada pelo Redis e o bloqueio para garantir que apenas uma instância esteja programando tarefas, então não precisei de single-beat ou BeatCop quando comecei a usá-lo.

Em nossa implementação, a batida de aipo é iniciada pelo supervisord em todas as instâncias, com Redbeat como agendador (por exemplo, exec celery beat --scheduler=redbeat.RedBeatScheduler --app=myproject.celery:app ). Infelizmente, não posso compartilhar código relacionado ao trabalho, mas fico feliz em responder a quaisquer perguntas adicionais sobre a implementação em geral.

Todos 48 comentários

Isso poderia ser resolvido usando kombu.pidbox , também é assim que celeryd detecta que já existe um nó com o mesmo nome em execução. Como celerybeat é centralizado, ele poderia usar um nome de nó fixo.

Como efeito colateral, poderemos controlar o celerybeat com comandos de controle remoto (pode haver um comando para recarregar a programação, por exemplo, ou ver quais tarefas devem ser feitas em um futuro próximo). Esse é um efeito colateral incrível, se você me perguntar.

Precisa de mais planejamento, pois há um caso de uso para executar várias instâncias no mesmo cluster. Por exemplo, para "fragmentar" a programação em várias partes. Deve haver pelo menos a possibilidade de selecionar um nome de nó para cada instância. Adiando para 2.3.0.

Tivemos um problema em que a caixa que estava executando celerybeat ficou offline sem um bom substituto para iniciar uma nova instância celerybeat para tomar o seu lugar. Qual é a forma de HA recomendada para executar celerybeat ?

Será que a abordagem kombu.pidbox nos permitiria executar várias instâncias de celerybeat que apenas ficariam suspensas se detectassem que uma instância já estava em execução com o nome de nó fixo e uma pesquisa para promover-se a ativa se isso a instância cai?

A execução de várias instâncias ativas parece interessante - que outros benefícios podem haver além de compartilhar a programação?

+1

+1

Isso é algo que é uma preocupação real para grandes implantações, onde a resiliência do agendamento é importante.

+9999;)
Há algo de errado em usar a solução kombu.pidbox ? Mesmo sem fragmentação e recursos sofisticados, isso seria ótimo e muito útil. No momento, preciso iniciar manualmente o celerybeat em outro host.

Pidbox poderia ser usado, mas o problema é que a beat não é um consumidor. Para responder a mensagens de transmissão como 'alguma instância de batida aqui?' ele teria que escutar constantemente as mensagens em sua fila de transmissão, e atualmente não pode porque está ocupado programando mensagens.

Tecnicamente, poderia estar usando um segundo thread, mas isso pode prejudicar o desempenho e sobrecarregar muito apenas esse recurso.

Uma segunda solução poderia ser usar uma fechadura, mas com a desvantagem de ter que soltá-la. Ou seja, se o processo de batida for interrompido, o bloqueio obsoleto exigiria intervenção manual para iniciar uma nova instância.

Ele também pode ter um tempo limite de 2 segundos na fechadura e atualizar a fechadura a cada segundo. Isso significa que uma nova instância teria que esperar 2 segundos se o bloqueio fosse mantido.

Um bloqueio no amqp pode ser criado declarando uma fila, por exemplo, `queue_declare ('celerybeat.lock', arguments = {'x-expires': 2000}` `

+1

Eu adoraria ver isso

+1

+1

+1 também

+1

Alguém realmente implementou a solução kombu.pidbox ou qualquer outro mecanismo que resolva este problema? Se assim for, por favor, compartilhe. Muitas pessoas ainda estão se perguntando qual é a melhor prática.

Alguém se afastou completamente do aipo devido a isso? Eu também estaria interessado em saber disso.

EDITAR:

Eu encontrei esta essência (https://gist.github.com/winhamwr/2719812) por meio de uma discussão no Google (https://www.google.co.in/search?q=celerybeat+lock&aq=f&oq=celerybeat+lock&aqs= chrome.0.57j62l3.2125j0 & sourceid = chrome & ie = UTF-8).

Também estou me perguntando se alguém acabou de usar um pidfile compartilhado para celerybeat diretamente, talvez com um EBS no AWS ou talvez em um balde S3… celerybeat --pidfile=/path/to/shared/volume .

Percebi que o master atual (3.1 dev) tem uma etapa de fofoca para o consumidor. Seria possível alavancar a fila de fofocas e a eleição do líder para coordenar os processos de batida embutidos? Ou seja, cada trabalhador executaria o processo de batimento integrado, mas apenas o líder enfileiraria a tarefa periódica. Isso provavelmente pressupõe um armazenamento de agendamento compartilhado.

@mlavin Isso pode funcionar, mas apenas para transportes de corretor que suportam transmissão

O problema com a solução pidbox é que o programa celerybeat deve ser reescrito para usar E / S Async.
Atualmente, ele não pode consumir tarefas e produzi-las, uma vez que o planejador está bloqueando.

Na maioria dos casos, esse não é um recurso necessário, pois a maioria das implantações de produção tem um host dedicado para o processo de batida e, em seguida, usar um --pidfile é o suficiente para garantir que você não inicie várias instâncias.

Eu descobri que muitas vezes as pessoas afetadas por esse problema são aquelas que usam a opção -B em um daemonização
script e, em seguida, duplica essa configuração para outro host.

Então eu entendo que é irritante, mas não acho que seja crítico. Se alguém realmente deseja uma solução, pode contribuir ou me contratar / doar para implementá-la.

Pode-se usar uWSGI para ter um processo de batimento único com fallback para outro (s) nó (s)

+1, lançamos instâncias idênticas do Amazon EC2 e será bom ter tarefas periódicas que são executadas apenas em um nó. Enquanto isso, vou tentar usar uWSGI obrigado pela sugestão.

+1

+1

Tenho defendido no trabalho o uso do Celerybeat para agendamento, mas não ter HA pronto para uso torna isso muito difícil. Na verdade, parece que vamos abandoná-lo completamente por causa disso. Muito simplesmente, a execução de apenas 1 instância do Celerybeat torna isso um único ponto de falha e, portanto, não está pronto para a produção para nós.

@junaidch Não acho que você deva deixar cair o aipo por causa disso. Você sempre pode simplesmente executar o agendador em cada servidor e, para tarefas periódicas, usar algum tipo de mecanismo de bloqueio para garantir que eles não se sobreponham de forma alguma e também não sejam executados com muita frequência. Além disso, você pode subclassificar o agendador e fazer um bloqueio lá também ou pular o bloqueio de nível de tarefa e apenas fazer tudo no agendador.

Seria melhor ter alguma funcionalidade embutida no aipo, pois isso é uma espécie de solução alternativa, mas ainda pode ser usado na produção.

Obrigado @ 23doors.

Minhas tarefas já mantêm um bloqueio Redis para evitar que outra instância da tarefa seja executada. Se eu executar 2 batidas em 2 máquinas diferentes e minhas tarefas forem agendadas para intervalos de 5 minutos, acho que funcionará, embora ambas as batidas estejam empurrando as tarefas para a fila. Argumentar para adoção fica ainda mais difícil quando você precisa implementar uma solução alternativa para sua funcionalidade principal.

Vou investigar a recomendação de subclassificação. Essa pode ser uma abordagem mais limpa.

Obrigado pelas sugestões!

Na Lulu, resolvemos isso escrevendo um gerenciador de singleton de cluster simples (denominado BeatCop). Ele usa um bloqueio Redis que está expirando para garantir que haja apenas um Celerybeat em execução em um pool de escalonamento automático de workers do Celery. Se algo acontecer com esse Celerybeat (como a instância é dimensionada ou morre ou o Celerybeat falha), outro nó gera automaticamente um novo Celerybeat. Abrimos o código do BeatCop .

@ingmar , escrevemos este https://github.com/ybrs/single-beat pelos mesmos motivos, da última vez que verifiquei não vi o seu comentário. nós também lançamos como código aberto pode ser útil para alguns outros. mais ou menos faz a mesma coisa.

até onde posso ver, as principais diferenças com o beatcop - usamos pyuv - então o beatcop é mais portátil, acho que menos dependências -, redirecionar o stderr e o stdout do filho como pais e sair se o filho morrer com o mesmo código, configure-o com variáveis ​​env. então é um pouco mais fácil adicionar ao supervisor.

espero que seja útil para outra pessoa.

+1

+1

Estou pensando em usar os valores-chave do Consul, como controlador de bloqueio. Alguém já experimentou essa abordagem? Assim, enquanto houver uma instância funcionando, as outras "hibernarão" até que o bloqueio não seja atualizado, então o mecanismo de eleição do Cônsul decidirá quem atualizará o valor da chave com carimbo de data / hora. Quem atualiza a fechadura é quem está trabalhando.

@ingmar Obrigado por isso! Vou dar uma chance ao meu cluster de trabalho.

+10, pois a implementação atual significa um único ponto de falha que foge do motivo pelo qual usamos uma fila distribuída em primeiro lugar

+1

+1

Parece que vai estar na v5.0.0 https://github.com/celery/celery/milestones/v5.0.0

+1

Fechando isso, como com os recursos atuais, levará 10 anos para ser concluído.

Desculpe, mas este é um problema sério para uma chamada fila "distribuída". Por mais tempo que isso leve para ser implementado, ele deve ser consertado. Fechar um problema perfeitamente válido porque você não tem os recursos _ agora_ não parece certo. Você poderia reabri-lo e aplicar um rótulo que indique que é de baixa prioridade no momento?

Sei que meu motivo para fechar foi absurdamente abrupto, então, como usuário de software, posso entender seu sentimento, mas tecnicamente o Beat é mais como um recurso adicional. Ele está completamente desacoplado do resto do Celery e foi intencionalmente projetado para não ser distribuído para manter a implementação simples. Começou como uma maneira elegante de definir cronjobs do Python como um bônus para usuários que já usam o Celery, então mais e mais pessoas usaram o Celery como um substituto do cron.

O problema está aberto há SEIS anos e, embora seja solicitado com frequência, e inúmeras empresas estejam dependendo dele, nenhuma jamais se ofereceu para pagar por sua implementação.

Na verdade, foi uma das questões que achei que seria interessante para as empresas patrocinarem. Concedido, não é comum as empresas se oferecerem para pagar por qualquer recurso, correção de bug ou até mesmo para ajudar a resolver um problema de produção. Provavelmente posso contá-los em uma mão (você é incrível), então agora eu sei como essa ideia era totalmente ingênua :)

Fechei uma cópia desta edição também hoje, consulte # 1495. Houve solicitações de pull tentando resolver o problema, e várias são promissoras, mas com a dedicação necessária para provar que uma determinada implementação funciona, ainda não tive tempo para analisá-las adequadamente. Talvez isso coloque alguém em ação, mesmo que não ache que seja melhor do que manter uma solicitação de recurso aberta por seis anos, quando ninguém está trabalhando nisso. Isso também é uma espécie de desserviço para os usuários que desejam ver isso corrigido.

@ask Bastante justo. É verdade que o cron distribuído é um grande problema complicado, como você disse no outro tópico. E soa como algo que deveria viver fora de Celery.

Obrigado por explicar seu raciocínio em detalhes.

@ask Eu queria saber se este problema pode ser contornado localizando o arquivo celerybeat-schedule (usado por celery.beat.PersistentScheduler ) dentro de um volume NFS que é compartilhado entre todos os nós do cluster.

A classe PersistentScheduler usa shelve como um módulo de banco de dados, portanto, gravações simultâneas no arquivo celerybeat-schedule devem ser evitadas por design. Este é um excerto do shelve documentação :

O módulo arquivado não suporta acesso simultâneo de leitura / gravação a objetos arquivados. (Múltiplos acessos de leitura simultâneos são seguros.) Quando um programa tem uma prateleira aberta para gravação, nenhum outro programa deve tê-la aberta para leitura ou gravação.

Supondo que comecemos a batida do aipo assim:

celery -A project-name beat -l info -s /nfs_shared_volume/celerybeat-schedule

onde /nfs_shared_volume é o volume compartilhado (por exemplo, gerenciado pelo AWS Elastic File System), podemos esperar que as programações não sejam bagunçadas mesmo se houver um processo de batimento de aipo em execução em cada nó do cluster?

@mikeschaekermann Se eu estiver lendo os documentos corretamente, shelve não faz nenhum esforço para impedir o acesso de gravação simultâneo. Apenas diz para você não deixar isso acontecer. A seção que você citou continua dizendo "O bloqueio de arquivos do Unix pode ser usado para resolver isso, mas isso difere nas versões do Unix e requer conhecimento sobre a implementação do banco de dados usada."

@ ze-phyr-us Acho que você está certo e interpretei mal os shelve docs. Ainda assim, gostaria de saber se o problema seria resolvido assumindo que o back-end Scheduler garante as operações @ask o pacote django-celery-beat suporta atomicidade para resolver o problema? Eu vi que ele usa transações para fazer algumas das atualizações.

Para qualquer pessoa que acabe aqui enquanto procura por uma batida de aipo amigável distribuída / dimensionada automaticamente e fique feliz em usar o Redis como back-end; Tentei o BeatCop e o single-beat mencionados acima, mas acabei escolhendo o RedBeat .

Oi @ddevlin
Estou tendo problemas semelhantes, quais problemas você enfrentou ao usar o single-beat? Além disso, se não for muito, você poderia compartilhar o exemplo de implementação de como você configurou o redbeat para vários servidores.

@ ankur11 batimento único garante que apenas uma instância de batimento de aipo esteja em execução, mas não sincroniza o estado da programação entre as instâncias.

Se eu usasse o agendador padrão com uma tarefa periódica destinada a ser executada a cada 15 minutos e tivesse um failover com batimento único 14 minutos após a última vez que a tarefa foi executada, a tarefa não seria executada até 15 minutos após a nova batida de aipo instância iniciada, resultando em um intervalo de 29 minutos.

Para compartilhar o estado da programação entre as instâncias, precisei usar um programador alternativo . django-celery-beat é a alternativa mencionada nos documentos do Celery, mas minha preferência era usar o Redis como back-end para a sincronização do cronograma, uma vez que eu já estava usando o Redis como back-end do Celery.

O Redbeat inclui o estado de programação compartilhada apoiada pelo Redis e o bloqueio para garantir que apenas uma instância esteja programando tarefas, então não precisei de single-beat ou BeatCop quando comecei a usá-lo.

Em nossa implementação, a batida de aipo é iniciada pelo supervisord em todas as instâncias, com Redbeat como agendador (por exemplo, exec celery beat --scheduler=redbeat.RedBeatScheduler --app=myproject.celery:app ). Infelizmente, não posso compartilhar código relacionado ao trabalho, mas fico feliz em responder a quaisquer perguntas adicionais sobre a implementação em geral.

@mikeschaekermann você pode tentar embrulhar sua batida de aipo com / use / bin / flock para bloquear o acesso ...

flock /nfs/lock.file celery beat ...

Supondo que você confie em sua implementação de bloqueio NFS :)

Isso garantiria que apenas um realmente funcionasse e os outros bloqueassem até que o armário morresse.

@mikeschaekermann você pode tentar embrulhar sua batida de aipo com / use / bin / flock para bloquear o acesso ...

bando /nfs/lock.file batida de aipo ...

Supondo que você confie em sua implementação de bloqueio NFS :)

Isso garantiria que apenas um realmente funcionasse e os outros bloqueassem até que o armário morresse.

Eu tentei esse método. Infelizmente, se o cliente que mantém o bloqueio NFS perder a conectividade com o servidor NFS, o bloqueio pode ser revogado pelo servidor NFS e fornecido a outro cliente. Quando o titular da fechadura original recupera a conectividade, o flock não percebe que a fechadura foi revogada, então agora há dois nós acreditando que são os 'líderes'.

Acabei usando um bloqueio consultivo no Postgres. Eu criei um comando de gerenciamento do Django que usa o módulo django_pglocks e executa o celery beat em um subprocesso.

Acabei usando um bloqueio consultivo no Postgres. Eu criei um comando de gerenciamento do Django que usa o módulo django_pglocks e executa o celery beat em um subprocesso.

Parece que pode ser suscetível aos mesmos problemas que vi ao usar o NFS. O que acontece se o cliente que está com o bloqueio perder a conexão com o servidor Postgres ou se o servidor Postgres for reiniciado?

@ swt2c Argh, claro que você está certo! Precisa haver algum tipo de keep alive.

Agora estou fazendo:

def _pre_exec():
    prctl.set_pdeathsig(signal.SIGTERM)

with advisory_lock(LOCK_ID) as acquired:
            assert acquired
            logging.info("Lock acquired: %s", acquired)
            p = subprocess.Popen(
                celery,
                shell=False,
                close_fds=True,
                preexec_fn=_pre_exec,
            )
            sys.exit(p.wait())

advisor_lock suporta recursão, mas não sei se ele está realmente verificando o banco de dados:

In [8]:  with advisory_lock('foo') as acquired:
   ...:     print acquired
   ...:     while True:
   ...:        with advisory_lock('foo') as acquired:
   ...:           print acquired
   ...:        time.sleep(1)
   ...:       

# Yes, it does:

True
True
True
<shutdown the instsance>
InterfaceError: cursor already closed

Então ... eu poderia modificá-lo para continuar sub-readquirindo o lock / polling e kill beat se falhar. Não garante exclusão mútua, mas pode ser bom o suficiente para meus objetivos.

Para o meu caso, batidas simultâneas são um aborrecimento desnecessário, mas não um problema de integridade. Se fosse, eu também poderia encerrar a tarefa em um bloqueio consultivo que, se o banco de dados cair, a tarefa falhará de qualquer maneira.

Também estou tentando armazenar o cronograma de batimento no banco de dados, mas não testei o que o batimento faz quando o banco de dados cai.

@ddevlin Fiquei feliz em ver seu comentário, pois essa era a solução que eu também estava pensando em implementar.

No entanto, se você pudesse compartilhar a lógica de como o supervisor é reiniciado automaticamente redbeat-1 quando redbeat-2 cai, isso seria de grande ajuda.

Isso pode ser devido à minha falta de compreensão sobre supervisor , mas parece que autorestart=True é eficaz apenas para programas que pelo menos entram no estado RUNNING uma vez.

Meu problema é:

  1. Eu tenho dois program em meu supervisor.conf de celery beat com redbeat.RedBeatScheduler .
  2. Supervisor inicial, um beat ( beat-1 ) obtém o bloqueio e executa, enquanto o outro ( beat-2 ) tenta iniciar algumas vezes e entra no FATAL state (com o erro Seems we're already running? ).
  3. Idealmente, se beat-1 parar, quero que o supervisor inicie beat-2 .
  4. No entanto, isso não acontece, uma vez que nunca esteve em um estado RUNNING para começar. O que significa que se eu parar beat-1 , ele irá parar e nada acontecerá.

Pensando bem, a solução seria ter cron que continua fazendo supervisorctl restart all cada 5 segundos ou mais, mas só queria saber como você conseguiu essa redundância com o supervisor.

Olá @harisibrahimkv , seu problema é que você está iniciando duas instâncias idênticas de batida de aipo no mesmo host; Espero que você esteja vendo ERROR: Pidfile (celerybeat.pid) already exists. em seus registros de beat-2 . Eu posso ver que ter duas instâncias de aipo em execução no mesmo host seria útil para testar o failover entre elas, mas para redundância real, você provavelmente deseja que o aipo em execução em vários hosts.

Para fazer com que várias instâncias sejam executadas no mesmo host, faça com que o supervisor inicie-as com o argumento --pidfile e forneça-lhes pidfiles separados: por exemplo,

# beat-1 
celery beat --scheduler=redbeat.RedBeatScheduler --pidfile="beat-1.pid" ...
# beat-2
celery beat --scheduler=redbeat.RedBeatScheduler --pidfile="beat-2.pid" ...

Ambas as instâncias devem ser iniciadas com sucesso sob o supervisor, mas se você verificar os arquivos de log, apenas uma delas deve estar agendando tarefas. Se você interromper essa instância, deverá ver a outra instância assumir o agendamento de tarefas.

Nosso objetivo era ter um pool de escalonamento automático de hosts idênticos executando operários de aipo e aipo sob supervisão. Cada host tem uma única instância de batida de aipo. Nesta configuração, a batida de aipo deve começar com sucesso em todos os hosts, mas quaisquer instâncias de batida de aipo que não adquiram o bloqueio serão efetivamente espera ativa e não agendam tarefas (embora todos os hosts no pool processem tarefas). Se a instância com o bloqueio for interrompida (por exemplo, quando o pool é reduzido ou quando estamos fazendo uma atualização contínua de hosts no pool), uma das instâncias em espera obterá o bloqueio e assumirá as tarefas de agendamento.

@ddevlin Muito obrigado por me responder e fazer da Internet um lugar tão maravilhoso! Agradeço sinceramente! (estava correndo contando a minha família inteira sobre sua resposta: D)

  1. O bit pidfile funcionou e eu fiquei muito feliz em ver beat-2 assumir as tarefas quando o outro parou. Pode configurar o tempo da batida com CELERYBEAT_MAX_LOOP_INTERVAL = 25 (no aipo 3.x).

  2. Sim, para redundância real, planejamos ter essa configuração em instâncias diferentes. Obrigado por explicar a configuração que você estava usando. Vou trabalhar nisso agora. A configuração de "vários hosts na mesma instância", como você entendeu corretamente, era apenas para validar inicialmente se o conceito de failover funciona com essa configuração de supervisor.

Calorosos agradecimentos,
De uma pequena aldeia na ponta mais meridional do subcontinente indiano. :)

Esta página foi útil?
0 / 5 - 0 avaliações