前回Redisを導入しましたが、Sessionの部分だけ使ってみたが、本体の方のMySQLに依存する部分もRedis化すべきなのでそちらも変更する。
Redis利用における仕様
ログイン失敗の管理をRedisで管理する
- keyを user_[userid] としてvalueは失敗毎に数値をインクリメント
- ログイン成功時にkeyを削除する
ログイン失敗時のIPの管理をRedisで管理する
- keyを ip_[ip] としてvalueは失敗毎に数値をインクリメント
- ログイン成功時にkeyを削除する
もともとMySQL管理のlogin_logは成功時のみのログを記録する
(ログイン後のページの前回ログイン成功の表示を取り出すのにはMySQLが都合がいいので)
追加で要実施の作業
ベンチマークスタート時に 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化
login_logは成功時のみ書き込むように修正
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)
);
}
};
banのチェックの所をRedisでやる
長ったらしい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
};
banされたIPリスト Userリストを取る部分もRedisで
こちらも複雑な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;
}
benchmark走らせる
$ ./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
35739点. ヒカリエまであと2000点足りない。。。
あと何ができるかもうちょっと考えて見る。