您的位置:

实现唯一标识符生成的最佳实践

在软件系统中,唯一标识符是非常重要的。一个唯一标识符是一个用来标识一个实体或对象的字符串或数字。比如,我们的用户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算法和使用数据库。不同的场景和需求,会有不同的选择。我们可以根据具体的场景和需求,选择合适的方法来生成唯一标识符。