目录

1.使用自增主键的弊端

2.主键生成算法

2.1.UUID

2.1.1.概述

2.1.2.JAVA中的UUID

2.2.雪花算法

2.2.1.概述

2.2.2.JAVA中使用雪花算法


1.使用自增主键的弊端

首先在实际工程中我们很少用1,2,3......这样的自增主键,原因如下:

  • 主键冲突
  • 性能问题
  • 安全问题

主键冲突:

比如我要跨数据库进行数据同步、或者在分布式系统中跨“分区”进行数据同步,不难想象,1,2,3......这种递增的单数字是极容易产生冲突的。

性能问题:

自增主键在数据库中使用的是自增序列来生成主键值,而在高并发情况下,多个线程可能同时请求获取下一个自增值,此时会发生竞争,因为数据库需要保证每个自增值只会被分配一次。为了保证自增值的唯一性,数据库使用锁机制来避免多个线程同时获取同一个自增值。

安全问题:

使用自增主键可以很容易地猜测出下一条记录的主键值,这可能会导致一些安全问题。

综上所述,我们可以发现使用复杂主键是很有必要的,要生成复杂且具有唯一性的主键就需要依赖主键生成算法了。

2.主键生成算法

常见的主键生成算法:

  • UUID
  • 雪花算法

2.1.UUID

2.1.1.概述

UUID,通用唯一标识符,UUID算法的核心思想是生成一个128位的唯一标识符,通常表示为32个十六进制数字的字符串。每个UUID会尽量保证其唯一性。

UUID算法目前有几个官方推出的版本,由Internet工程任务组(IETF)、国际标准化组织(ISO)和国际电信联盟(ITU)共同制定:

  1. UUIDv1:基于时间戳和MAC地址生成UUID,保证同一台计算机上生成的UUID是唯一的,但不适合在分布式系统中使用。

  2. UUIDv2:基于DCE安全机制的UUID,使用POSIX的UID/GID和当前时间生成UUID,不常用。

  3. UUIDv3:基于命名空间和字符串生成UUID,使用MD5散列算法生成UUID。

  4. UUIDv4:使用随机数生成UUID,保证在所有计算机上都是唯一的。

  5. UUIDv5:与UUIDv3类似,但使用SHA-1散列算法生成UUID。

其实本质上来说UUID只是个概念,UUID的算法我们甚至可以自己去写一个,只是我们自己写的肯定会没那么优质,保证唯一性的效果不会好。

2.1.2.JAVA中的UUID

工程上我们一般都是在JAVA后端生成UUID作为每条要插入数据库的数据的主键,JAVA中生成UUID很简单:

import java.util.UUID;

public class UUIDExample {
    public static void main(String[] args) {
        // 生成一个随机的UUID
        UUID uuid = UUID.randomUUID();
        System.out.println("Random UUID: " + uuid.toString());
    }
}

生成过程其实也很简单:

public static UUID randomUUID() {
    SecureRandom ng = Holder.numberGenerator;

    byte[] randomBytes = new byte[16];
    ng.nextBytes(randomBytes);
    randomBytes[6]  &= 0x0f;  /* clear version        */
    randomBytes[6]  |= 0x40;  /* set to version 4     */
    randomBytes[8]  &= 0x3f;  /* clear variant        */
    randomBytes[8]  |= 0x80;  /* set to IETF variant  */
    return new UUID(randomBytes);
}
  1. 首先从Holder静态内部类中获取一个SecureRandom对象。Holder类是一个懒加载的单例类,用于确保SecureRandom对象只被创建一次,并且线程安全。

  2. 然后生成一个长度为16的字节数组randomBytes,使用ng.nextBytes(randomBytes)方法填充随机数。

  3. 接下来将字节数组randomBytes中的一些特定位进行修改,以符合UUID版本4的格式要求:

    • 将第7个字节的高4位清零,再将其低4位设置为4,表示这是UUID版本4。
    • 将第9个字节的高2位清零,再将其第7位设置为1,表示这是符合IETF标准的UUID。
  4. 最后,将修改后的字节数组作为参数,调用UUID(byte[] data)构造方法生成一个UUID对象。

2.2.雪花算法

2.2.1.概述

雪花算法(Snowflake Algorithm)是Twitter公司开发的一种分布式ID生成算法,用来生成64位的唯一ID。它的核心思想是:利用一个64位的long型数字作为全局唯一ID,高位部分表示时间戳,中间部分表示机器ID,低位部分表示在此机器上的序列号。

雪花算法的64位long型数字由以下几个部分组成:

  • 符号位(1 bit):由于long型数字是有符号的,而雪花算法生成的ID必须是正数,所以符号位固定为0。
  • 时间戳部分(41 bit):记录时间戳,精确到毫秒级别,可以使用当前时间减去一个固定的起始时间,得到一个相对时间戳,从而可以使用41位二进制数表示该时间戳可支持的时间范围为约69年。
  • 机器ID部分(10 bit):可以配置多台机器,每台机器都分配一个唯一的ID,用于在分布式环境下防止ID重复。
  • 序列号部分(12 bit):表示在同一毫秒内生成的序列号,可以使用自增来实现,最多支持4096个ID。

2.2.2.JAVA中使用雪花算法

原生JDK中并没有提供雪花算法的实现,在一些常用的ORM框架中支持了使用雪花算法生成主键ID的功能,如hibernate、mybatis-plus。

以使用Hibernate为例:

可以在实体类的主键字段上使用@GenericGenerator注解和@GeneratedValue注解,其中@GenericGenerator注解用来指定主键生成器的名称和类型,@GeneratedValue注解则用来指定主键生成策略和生成器的名称,例如:

@Entity
@Table(name = "user")
public class User {
 
    @Id
    @GenericGenerator(name = "snowflake", strategy = "com.xxx.snowflake.SnowflakeIdGenerator")
    @GeneratedValue(generator = "snowflake")
    @Column(name = "id")
    private Long id;
 
    // ...
}

以mybatis-plus为例:

配置主键生成器为雪花算法的生成器

mybatis-plus:
  global-config:
    db-config:
      id-type: ASSIGN_ID
      key-generator: com.baomidou.mybatisplus.core.incrementer.SnowflakeKeyGenerator

然后给主件指定使用所配置的主键生成器

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

@Data
public class User {

    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    private String name;

    private Integer age;

    // 其他字段省略...
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