项目是一个高并发的web项目,并且会有后台进程(pcntl并发),两者都会利用uniqid去生成唯一id,今天发现一个bug,在高并发情况下,uniqid可能产生重复输出。以下是测试代码:<?php
function new_child($func_name) {
$args = func_get_args();
unset($args[0]);
$pid = pcntl_fork();
if($pid == 0) {
function_exists($func_name) and exit(call_user_func_array($func_name, $args)) or exit(-1);
} else if($pid == -1) {
echo "Couldn’t create child process.";
} else {
return $pid;
}
}
function generate() {
$t = array();
while($i ++ < 10) {
$uid = uniqid(true)."\n";
array_push($t, $uid);
}
sort($t);
while(-- $i >=0) {
echo array_shift($t);
}
}
while($i ++ < 1000) {
new_child(generate);
}
?>
测试方法: 命令行运行此程序,重定向输出到文件,然后利用下面程序检查重复:<?php
$f = file("tttttt");
$f = array_count_values($f);
foreach($f as $k => $c) if($c > 1) echo $c.'_'.$k;
?>解决方法: 我们现在是在uniqid后又加了rand(1, 10000),在1000并发,每进程10次uniqid的情况下,再没有产生重复。
function new_child($func_name) {
$args = func_get_args();
unset($args[0]);
$pid = pcntl_fork();
if($pid == 0) {
function_exists($func_name) and exit(call_user_func_array($func_name, $args)) or exit(-1);
} else if($pid == -1) {
echo "Couldn’t create child process.";
} else {
return $pid;
}
}
function generate() {
$t = array();
while($i ++ < 10) {
$uid = uniqid(true)."\n";
array_push($t, $uid);
}
sort($t);
while(-- $i >=0) {
echo array_shift($t);
}
}
while($i ++ < 1000) {
new_child(generate);
}
?>
测试方法: 命令行运行此程序,重定向输出到文件,然后利用下面程序检查重复:<?php
$f = file("tttttt");
$f = array_count_values($f);
foreach($f as $k => $c) if($c > 1) echo $c.'_'.$k;
?>解决方法: 我们现在是在uniqid后又加了rand(1, 10000),在1000并发,每进程10次uniqid的情况下,再没有产生重复。
========================================
这也只是降低了碰撞几率吧,呵呵
我记得uniqid(mt_rand(), true),单CPU主机,能保证不重复吧。
以前写数据库模拟session机制,构造session_id就用了这个
呵呵,是的,降低碰撞几率大家有没有什么更好的方法,我这边进程并发量挺高的。另外,我是用pcntl做的并发控制,过程中会大量产生子进程,我通过父进程记录子进程pid,隔一段时间清理一次的方式防止僵尸进程,不知道这方面有没有更好的解决方法。。谢谢。
因为 uniqid 产生的是基于系统时间(微秒)的随机数,所以若你的机器太快,就可能出现重码
解决的方法应不是加随机数,而是加定长的顺号
谢谢,第一个参数设置为true无意义,这个我没查过
,来项目里这样用,我也就想当然的用了14楼:
我这边是多进程控制的,所以顺序号是不好加,要加又得共享存储空间来解决。10楼:
你说的rand 500多次就会重复,我这边是多进程环境,每个进程里面uniqid+rand顶多不超过5次,不知道会不会有同样的问题?谢谢各位。
子进程里面是调用了已有的业务处理
而uniqid的调用是经过了好多层调用的。。
并且,这些业务处理并不只是在这里的子进程使用,因为我们的应用是web, 但是有独立进程来处理离线业务顺序号难加的原因:
cli因为有一个while(true)的主控进程,可以加,但是,php 在web中怎么来保持状态?
uniqid是基于microtime的?可以讲解一下其中原理吗?谢谢。
uniqid() returns a prefixed unique identifier based on the current time in microseconds.
基于 mircotime 来构造出来的。
$ar[rand(1, 1000)]++;
$p = array_filter($ar, 'foo');
echo count($p) . PHP_EOL;
echo array_sum($p) . PHP_EOL;
print_r($p);function foo($v) {
return $v>1;
}
可知,随机数的重复概率是相当高的
<?php
for($i=0; $i<10; $i++)
$ar[rand(1, 100000)]++;
$p = array_filter($ar, 'foo');
echo count($p) . PHP_EOL;
echo array_sum($p) . PHP_EOL;
print_r($p);function foo($v) {
return $v>1;
}
?>
//运行一下,你会发现,两行输出,规则就差前面的1,是因为前缀,true====>1,结果通常也是最后一位不同
echo uniqid(true);
$time = gettimeofday();
echo '<br />';
echo dechex($time['sec']) . dechex($time['usec']);
而php构造uniqid函数返回字符串的核心语句如下
gettimeofday((struct timeval *) &tv, (struct timezone *) NULL);
sec = (int) tv.tv_sec;
usec = (int) (tv.tv_usec % 0x100000); if (more_entropy) {
spprintf(&uniqid, 0, "%s%08x%05x%.8F", prefix, sec, usec, php_combined_lcg(TSRMLS_C) * 10);
} else {
spprintf(&uniqid, 0, "%s%08x%05x", prefix, sec, usec);
}