Сериализация переменной пакета Perl и обновление каждые 24 часа из скрипта CGI - PullRequest
2 голосов
/ 05 июня 2019

Я не слишком опытен с Perl, но я пытаюсь достичь чего-то, что звучит относительно разумно и просто.

Я хочу создать переменную хэша пакета, которая сериализуется где-то и обновляется каждые 24 часа.В основном кеш данных от внешнего сервиса за день.Для проверки я попробовал следующее:

our %hashMap;

sub updateMap {
    my $mapSize = scalar(keys %hashMap);
    if ($mapSize == 0) {
        populateMap();
    }

    return \%hashMap;
}

Я добавил несколько операторов регистрации и вижу, что каждый раз, когда я вызываю updateMap, размер карты всегда равен 0, поэтому он всегда заново заполняет карту.Проблема заключается в том, что это сценарий CGI, поэтому ничего не сохраняется.

Как я могу получить значение карты для привязки между вызовами функций и как я могу обновлять эту карту один раз каждые 24 часа?Один из вариантов, который я имею в виду, - это использование Storable store / retrieve для сохранения хеша в файл и последующего извлечения.Можно ли проверить, когда файл последний раз изменялся в Perl, чтобы определить, прошло ли 24 часа?

Ответы [ 2 ]

2 голосов
/ 05 июня 2019

Здесь есть несколько вопросов о том, как это настроить, а также об обновлении / постоянстве.

Простой и хороший способ организовать это - иметь модуль для вашей «карты» с подпрограммами, которые обеспечивают доступ, обновление, сохранение / загрузку и все остальное, что может быть полезным.

Один из способов поддерживать актуальность данных - проверять каждый раз, когда пользовательский код извлекает «карту» из модуля, например, проверяя временную метку в файле, в котором данные сериализуются. (Другие способы упомянуты в конце.)

Модуль

package MapService;

use warnings;
use strict;
use feature qw(say state);
use Data::Dump qw(dd pp);

use Exporter qw(import);
our @EXPORT_OK = qw(get_map force_update save_to_file);

use Storable qw(nstore retrieve);  # consider locking versions

my $data_file = 'data.storable';

my %map;

my $_populate_map = sub { 
    # use "external service" call to populate (update)
    state $cnt = 1;
    %map = ( a => 1, b => 2, cnt => $cnt++ );
    save_to_file();
};

if (-f $data_file) {                         # initialize
    %map = %{ load_from_file($data_file) };
}
else {
    $_populate_map->();
    save_to_file();
}

my $_update_map = sub {
    my $filename = $_[0] // $data_file;     #/
    if (-M $data_file >= 1)  {              # one+ day old
        $_populate_map->();
        save_to_file(file => $filename);
    }   
};

sub update_map { $_update_map->(@_) };  # outside use, if supported

sub get_map {                           # use this call to check/update
    $_update_map->(@_);
    return \%map;
};

sub save_to_file {
    my %opts = @_; 
    my $file = $opts{file} // $data_file;
    my $map  = $opts{map}  // \%map;
    nstore $map, $file;
}

sub load_from_file {
    my $filename = $_[0] // $data_file;
    return retrieve $filename;
}

sub force_update { $_populate_map->() }   # for tests

1;

с тестовым драйвером

use warnings;
use strict;
use feature 'say'; 
use Data::Dump qw(dd);

use MapService qw(get_map force_update save_to_file);

my $map = get_map();
dd $map;

force_update(); force_update();   # to force more changes in "map"
dd get_map();

save_to_file();  # perhaps in END block

Повторные прогоны и проверка файла данных подтверждают постоянство и манипулирование данными. Небольшие изменения в драйвере также помогают или добавляют подпрограммы для изменения данных по желанию для лучшего тестирования.

Примечания

  • save_to_file часто вызывается для обновления метки времени, для проверок позже в том же прогоне

  • Для краткости в модуле есть несколько угловых и глупых вариантов

  • Лексические кодовые ссылки ($_populate_map и $_update_map) предназначены для внутреннего использования модуля, к которому также может быть предоставлен доступ извне, как с update_map & dagger;

  • Storable - это всегда хороший выбор, но есть и другие варианты. Несомненным недостатком этого модуля является то, что данные должны быть записаны и прочитаны вместе с ним (и даже версии модуля не должны сильно отличаться); Преимущества в том, что он принимает практически любые действительные данные Perl и работает быстро

    В частности, рассмотрим JSON и YAML, широко используемые форматы, которые работают в разных языках (и доступны для чтения). Если ваши данные достаточно просты, я определенно рекомендую эти

  • Нам говорят, что это для устаревшей системы без множества инструментов, возможно, даже не cron

  • Для этого требуется много проверки ошибок и добавлена ​​обработка

  • Рассмотрите возможность использования блокировок для любой работы с сериализованными данными здесь

  • Все это заглушка для более реалистичных аранжировок по мере необходимости

