我需要打开一个日志文件进行写入。 问题是,很多事情可能会同时这样做,我不希望冲突。 每次写入将是一行,通常约为 150 字节(并且始终小于 1K),并且不严格要求按时间顺序获取内容。
我想我想要的是尝试flock()
,如果失败了,请继续尝试几秒钟。 如果多次尝试后仍无法建立锁定,请放弃。
$fh=fopen($logfile, "a");
if (flock($fh, LOCK_EX|LOCK_NB)) {
$locked=TRUE;
} else {
$locked=FALSE;
// Retry lock every 0.1 seconds for 3 seconds...
$x=0; while($x++ < 30) {
usleep(100000);
if (flock($fh, LOCK_EX|LOCK_NB)) {
$locked=TRUE;
break;
}
}
}
if ($locked) {
if (fwrite($fh, strftime("[%Y-%m-%d %T] ") . $logdata . "'n")) {
print "Success.'n";
} else {
print "Fail.'n";
}
flock($fh, LOCK_UN)
} else {
print "Lock failed.'n";
}
我有两个问题,一个是一般性问题,一个是具体问题。 首先,除了以不同的方式(do...while
等)实现相同的解决方案之外,是否有更好的通用策略来处理此类仅在PHP中运行的问题? 其次,有没有更好的方法在PHP中实现这一点? (是的,我把它们分开了,因为我对策略部分真的很感兴趣。
我考虑过的一种选择是使用 syslog(),但 PHP 代码可能需要在系统级管理(即向/etc/syslog.conf 添加内容)可能无法作为选项的平台上运行。
更新:根据兰迪的建议,在上面的代码中添加了|LOCK_NB
。
根据我在 PHP 制作日志方面的长期经验(在 Linux 下!我从未遇到过冲突问题(即使有数百个同时写入和并发写入)。如此简单的跳过锁定管理:
$fh=fopen($logfile, "a");
if (fwrite($fh, strftime("[%Y-%m-%d %T] ") . $logdata . "'n")) {
print "Success.'n";
} else {
print "Fail.'n";
}
fclose($fh);
关于此策略,文件日志记录(带或不带锁)不是最佳解决方案,因为每个带有"a"的 fopen 都意味着一个搜索系统调用,用于在文件末尾设置光标。保持文件打开的系统日志可避免此开销。
当然,对于"大"文件,开销变得很重要(在性能上),一个简单的解决方案是创建名称中包含日期(或日期时间)的日志文件。
加
Apache 软件包包含一个测试程序:AB,允许并发查询,您可以测试我的论文,强调您的服务器由 100000 个线程完成 1000000 个查询。
添加 - 在评论之后
不,这不是一项不可能完成的任务。
我找到了一张来自 http://php.net/manual/en/function.fwrite.php 的便笺
如果句柄在追加模式下是 fopen()ed,则 fwrite() 是原子的(除非 字符串的大小超过了文件系统的块大小,在某些 平台,只要文件位于本地文件系统上)。那是 在调用 fwrite() 之前不需要 flock() 资源;全部 数据将不间断地写入。
要知道一个块有多大(以字节为单位)(通常为 4K):
写入的"dumpe2fs/dev/sd_your_disk_partition |less -i
原子性"是实现的,阻止其他"代理"写入(当你在"ps ax"中看到进程处于"D"状态时),但是PHP流工具可以解决这个问题,参见:*stream_set_blocking*。此方法可能会引入部分写入,因此您必须验证记录的完整性。
在任何情况下,无论使用鸡群,fwrite(网络或文件)都容易受到阻塞/故障的影响。恕我直言,羊群只引入开销。
关于您最初的问题和您的目标(尝试在高度规避风险的环境中实施公司政策),即使是 fwrite 也可能出现问题,我只能想象一个简单的解决方案:使用 DB
- 大部分复杂性都来自 PHP,是用 C 编写的!
- 完全控制操作流程
- 高并发级别
这是我最近在 https://CodeReview.stackexchange.com/questions/283930/php-flock-with-timeout-for-lock-sh-lock-ex 发布的flockWithTimeout的实现
function flockWithTimeout($handle, int $flags, int &$would_block = null, float $timeout_seconds = 10, float $sleep_time_seconds = 0.01): bool
{
if($flags !== LOCK_UN) {
$flags = $flags | LOCK_NB;
}
$would_block = null;
$timeout_timestamp = microtime(true) + $timeout_seconds;
$sleep_time_microseconds = (int)($sleep_time_seconds * 1000000);
for (;;) {
$success = flock($handle, $flags, $would_block);
if ($success) {
return true;
}
if (!$would_block) {
// something else is wrong, like flocking on FAT32 which doesn't support flock?
return false;
}
// another process has the lock
if (microtime(true) >= $timeout_timestamp) {
return false;
}
usleep($sleep_time_microseconds);
}
throw new 'LogicException('unreachable');
}
示例用法:
$h = tmpfile();
$h2 = fopen(stream_get_meta_data($h)['uri'], "rb");
if(flockWithTimeout($h, LOCK_EX)){
echo "got the lock! :)'n";
} else {
echo "did not get the lock :('n";
}
$timeout_seconds = 0.1;
if(flockWithTimeout($h2, LOCK_SH, $would_block, $timeout_seconds)){
echo "got the lock! :)'n";
} else {
echo "did not get the lock :('n";
}
应该打印
got the lock! :)
did not get the lock :(
did not get the lock :(
大约需要 100 毫秒