序列化Perl软件包变量,并每24小时从CGI脚本更新一次

时间:2019-06-04 23:30:43

标签: perl hash

我对Perl不太了解,但是我正在尝试实现听起来相对合理和简单的东西。

我想创建一个打包变量哈希值,该哈希值在某处序列化并每24小时更新一次。基本上是一天中来自外部服务的数据缓存。我尝试了以下测试:

our %hashMap;

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

    return \%hashMap;
}

我添加了一些日志记录语句,看到每次调用updateMap时,地图大小始终为0,因此它始终会重新填充地图。问题在于,这是一个CGI脚本,因此没有任何持久性。

如何获取映射值以保留在函数调用之间,如何每24小时更新一次此映射?我想到的一个选项是使用可存储的存储/检索将哈希保存到文件中,然后再检索。是否可以检查文件在Perl中的最后修改时间,以确定是否已过去24小时?

2 个答案:

答案 0 :(得分:2)

这里有一些问题,关于如何设置以及更新/持久性。

一种简单而有效的组织方法是为您的“地图”提供一个模块,其子菜单提供访问,更新,保存/加载以及其他有用的功能。

一种使数据保持最新的方法是检查用户代码每次从模块中检索“映射”的时间,例如,通过检查序列化了数据的文件上的时间戳。 (最后提到了其他方法。)

模块

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一样从外部进行访问,†< / sup>

  • Storable始终是可靠的选择,但还有其他选择。该模块的一个明显的缺点是必须同时写入和读取数据(甚至模块版本也应该相差不大)。优点是它几乎可以获取任何有效的Perl数据,而且速度快

    尤其要考虑JSONYAML,它们是跨语言工作(并且可读)的广泛使用的格式。如果您的数据足够简单,我肯定会推荐这些

  • 我们被告知这是针对没有许多工具的遗留系统,甚至没有cron

  • 这需要添加大量错误检查和处理

  • 请考虑在此处使用锁进行序列化数据的任何处理

  • 所有这些都是根据需要进行更实际安排的存根

上面以非常简单的方式解决了该问题标题中有关如何每天运行一次并保存数据的查询。实际执行的方式取决于项目的其余部分,当然还有其他方法。

当数据被get_map从模块中拉出时,检查是否需要更新数据,因此在两次调用之间,我们可能错过了更新的需要;如果调用方只加载了一次数据(在启动时),我们就不会在运行过程中进行检查。

解决此问题的一种方法是计算程序开始更新之前的剩余时间,然后fork另一个进程,并在该持续时间内sleep进行更新,然后运行更新并发送信号。然后,主脚本可以在信号处理程序中更新其“地图”数据。

另一种方法是为计时器设置一个事件循环,但这可能是一个过大的选择(这会大大提高整体复杂性)。


与Perl中“没有私有方法”的说法相反,无法从模块外部访问通过词法(my)代码引用给出的函数。 (词汇​​变量不在其范围之外。)

如果考虑将其用于面向对象设计中的系统,则存在巨大的局限性,从这个意义上说,确实没有(好的)私有方法,但是有可能具有(真正的)内部功能,并且这被用于受限的目的。 / p>

答案 1 :(得分:1)

您喜欢的声音需要具有到期日期的缓存。我将从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