Pergunta

Estou escrevendo registros em mnésia que devem ser mantidos lá apenas por um tempo permitido (24 horas). após 24 horas, antes que um usuário modifique parte deles, o sistema deve removê-los automaticamente. por exemplo, um usuário recebe tempo de antena gratuito (para chamadas de voz) que eles devem usar em um determinado momento. se não o usarem, após 24 horas, o sistema deve remova essas reservas de recursos do registro de usuários.

Agora, isso trouxe cronômetros. um exemplo de estrutura de registro é:

 -record(free_airtime,
            {
                reference_no,
                timer_object,   %% value returned by timer:apply_after/4
                amount
            }).
 

O objeto timer no registro é importante porque no caso do usuário finalmente coloca em uso os recursos reservados antes que eles se esgotem (ou se eles expirarem), o sistema pode chamar timer:cancel/1 de modo a aliviar o servidor de cronômetro a partir deste objeto. Agora o problema, eu tenho duas maneiras de lidar com temporizadores nesses registros:

Opção 1: cronômetros controlados na transação

 reserve_resources(Reference_no,Amnt)->
    F = fun(Ref_no,Amount) -> 
            case mnesia:read({free_airtime,Ref_no}) of
                [] -> 
                    case mnesia:write(#free_airtime{reference_no = Ref_no,amount = Amount}) == ok of
                        true -> 
                            case timer:apply_after(timer:hours(24),?MODULE,reference_no_timed_out,[Ref_no]) of
                                {ok,Timer_obj} -> 
                                    [Obj] = mnesia:read({free_airtime,Ref_no}),
                                    mnesia:write(Obj#free_airtime{timer_object = Timer_obj});
                                _ -> mnesia:abort({error,failed_to_time_object})
                            end;
                        false -> mnesia:abort({error,write_failed})
                    end;
                [_] -> mnesia:abort({error,exists,Ref_no})
            end
        end,
    mnesia:activity(transaction,F,[Reference_no,Amnt],mnesia_frag).

Sobre a opção acima.

Os documentos da Mnesia dizem que as transações podem ser repetidas pelo gerenciador de tm (por algum motivo) até que tenham sucesso, e então quando você colocar o código que é io:format/2 ou qualquer outro que não tenha nada a ver com escreve ou lê, pode ser executado várias vezes. Esta declaração me fez parar neste ponto e pensar em uma maneira de lidar com temporizadores fora da própria transação, então modifiquei o código como segue:

Opção 2: cronômetros controlados fora da transação

reserve_resources(Reference_no,Amnt)->
    F = fun(Ref_no,Amount) -> 
            case mnesia:read({free_airtime,Ref_no}) of
                [] -> 
                    P = #free_airtime{reference_no = Ref_no,amount = Amount},
                    ok = mnesia:write(P),
                    P;
                [_] -> mnesia:abort({error,exists,Ref_no})
            end
        end,    
    Result  =   try mnesia:activity(transaction,F,[Reference_no,Amnt],mnesia_frag) of
                    Any -> Any
                catch
                    exit:{aborted,{error,exists,XX}} -> {exists,XX}
                    E1:E2 -> {error,{E1,E2}}
                end,
    on_reservation(Result).

on_reservation(#free_airtime{reference_no = Some_Ref})-> 
    case timer:apply_after(timer:hours(24),?MODULE,reference_no_timed_out,[Some_Ref]) of
        {ok,Timer_obj} -> 
                [Obj] = mnesia:activity(transaction,fun(XX) -> mnesia:read({free_airtime,XX}) end,[Some_Ref],mnesia_frag),
                ok = mnesia:activity(transaction,fun(XX) -> mnesia:write(XX) end,[Obj#free_airtime{timer_object = Timer_obj}],mnesia_frag);
        _ -> 
            ok = mnesia:activity(transaction,fun(XX) -> mnesia:delete({free_airtime,XX}) end,[Some_Ref],mnesia_frag),
            {error,failed_to_time_object}
    end;
on_reservation(Any)-> Any.

O código para controlar o tempo limite da reserva:

reference_no_timed_out(Ref_no)->
    do_somethings_here.....
    then later remove this reservation from the database....below..
    ok = mnesia:activity(transaction,fun(XX) -> mnesia:delete({free_airtime,XX}) end,[Ref_no],mnesia_frag).

Agora pensei que na opção 2, estou mais seguro mantendo o processamento do cronômetro codificar, mesmo quando mnesia_tm executa novamente a transação devido aos seus motivos , este trecho de código não é executado duas vezes (evito ter vários objetos timer contra o mesmo registro).

Pergunta 1: Qual dessas duas implementações está certa? e / ou errado? Me diga (também) se ambos estão errados

Pergunta 2: O cronômetro do módulo é adequado para lidar com um grande número de cronômetros empregos na produção?

Pergunta 3: em comparação com o timer_mn-1.1 , que roda no topo da mnésia, é o módulo do cronômetro (possivelmente rodando no topo das tabelas Ets) menos capaz (de verdade) na produção? (estou perguntando porque usando o timer_mn de Sean Hinde em um sistema que está usando mnesia aparece ser um problema em termos de mudanças de esquema, problemas de nó e.t.c)

Se alguém tiver outra maneira de lidar com problemas relacionados ao cronômetro com mnésia, me atualize obrigado pessoal ...

Foi útil?

Solução

Pergunta 1:

Controle o cronômetro fora da transação. Quando as transações colidem na Mnesia, elas são simplesmente repetidas. Isso daria a você mais de uma referência de cronômetro e dois acionadores do cronômetro. Não é um problema em si, mas se você esperar até o sucesso da transação antes de instalar o cronômetro, poderá evitar o problema.

A segunda solução é o que eu faria. Se o TX estiver bom, você pode instalar um temporizador nele. Se o cronômetro disparar e não houver referência ao objeto, não importa. Você só deve se preocupar se essa situação acontecer muito, já que você teria um grande número de temporizadores perdidos.

Pergunta 2:

O módulo do cronômetro é legal, mas o guia de desempenho recomenda que você use os BIFs erlang:start_timer, consulte

http://www.erlang.org/doc/efficiency_guide/commoncaveats. html # id58959

Eu introduziria um processo separado como um gen_server que lida com as coisas de tempo. Você envia a ele uma mensagem remove(timer:hours(24), RefNo) e então ele inicia um cronômetro, obtém um TRef e instala um {TRef, RefNo, AuxData} de mapeamento no Mnesia ou no ETS. Quando o cronômetro é acionado, o processo pode gerar um auxiliar removendo a entrada RefNo da tabela principal.

Neste ponto, você deve estar se perguntando sobre travamentos. O gen_server de remoção pode falhar. Além disso, todo o nó pode falhar. Como você deseja reinstalar os temporizadores caso isso aconteça é com você, mas você deve refletir sobre isso acontecer para que possa resolver. Suponha que voltemos e as informações do temporizador sejam carregadas do disco. Como você planeja reinstalar os temporizadores?

Uma maneira é fazer com que AuxData contenha informações sobre o ponto de tempo limite. A cada hora ou 15 minutos, você examina toda a mesa, removendo caras que não deveriam estar lá. Na verdade, você pode optar por esta ser a principal forma de remover estruturas de temporizador. Sim, você dará às pessoas 15 minutos de tempo extra no pior caso, mas pode ser mais fácil de lidar com o código. Pelo menos ele lida melhor com o caso em que o nó (e, portanto, os temporizadores) morre.

Outra opção é trapacear e apenas armazenar temporizações em uma estrutura de dados, o que torna muito barato encontrar todos os RefNos expirados nos últimos 5 minutos e depois executá-los a cada 5 minutos. Fazer coisas em massa provavelmente será mais eficaz. Esse tipo de manipulação em massa é muito usado por kernels de sistema operacional, por exemplo.

Pergunta 3

Não sei nada sobre timer-tm, desculpe :)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top