>  기사  >  백엔드 개발  >  请教一个生成唯一id的算法是否存在重复?

请教一个生成唯一id的算法是否存在重复?

WBOY
WBOY원래의
2016-06-20 12:41:131430검색

在网上看到一段代码 生成唯一id,有人评论说php的这段代码 生成id时重复性比较大,请教一下大家的看法,

class Idwork{    const debug = 1;    static $workerId;    static $twepoch = 1361775855078;    static $sequence = 0;    const workerIdBits = 4;    static $maxWorkerId = 15;    const sequenceBits = 10;    static $workerIdShift = 10;    static $timestampLeftShift = 14;    static $sequenceMask = 1023;    private static $lastTimestamp = -1;    public function __construct($workId)    {        if ($workId > self::$maxWorkerId || $workId < 0) {            throw new Exception('worker Id can\'t be greater than 15 or less than 0');        }        self::$workerId = $workId;    }    public function timeGen()    {        //获得当前时间戳        $time = explode(' ', microtime());        $time2 = substr($time[0], 2, 3);        $timestramp = $time[1] . $time2;        return $time[1] . $time2;    }    public function tilNextMillis($lastTimestamp)    {        $timestamp = $this->timeGen();        while ($timestamp <= $lastTimestamp) {            $timestamp = $this->timeGen();        }        return $timestamp;    }    public function nextId()    {        $timestamp = $this->timeGen();//1452043798718        if (self::$lastTimestamp == $timestamp) {            self::$sequence = self::$sequence + 1 & self::$sequenceMask;            if (self::$sequence == 0) {                $timestamp = $this->tilNextMillis(self::$lastTimestamp);            }        } else {            self::$sequence = 0;        }        if ($timestamp < self::$lastTimestamp) {            throw new Excwption('Clock moved backwards.  Refusing to generate id for ' . (self::$lastTimestamp - $timestamp) . ' milliseconds');        }        self::$lastTimestamp = $timestamp;        $nextId = sprintf('%.0f', $timestamp) - sprintf('%.0f', self::$twepoch) | self::$workerId << self::$workerIdShift | self::$sequence;        return $nextId;    }}$Idwork = new Idwork(1);$a = $Idwork->nextId();


还有下面这个生成唯一id的方式
function get_order_sn(){    mt_srand((double) microtime() * 1000000);    return date('Ymd') . str_pad(mt_rand(1, 99999), 4, '0', STR_PAD_LEFT);}echo get_order_sn();


这两种方式哪种更好一些,或者还有没有其它的方式在高并发的情况下生成唯一id不重复的方法


回复讨论(解决方案)

请使用PHP内置函数uniqid

参考: http://php.net/manual/zh/function.uniqid.php

echo uniqid(), PHP_EOL;echo uniqid(), PHP_EOL;
568c83e69c671568c83e69c671
function get_order_sn(){    mt_srand((double) microtime() * 1000000);     return date('Ymd') . str_pad(mt_rand(1, 99999), 4, '0', STR_PAD_LEFT);}echo get_order_sn(), PHP_EOL;echo get_order_sn(), PHP_EOL;
201601063753201601063753
显然都不能通过本身的测试

$Idwork = new Idwork(1);echo $Idwork->nextId(), PHP_EOL;echo $Idwork->nextId(), PHP_EOL;$test = new Idwork(1);echo $test->nextId(), PHP_EOL;echo $test->nextId(), PHP_EOL;
可以通过本身的测试
但并发的时候呢?

令你的第一段代码为 Idwork.php,则

$mch = curl_multi_init();for($i=0; $i<4; $i++) {  $ch = curl_init('http://localhost/Idwork.php');  curl_multi_add_handle($mch, $ch);}$running = NULL;do {    usleep ( 10000 );    curl_multi_exec ( $mch, $running );} while ( $running > 0 );
8015005880150059801500588015005980150058801500598015005880150059801500588015005980150058801500598015005880150059
显然是不能通过并发测试的

get_order_sn方法里面
date('YmdHis'),取到秒,重复的概率就比较小了

我给你一种方法,但是位数的问题就看你自己怎么调整了

有一种不限制数字长度的方法可以使用(可以看出年月日十分秒+随机数)(26位数字)

$order_sn = date('YmdHis').substr(time(),-5).substr(microtime(),2,5).rand(10,99);


优点:
1、不用操作数据库,性能较高。
2、较为直观,不难看出订单产生的大致时间
3、订单号重复的概率极小,只有程序在百万分之一秒内同时处理一个以上的生成订单号请求,而且同时生成的10-99的随机数也一样才会出现重复的订单号。

2016010612111453474171874820160106121114534741718783201601061211145347417187832016010612111453474171871920160106121114534741718730201601061211145347417187772016010612111453474171871320160106121114534741718782

依然不能通过并发测试

只有 com_create_guid 生成全局唯一标识符(GUID)
可确切的保证在同一服务器中不会重复
多台服务器间是否会重复,没有测试不能确认

而 GUID 是号称全球唯一的

只有 com_create_guid 生成全局唯一标识符(GUID)
可确切的保证在同一服务器中不会重复
多台服务器间是否会重复,没有测试不能确认

而 GUID 是号称全球唯一的

