项目是一个高并发的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的情况下,再没有产生重复。

解决方案 »

  1.   

    解决方法: 我们现在是在uniqid后又加了rand(1, 10000),在1000并发,每进程10次uniqid的情况下,再没有产生重复。
    ========================================
    这也只是降低了碰撞几率吧,呵呵
      

  2.   

    uniqid 第一个参数是前缀,所以true没什么意义
    我记得uniqid(mt_rand(), true),单CPU主机,能保证不重复吧。
    以前写数据库模拟session机制,构造session_id就用了这个
      

  3.   


    呵呵,是的,降低碰撞几率大家有没有什么更好的方法,我这边进程并发量挺高的。另外,我是用pcntl做的并发控制,过程中会大量产生子进程,我通过父进程记录子进程pid,隔一段时间清理一次的方式防止僵尸进程,不知道这方面有没有更好的解决方法。。谢谢。
      

  4.   

    rand在运算400多次以后就会出现重复
      

  5.   

    unquie+现在时间(date('YmdHis', time()))+rand
      

  6.   

    只要超过一定的数rand以后生成的全是重复的没有例外,前一阵子有个通知发个帖子提问过这个问题,如果单纯的靠rand生成大量的数据那么注定会在一段数据之后全变成重复,如果你生成10000个数,那么就前几百个是正常的,后面的全不能用,还小么感觉楼主尽量将多种生成随机数据的方法连用,时间戳不过,或者楼主将随机数转成asscii,然后unquie+时间戳+随机其他的应该就能将重复的数据降到很低了
      

  7.   

    出现你说得情况的原因是你的机器太快了
    因为 uniqid 产生的是基于系统时间(微秒)的随机数,所以若你的机器太快,就可能出现重码
    解决的方法应不是加随机数,而是加定长的顺号
      

  8.   

    uniqid(true)楼上说了,第一个参数是前缀,设成true无意义,另外,第二个参数设成true,产生23位长串,比不设该参数结果要更唯一
      

  9.   

    15楼:
    谢谢,第一个参数设置为true无意义,这个我没查过
    ,来项目里这样用,我也就想当然的用了14楼:
    我这边是多进程控制的,所以顺序号是不好加,要加又得共享存储空间来解决。10楼:
    你说的rand 500多次就会重复,我这边是多进程环境,每个进程里面uniqid+rand顶多不超过5次,不知道会不会有同样的问题?谢谢各位。
      

  10.   

    既然都 function_exists 了,还说什么顺序号不好加?
      

  11.   


    子进程里面是调用了已有的业务处理
    而uniqid的调用是经过了好多层调用的。。
    并且,这些业务处理并不只是在这里的子进程使用,因为我们的应用是web, 但是有独立进程来处理离线业务顺序号难加的原因:
    cli因为有一个while(true)的主控进程,可以加,但是,php 在web中怎么来保持状态?
      

  12.   

    rand有可能重复,uniqid也有可能重复,但uniqid是基于mircotime的。两者加起来,基本不会重复,或者说机率很低微。
      

  13.   


    uniqid是基于microtime的?可以讲解一下其中原理吗?谢谢。
      

  14.   

    原理就不会了,菜鸟一个。但手册上是这样写的:
    uniqid() returns a prefixed unique identifier based on the current time in microseconds.
    基于 mircotime 来构造出来的。
      

  15.   

    观察如下代码for($i=0; $i<1000; $i++)
      $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;
    }
    可知,随机数的重复概率是相当高的
      

  16.   

    回22楼,谢谢。不过我们的应用场景是下面这样,所以加个rand目前是最方便的做法。
    <?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;
    }   
    ?>
      

  17.   


    //运行一下,你会发现,两行输出,规则就差前面的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);
    }
      

  18.   

    用uuid 16字节类似:550e8400-e29b-41d4-a716-446655440000
      

  19.   

    参考:http://zh.wikipedia.org/zh/UUID