为什么需要全局唯一ID
我们这里引用美团 Leaf 的场景介绍:在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一 ID 来标识一条数据或消息,数据库的自增 ID 显然不能满足需求;特别一点的如订单、骑手、优惠券也都需要有唯一 ID 做标识。此时一个能够生成全局唯一ID 的系统是非常必要的。
UUID
UUID (Universally Unique Identifier),通用唯一识别码的缩写。UUID是由一组32位数的16进制数字所构成,所以UUID理论上的总数为 1632=2128,约等于 3.4 x 10^38。也就是说若每纳秒产生1兆个UUID,要花100亿年才会将所有UUID用完。生成的UUID是由 8-4-4-4-12格式的数据组成,其中32个字符和4个连字符’ - ',一般我们使用的时候会将连字符删除 uuid.toString().replaceAll(“-”,“”)。
UUID的产生方式主要包括以下几种:
- 基于时间戳和MAC地址(UUID v1):这种方式结合了主机的网络接口卡MAC地址和当前的日期时间来生成UUID。由于包含了MAC地址,因此这种方式生成的UUID在一定程度上是可以被跟踪的,因为它隐含了生成UUID的设备信息。
- 基于随机数(UUID v4):这是目前最常用的UUID生成方式。它完全基于随机数或伪随机数,没有任何可以识别的个人或组织信息,因此是完全匿名的。这也是为什么它被广泛应用于需要高度安全性和隐私保护的场景。
- 基于命名空间的UUID(UUID v3、v5):这种方式基于MD5和SHA-1散列算法,通过将特定命名空间的标识符和名字进行散列来生成UUID。这种方法允许UUID基于给定的名字和命名空间来生成,确保不同命名空间下的UUID是唯一的。
- 基于硬件的UUID:某些系统可能还会根据其他硬件信息来生成UUID,例如硬盘序列号等。
java实现的uuid为基于随机数,使用代码:
public static void main(String[] args) {
//获取一个版本4根据随机字节数组的UUID。
UUID uuid = UUID.randomUUID();
System.out.println(uuid.toString().replaceAll("-",""));
}
虽然 UUID 生成方便,本地生成没有网络消耗,但是使用起来也有一些缺点:
- 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
- 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,暴露使用者的位置。
- 对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能,可以查阅 Mysql 索引原理 B+树的知识。
使用redis实现
Redis实现分布式唯一ID主要是通过提供像 INCR 和 INCRBY 这样的自增原子命令,由于Redis自身的单线程的特点所以能保证生成的 ID 肯定是唯一有序的。以下代码通过引入jedis来实现:
import redis.clients.jedis.Jedis;
public class UniqueIdGenerator {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final String UNIQUE_ID_KEY = "unique_id";
public static void main(String[] args) {
Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
long uniqueId = generateUniqueId(jedis);
System.out.println("生成的唯一ID: " + uniqueId);
jedis.close();
}
private static long generateUniqueId(Jedis jedis) {
return jedis.incr(UNIQUE_ID_KEY);
}
}
雪花算法-Snowflake
Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。而 Java中64bit的整数是Long类型,所以在 Java 中 SnowFlake 算法生成的 ID 就是 long 来存储的。
具体实现步骤如下:
- 第一位为未使用(符号位表示正数),接下来的41位为毫秒级时间(41位的长度可以使用69年)
- 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)
- 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
以下是java实现版本:
public class Snowflake implements Serializable {
private static final long serialVersionUID = 1L;