  谢谢版主的热心回答,但是如果把这个作为订单号的话就太长了,如果以12位的纯数字作为订单号的话  怎么设置订单号才能尽可能的保证唯一性


只有 com_create_guid 生成全局唯一标识符(GUID)
可确切的保证在同一服务器中不会重复
多台服务器间是否会重复,没有测试不能确认

而 GUID 是号称全球唯一的

  谢谢版主的热心回答,但是如果把这个作为订单号的话就太长了,如果以12位的纯数字作为订单号的话  怎么设置订单号才能尽可能的保证唯一性
时间+用户id+商品序号+随机数 

echo uniqid(), PHP_EOL;echo uniqid(), PHP_EOL;
568c83e69c671568c83e69c671
function get_order_sn(){    mt_srand((double) microtime() * 1000000);     return date('Ymd') . str_pad(mt_rand(1, 99999), 4, '0', STR_PAD_LEFT);}echo get_order_sn(), PHP_EOL;echo get_order_sn(), PHP_EOL;
201601063753201601063753
显然都不能通过本身的测试

$Idwork = new Idwork(1);echo $Idwork->nextId(), PHP_EOL;echo $Idwork->nextId(), PHP_EOL;$test = new Idwork(1);echo $test->nextId(), PHP_EOL;echo $test->nextId(), PHP_EOL;
可以通过本身的测试
但并发的时候呢?



echo uniqid(), PHP_EOL;echo uniqid(), PHP_EOL;function get_order_sn(){    mt_srand((double) microtime() * 1000000);      return date('Ymd') . str_pad(mt_rand(1, 99999), 4, '0', STR_PAD_LEFT);}echo get_order_sn(), PHP_EOL;echo get_order_sn(), PHP_EOL;

这两种生成的方式   本身无法测试通过 是通过什么方式测试的  是指循环输出吗?



只有 com_create_guid 生成全局唯一标识符(GUID)
可确切的保证在同一服务器中不会重复
多台服务器间是否会重复,没有测试不能确认

而 GUID 是号称全球唯一的

  谢谢版主的热心回答,但是如果把这个作为订单号的话就太长了,如果以12位的纯数字作为订单号的话  怎么设置订单号才能尽可能的保证唯一性
时间+用户id+商品序号+随机数 
这个方法是不错,但是不能保证生成的位数  这个订单号会随着用户的id的增长 越来越大




只有 com_create_guid 生成全局唯一标识符(GUID)
可确切的保证在同一服务器中不会重复
多台服务器间是否会重复,没有测试不能确认

而 GUID 是号称全球唯一的

  谢谢版主的热心回答,但是如果把这个作为订单号的话就太长了,如果以12位的纯数字作为订单号的话  怎么设置订单号才能尽可能的保证唯一性
时间+用户id+商品序号+随机数 
这个方法是不错,但是不能保证生成的位数  这个订单号会随着用户的id的增长 越来越大
本身你定长的数字 也是有上限的。。。 

在一次运行中产生 2 个 id,至少应保证这两个 id 应是不同的
如果连这个都不能保证,那如何能保证两个请求得到的 id 是不同的呢?

你的 Idwork 类可以保证在一次程序运行中不出现重复,但在多次运行中仍会出现重复

com_create_guid 确实很长,但正因为如此,才能保证唯一

从常理可知,要想得到一个不曾出现过的 id,那就要检查他不在已出现过的 id 序列之中
而这个 已出现过的 id 序列 要保存在一个公共的地方,还要防止共享冲突

所以最佳的选择是利用数据库的自增字段

   感谢大家的热心回答  又学到了很多知识

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.