Запрос из заголовка этого вопроса о том, как запускать его один раз в день и хранить данные, рассмотрен выше очень простым способом. Как на самом деле это сделать, зависит от остальной части проекта, и, конечно, есть и другие способы.

Проверка необходимости обновления данных выполняется по мере извлечения данных из модуля с помощью get_map, поэтому между такими вызовами мы могли упустить необходимость обновления; если данные загружаются вызывающей стороной только один раз (при запуске), мы никогда не проверяем во время выполнения.

Одним из способов решения этой проблемы является вычисление оставшегося времени до обновления при запуске программы, затем fork другого процесса и sleep в нем в течение этого времени, затем запуск обновления и отправка сигнала. Затем основной скрипт может обновить свои данные «карты» в обработчике сигналов.

Другим способом было бы установить цикл событий для таймера, но это, вероятно, излишнее (и которое значительно увеличило бы общую сложность).


& dagger; & thinsp; Вопреки мантре о том, что в Perl «нет частных методов», функция, заданная через лексическую (my) ссылку на код, недоступна извне модуля. (Лексические переменные не существуют вне их области видимости.)

Это имеет огромные ограничения, если рассматривать его для системного использования в объектно-ориентированном проектировании, и в этом смысле действительно нет (хороших) частных методов, но возможно иметь (действительно) внутренние функции, и это используется для ограниченных целей.

1 голос
/ 05 июня 2019

Похоже, вам нужен кеш с датой истечения срока действия. Я бы начал с CHI . Поскольку вам нужен кеш, который сохраняется после срока службы вашего процесса, самый простой драйвер для использования - CHI :: Driver :: File . Вот пример кода, который устанавливает срок действия в 1 секунду в кеше и запрашивает его через произвольные интервалы, чтобы показать, что срок действия работает.

use feature 'say';
use strict;
use warnings;

use CHI;

use Data::Dumper;
use Time::Piece;
use Time::HiRes qw[ usleep ];

sub populateMap {
    my $date = localtime->datetime;
    say "Put: $date";
    return { date => $date};
}

sub updateMap {
    my $cache = CHI->new(
                         driver         => 'File',
                         root_dir       => 'cache',
                        );

    my $data = $cache->get( 'key' );
    if ( ! defined $data ) {
        $data = populateMap ();
        $cache->set( 'key', $data, '1 second' );
    }
    return $data;
}

my $data = updateMap();

for (0..10){
    my $sleep = 0.75e6 * rand() + 0.25e6;
    say sprintf "\nSleep: %.2f seconds", $sleep / 1e6;
    usleep $sleep;
    say "Got:", updateMap()->{date};
}

И некоторые выходные данные:

% perl cache.pl
Put: 2019-06-05T09:42:05

Sleep: 0.97 seconds
Put: 2019-06-05T09:42:06
Got:2019-06-05T09:42:06

Sleep: 0.83 seconds
Got:2019-06-05T09:42:06

Sleep: 0.49 seconds
Put: 2019-06-05T09:42:07
Got:2019-06-05T09:42:07

Sleep: 0.88 seconds
Put: 2019-06-05T09:42:08
Got:2019-06-05T09:42:08

Sleep: 0.75 seconds
Put: 2019-06-05T09:42:09
Got:2019-06-05T09:42:09

Sleep: 0.28 seconds
Got:2019-06-05T09:42:09

Sleep: 0.60 seconds
Got:2019-06-05T09:42:09

Sleep: 0.34 seconds
Put: 2019-06-05T09:42:10
Got:2019-06-05T09:42:10

Sleep: 0.80 seconds
Put: 2019-06-05T09:42:11
Got:2019-06-05T09:42:11

Sleep: 0.63 seconds
Got:2019-06-05T09:42:11

Sleep: 0.95 seconds
Put: 2019-06-05T09:42:12
Got:2019-06-05T09:42:12

Примечания:

  • Несмотря на то, что пример представляет собой один процесс, базовый механизм переживет время жизни процесса.
  • CHI предоставляет хранилище key-value , поэтому вам необходимо создать ключ. Я выбрал строку key.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...