所以我有一个脚本用于接受和处理来自其他脚本和/或应用程序的请求。然而,我的脚本必须完成的任务之一是为每个请求分配一个唯一的、连续的“ID”。
例如,假设应用程序 A 向我的脚本发出 1000 个请求,同时应用程序 B 向我的脚本发出 500 个请求。我必须给他们 1500 个唯一的连续编号,例如每个人 2001~3500。
但是,它们之间的顺序并不重要,所以我可以给它们这样的数字:
#2001 for 1st request from A (henceforth, A1)
#2002 for A2
#2003 for B1
#2004 for A3
#2005 for B2
...and so on...
我尝试创建一个存储该号码的文件和一个具有如下功能的单独的锁定文件:
private function get_last_id()
{
// Check if lock file exists...
while (file_exists("LAST_ID_LOCKED")) {
// Wait a little bit before checking again
usleep(1000);
}
// Create the lock file
touch("LAST_ID_LOCKED");
// Create the ID file for the first time if required
if (!file_exists("LAST_ID_INDICATOR")) {
file_put_contents("LAST_ID_INDICATOR", 0);
}
// Get the last ID
$last_id = file_get_contents("LAST_ID_INDICATOR");
// Update the last ID
file_put_contents("LAST_ID_INDICATOR", $last_id + 1);
// Delete the lock file
unlink("LAST_ID_LOCKED");
return $last_id;
}
然而,这段代码会产生竞争条件,如果我向他们发送 1500 个请求,最后一个 ID 将有相当多的缺失(例如,仅达到 3211 而不是 3500)。
我也尝试过像这样使用集群,但没有成功:
private function get_last_id()
{
$f = fopen("LAST_ID_INDICATOR", "rw");
while (true) {
if (flock($f, LOCK_SH)) {
$last_id = fread($f, 8192);
flock($f, LOCK_UN);
fclose($f);
break;
}
usleep($this->config["waiting_time"]);
}
$f = fopen("LAST_ID_INDICATOR", "rw");
while (true) {
if (flock($f, LOCK_SH)) {
$last_id = fread($f, 8192);
$last_id++;
ftruncate($f, 0);
fwrite($f, $last_id);
flock($f, LOCK_UN);
fclose($f);
break;
}
usleep($this->config["waiting_time"]);
}
return $last_id;
}
那么,我还能做些什么来寻找针对这种情况的解决方案?
注意事项:由于服务器限制,我仅限于 PHP 5.2,没有信号量之类的东西。
由于似乎没有人给出答案,我会给你一个可能的解决方案。
使用 Lamport 烘焙算法 作为解决方案的一部分。
编辑:如果您不需要保留订单,过滤器锁会工作得更好。
显然,这在实施时会遇到一些挑战,但值得一试,如果你做对了,它可能会实现你想做的事情。
既然您提到了信号量,我假设您已经了解了足够的知识来理解这个概念。
这可以在“多处理器编程的艺术”的第二章中找到。
如果您有权访问具有锁定功能的数据库,则可以使用它。例如。对于具有 PHP 框架代码的 MySQL:
创建一个一行一列的表格(如果您不想“双重使用”现有表格):
$sql = 'CREATE TABLE `TABLENAME` (COLUMNNAME INTEGER) ENGINE=MyISAM';
excecuteSql($sql) ...
创建一个 PHP 函数来(重新)设置计数器/Id 值:
$sql = 'UPDATE `TABLENAME` SET `COLUMNNAME`=0';
executeSql($sql); ...
创建一个 PHP 函数来获取唯一的、连续的 id:
$sql = "SELECT GET_LOCK('numberLock',10)";
executeSql($sql); ...
$sql = 'SELECT * FROM `TABLENAME`';
if ($result = mysqli_query($link, $sql)) {
$row = mysqli_fetch_row($result);
$wantedId = $row[0];
// do something with the id ...
mysqli_free_result($result);
}
$sql = 'UPDATE `TABLENAME` SET `COLUMNNAME`=`COLUMNNAME`+1';
executeSql($sql); ...
$sql = "SELECT RELEASE_LOCK('numberLock')";
executeSql($sql); ...