在软件系统中,唯一标识符是非常重要的。一个唯一标识符是一个用来标识一个实体或对象的字符串或数字。比如,我们的用户ID、订单号等都需要是唯一的。在本文中,我们将探讨一些关于生成唯一标识符的最佳实践。
一、UUID
UUID(通用唯一标识符)是一个用于标识信息的128位数字,它的唯一性和随机性很高。它不需要像序列号那样在多台计算机之间进行同步,也不需要像GUID那样需要Windows API的支持。因此,它是一种生成唯一标识符的很好选择。
<?php
function generateUUID() {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
?>
这个generateUUID函数生成一个UUID。注意,它使用了mt_rand,这是一个更好的随机数生成器。如果你在旧的PHP版本中使用rand,你可能会得到两个完全相同的UUID。
二、Snowflake算法
Snowflake是由Twitter发明的算法,它可以在分布式系统中生成唯一的ID,是生成分布式唯一ID的一种解决方案。它的核心思想是将ID分解成多个部分,通过对每个部分的合理设计来保证生成的所有ID全局唯一。Snowflake IDs由以下几个部分组成:
- 一个时间戳(毫秒级,41位)
- 一个机器ID(10位)
- 一个序列号(12位)
<?php
class Snowflake
{
const EPOCH = 1593649964177;
const MACHINE_ID = 1;
private static $lastTimestamp = -1;
private static $sequence = 0;
public static function generateId()
{
$timestamp = self::getUnixTimestampInMilliseconds();
if (self::$lastTimestamp === $timestamp) {
self::$sequence = (self::$sequence + 1) & 4095;
if (self::$sequence === 0) {
$timestamp = self::waitNextMillis($timestamp);
}
} else {
self::$sequence = 0;
}
$lastTimestamp = $timestamp;
return ((string) ($timestamp - self::EPOCH) << 22)
| ((string) (self::MACHINE_ID << 12))
| (string) self::$sequence;
}
private static function getUnixTimestampInMilliseconds()
{
return round(microtime(true) * 1000);
}
private static function waitNextMillis($timestamp)
{
while ($timestamp === self::$lastTimestamp) {
$timestamp = self::getUnixTimestampInMilliseconds();
}
return $timestamp;
}
}
?>
如上述代码,我们可以用Snowflake生成全局唯一标识符,可以调用Snowflake::generateId()函数来生成唯一 ID。
三、利用数据库
使用数据库自增长列的思路是最传统的方式生成QQ号、订单号等唯一编码,利用auto_increment列可以生成唯一ID。然而,使用数据库有一个明显的缺点,那就是需要和数据库进行交互,如果高并发,可能会占用数据库大量的资源。
<?php
class MysqlIdMaker
{
private $db;
public function __construct(mysqli $db)
{
$this->db = $db;
}
public function nextId()
{
$result = $this->db->query('SELECT next_id FROM sequence WHERE `key`=\'global\'');
if (!$result || $result->num_rows < 1) {
throw new Exception('Sequence not found');
}
$row = $result->fetch_assoc();
$nextId = $row['next_id'];
$this->db->query(
'UPDATE sequence SET next_id=next_id+1 WHERE `key`=\'global\' AND next_id=' . $nextId
);
if ($this->db->affected_rows < 1) {
return $this->nextId();
}
return $nextId;
}
}
?>
上述例子中抽象出了一个MysqlIdMaker类,通过数据库自增长列生成全局唯一ID,我们可以调用MysqlIdMaker::nextId()函数来生成下一个唯一ID。
四、结语
本文讨论了生成唯一标识符的最佳实践,包括使用UUID、Snowflake算法和使用数据库。不同的场景和需求,会有不同的选择。我们可以根据具体的场景和需求,选择合适的方法来生成唯一标识符。