ByProduct - 副産物

IT FukuSanButsu Blog

社内インフラエンジニアの自宅からはじまるIT
自宅のPCに向き合いながら気づいたことや個人的な知見をまとめています


プロフィール
しらせ(HN)
とあるIT企業のインフラエンジニア。プライベートでは開発もちょっとやります。
※本ブログの内容はすべて個人の見解であり、所属する企業とは関連ありません。
2023/09/30 暫く更新停止中m
プロフィールを読む
カテゴリ別
内部リンク
相互リンク
Twitter
来訪
1069867 [合計]
8 [今日]
382 [昨日]
Powered by
Powered by AWS Cloud Computing

【備忘】phpでアクセスカウンターを作った時に詰まった話

2021/06/30
2021/06/30

コーディング


お疲れ様です。
しらせです。

今更ですが、2019年の2月にphpでブログのアクセスカウンターを実装した時のつまりポイントをまとめておきます。
なぜかある日突然カウントがゼロに戻るという事象になります。

割とビギナーが陥りやすいメジャーな事象なんですかね?
自分で言うのも難ですが昔から車輪の再発明ばっかりやってます。

もくじ

当初の実装

まずは何も知らないで実装した問題のあるパターンです。

一見するとカウンター用ファイルを読み込んで表示した後で、インクリメントしてファイルに書き戻すという単純な処理ですが、
これを各ページに埋め込んでしまうと、悲しいことにある日突然カウントがゼロに戻るんです。

$blog_access = trim(file_get_contents('/data/access.txt'));
echo $blog_access;
$blog_access += 1;
file_put_contents('/data/access.txt',$blog_access);

原因も分からずにとりあえずデイリーでバックアップするcronを書いて、
事象が起きたら手動で書き戻すという辛い作業をしていました。

原因

phpに詳しい方ならすぐに分かるんだと思いますが、
私は詳しくないので数か月悩んで調べて解決に至りました。

問題なのは1行目のfile_get_contentsになります。

これは、カウンターを設置しているページに同時アクセスがあった場合に発生します。
公式マニュアルにも丁寧に記載があります。

It's important to understand that LOCK_EX will not prevent reading the file unless you also explicitly acquire a read lock (shared locked) with the PHP 'flock' function.

i.e. in concurrent scenarios file_get_contents may return empty if you don't wrap it like this:

<?php
$myfile=fopen('test.txt','rt');
flock($myfile,LOCK_SH);
$read=file_get_contents('test.txt');
fclose($myfile);
?>

If you have code that does a file_get_contents on a file, changes the string, then re-saves using file_put_contents, you better be sure to do this correctly or your file will randomly wipe itself out.

(参考)https://www.php.net/manual/ja/function.file-put-contents.php

ちゃんとflock使わないとランダムにファイルがリセットされると。。。

修正版

そして修正版のコードがこちらです。

これでもなかなか無理やりに見えますよね。
文字量も多いのであまりスマートな書き方ではないと思いますが。。

$fp = fopen('/data/access.txt','r+');
if($fp && flock($fp, LOCK_EX)){
$counter = (int)trim(fgets($fp))+1; #インクリメント
rewind($fp); #ファイルポインタを先頭に
ftruncate($fp, 0); #ファイルを初期化
fputs($fp, $counter); #出力
fclose($fp); #ファイルポインタクローズ
}

重要なのが2行目でファイルポインタを開いた後にロックをします。
こうすることで、同時アクセスの際にも排他ロックを掛けて後発のアクセスを待ち状態にさせます。

その後、ファイルを数値で取得してインクリメントした後でまたファイルポインターの先頭から書き出します。

ちなみに公式ではロックにLOCK_SHを使っているみたいなんですが共有ロックだと別の問題があります。
処理によっては同じタイミングで参照した場合に後発のアクセスがカウントされないケースがあるんです。

「排他ロックで他のセッションを待たせる方が問題では?」

まぁそうなんですが、
カウントの処理なんてほんの数msですし、
アクセスの少ないウェブサイトにはちょうど良いのですw

待ち時間が長くなるくらいアクセス増えたら考えます。

以上、
おつかれさまでした。



View:1477 この記事をツイート!