尝试解决ISUCON4的预选赛问题。(第3部分)

这样1
这样2

上次我们引入了Redis,但只是尝试了Session的部分,现在也应该将与主体的MySQL有关的部分也改为Redis。

Redis在使用中的规范

使用Redis来管理登陆失败的处理

    • keyを user_[userid] としてvalueは失敗毎に数値をインクリメント

 

    ログイン成功時にkeyを削除する

使用Redis来管理登录失败时的IP

    • keyを ip_[ip] としてvalueは失敗毎に数値をインクリメント

 

    ログイン成功時にkeyを削除する

由于MySQL适合提取登录后页面上上次成功登录的显示,因此原本MySQL管理的login_log仅记录成功时的日志。

需要进行额外的工作

当启动基准测试时,会运行init.sh脚本进行MySQL的初始化和初始数据的导入,但同时需要预先将存储在login_log(dummy_log.sql)中的数据,按照上述规格,保存到Redis中。将初始状态的login_log数据导出为TSV文件,并将其读入以下的perl程序,以创建Redis的初始状态。

# sql/set_initlog_to_redis.pl
use strict;
use warnings;
use Data::Dumper;
use Redis;

my $redis = Redis->new();

while(<STDIN>) {
    chomp;
    my @item = split "\t";

    if ($item[5] eq 1) {
        $redis->del("user_". $item[3]);
        $redis->del("ip_". $item[4]);
    } else {
        $redis->incr("user_". $item[3]);
        $redis->incr("ip_". $item[4]);
    }
}

将以下内容添加到init.sh中,以便在启动基准测试时执行。

# init.sh
...
redis-cli KEYS "*" | xargs redis-cli DEL
cat sql/dump_login_log.tsv | perl sql/set_initlog_to_redis.pl

修改 web.pm

进行Redis初始化

sub redis {
  my ($self) = @_;

  $self->{_redis} ||= do {
    Redis->new();
  };
};

将MySQL中的login_log记录转换为Redis记载。

将登录日志仅在成功时进行修改并进行写入。

sub login_log {
  my ($self, $succeeded, $login, $ip, $user_id) = @_;

  if($succeeded) {
    $self->redis->del("user_". $login);
    $self->redis->del("ip_". $ip);
  } else {
    $self->redis->incr("user_". $login);
    $self->redis->incr("ip_". $ip);
  }

  if ($succeeded) {
    $self->db->query(
      'INSERT INTO login_log (`created_at`, `user_id`, `login`, `ip`, `succeeded`) VALUES (NOW(),?,?,?,?)',
      $user_id, $login, $ip, 1)
    );
  }
};

使用Redis进行ban检查

之前用冗长的SQL做检查,现在变得简单了。

sub user_locked {
  my ($self, $user) = @_;

  my $fauluers = $self->redis->get("user_".$user->{login});
  $fauluers ? $self->config->{user_lock_threshold} <= $fauluers : 0
};

sub ip_banned {
  my ($self, $ip) = @_;

  my $fauluers = $self->redis->get("ip_".$ip);
  $fauluers ? $self->config->{ip_ban_threshold} <= $fauluers : 0
};

使用Redis来获取被禁止的IP列表和用户列表。

将复杂的SQL简化为使用Redis的keys方法。

sub banned_ips {
  my ($self) = @_;
  my @ips;
  my $threshold = $self->config->{ip_ban_threshold};

  for my $key ($self->redis->keys("ip_*")) {
    if($self->redis->get($key) >= $threshold) {
       push @ips, substr($key, 3)
    }
  }

  \@ips;
};

sub locked_users {
  my ($self) = @_;
  my @user_ids;

  my $threshold = $self->config->{user_lock_threshold};

  for my $key ($self->redis->keys("user_*")) {
    if($self->redis->get($key) >= $threshold) {
       push @user_ids, substr($key, 5)
    }
  }

  \@user_ids;
}

启动基准测试

$ ./benchmarker bench --workload 10
15:27:05 type:info      message:launch benchmarker
15:27:05 type:warning   message:Result not sent to server because API key is not set
15:27:05 type:info      message:init environment
15:27:22 type:info      message:run benchmark workload: 10
15:28:22 type:info      message:finish benchmark workload: 10
15:28:27 type:info      message:check banned ips and locked users report
15:28:29 type:report    count:banned ips        value:638
15:28:29 type:report    count:locked users      value:4083
15:28:30 type:info      message:Result not sent to server because API key is not set
15:28:30 type:score     success:155490  fail:0  score:33591

删除在ReverseProxy侧记录访问日志的部分,然后重新尝试。

$ ./benchmarker bench --workload 10
15:31:47 type:info      message:launch benchmarker
15:31:47 type:warning   message:Result not sent to server because API key is not set
15:31:47 type:info      message:init environment
15:32:04 type:info      message:run benchmark workload: 10
15:33:04 type:info      message:finish benchmark workload: 10
15:33:09 type:info      message:check banned ips and locked users report
15:33:11 type:report    count:banned ips        value:645
15:33:11 type:report    count:locked users      value:4124
15:33:12 type:info      message:Result not sent to server because API key is not set
15:33:12 type:score     success:165440  fail:0  score:35739

距离光之塔还差2000点,总计为35739点。

再多想一下还能做什么。

请点击这里获取后续内容。

广告
将在 10 秒后关闭
bannerAds