尝试解决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点。
再多想一下还能做什么。
请点击这里获取后续内容。