# 前言
表主键的设计在实际的开发中经常用,oracle里边都习惯使用UUID,mysql里边都比较习惯使用自增id,hbase的表id的设计非常重要,这关系到查询的效率以及排序问题,所以表主键id的设计极其重要。对于业务量比较大的设计也需要长远的考虑,是否后期会用到分库分表的策略,如果分库分表主键id是否会重复等。来学习一波数据库表主键id的设计。
UUID 是由一组32位数的16进制数字所构成,以连字号分隔的五组来显示,形式为 8-4-4-4-12,总共有 36个字符。
java中util工具类提供了UUID工具类
/**
* Static factory to retrieve a type 4 (pseudo randomly generated) UUID.
*
* The {@code UUID} is generated using a cryptographically strong pseudo
* random number generator.
*
* @return A randomly generated {@code UUID}
*/
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);
}
测试UUID:
public class UUIDTest {
public static void main(String[] args) {
System.out.println(UUID.randomUUID().toString());
System.out.println(UUID.randomUUID().toString());
System.out.println(UUID.randomUUID().toString());
System.out.println(UUID.randomUUID().toString());
// 6c736056-cdb4-42b8-a40c-cda25f81b520
// c315c255-c932-4984-8550-95e7fbca7253
// 5048b670-f9e8-426d-a9d5-b68cf378a045
// efa190d8-5316-40af-b7f4-c43edfba6074
}
}
UUID优缺点
优点:
• 使用比较简单,自带的工具类生成即可。
• 性能好
缺点:
• 由于主键生成的不规律性,一般主键都是自动创建索引,长度比较长,带来的问题是占用的存储空间比较大,查询效率不高。
• 不适合作为分布式id。
此处以mysql为例进行使用。
每次进行数据库插入操作的时候,id会进行自动的根据设置的规则进行增加。
可以看到我们数据库遍历的设置。
mysql> show variables like '%increment%';
+-----------------------------+-------+
| Variable_name | Value |
+-----------------------------+-------+
| auto_increment_increment | 1 |
| auto_increment_offset | 1 |
| div_precision_increment | 4 |
| innodb_autoextend_increment | 64 |
+-----------------------------+-------+
执行插入之后查询下最新的id
INSERT INTO person(username, age) VALUES('elite', 22);
SELECT LAST_INSERT_ID();
自增的ID只是单机数据库下使用,分库分表的情况下不适合,会产生重复的ID。
基于全局唯一ID的特性,我们可以通过Redis的INCR命令来生成全局唯一ID。
INCR key:对存储在指定key的数值执行原子的加1操作。如果指定的key不存在,那么在执行incr操作之前,会先将它的值设定为0。
如果指定的key中存储的值不是字符串类型(fix:)或者存储的字符串类型不能表示为一个整数,
@Autowired
private StringRedisTemplate redisTemplate;
///设置一个起始的时间戳
private static final long BEGIN_TIMESTAMP = xxx;
/**
* 生成分布式ID
* 符号位 时间戳[31位] 自增序号【32位】
* @param bizType 设置没个业务的
* @return
*/
public long nextId(String bizType){
// 1.生成时间戳
LocalDateTime now = LocalDateTime.now();
// 格林威治时间差
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
// 我们需要获取的 时间戳 信息
long timestamp = nowSecond - BEGIN_TIMESTAMP;
// 2.生成序号 --》 从Redis中获取
// 当前当前的日期
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
// 获取对应的自增的序号
Long increment = redisTemplate.opsForValue().increment("id:" + bizType + ":" + date);
return timestamp << 32 | increment;
}
雪花算法(Snowflake)是Twitter开源的一种分布式ID生成算法,用来保证在大规模分布式系统中生成全局唯一的ID。雪花算法产生的ID是一个64位的整数,由以下几部分组成:
• 符号位:第一位为0。
• 时间戳:41位,用来记录时间戳,精确到毫秒。
• 数据中心ID:5位,用来记录生成ID的数据中心或机器ID。
• 工作机器ID:5位,用来记录在当前数据中心的工作机器ID。
• 序列号:12位,用来记录同一毫秒内产生的不同ID。
其主要优点是保证了全局ID的唯一性,避免了分布式系统环境下的ID冲突;并且生成ID的过程中无需依赖数据库等外部系统,减少了系统复杂性。不足的地方是,由于依赖时间戳,如果系统时间回拨,有可能会导致ID冲突。
下面给出Java实现雪花算法的一种方式:
package com.elite.java.distributeid;
public class SnowflakeIdWorker {
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long twepoch = 1288834974657L;
private long workerIdBits = 5L;
private long datacenterIdBits = 5L;
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private long sequenceBits = 12L;
private long workerIdShift = sequenceBits;
private long datacenterIdShift = sequenceBits + workerIdBits;
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private long sequenceMask = -1L ^ (-1L << sequenceBits);
private long lastTimestamp = -1L;
public SnowflakeIdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0");
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id for "+ (lastTimestamp - timestamp) + " milliseconds");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen(){
return System.currentTimeMillis();
}
}
测试
public class SnowFlakeTest {
public static void main(String[] args) {
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
for (int i = 0; i < 10; i++) {
long id = idWorker.nextId();
System.out.println(id);
}
}
}