TSID:时间排序唯一标识符的完整指南
1. 简介
标准的128位随机UUID虽然流行,但在数据库主键应用中存在显著缺陷:
UUID作为主键的问题:
- 存储空间大:每条记录需要16字节,影响外键列存储
- 索引效率低 :B+树索引随机值导致:
- 填充因子低:8kB页只存储少量元素,浪费磁盘和内存
- 频繁页分裂:随机插入导致索引频繁重新平衡
- 缓存效率差:随机访问模式降低缓冲池效果
2. TSID核心概念
2.1 什么是TSID?
TSID(Time-Sorted Unique Identifier)融合了Twitter Snowflake和ULID思想:
- ✅ 时间排序:按生成时间自然排序
- ✅ 紧凑存储:64位整数或13字符字符串
- ✅ Base32编码:采用Crockford编码,URL安全、无大小写敏感
- ✅ 更短长度:比UUID、ULID、KSUID更短
2.2 TSID结构
42位时间戳 + 22位随机部分
- 时间部分(42位):自2020-01-01 00:00:00 UTC以来的毫秒数
- 随机部分 (22位):
- 节点标识符:0-20位(默认10位)
- 计数器:2-22位(取决于节点位)
配置示例(默认):
- 节点位:10位 → 最大节点值:2¹⁰-1 = 1023
- 计数位:12位 → 最大计数值:2¹²-1 = 4095
- 每毫秒最大TSID数:4096
2.3 基本使用
java
// 1. 创建TSID对象
TSID tsid = TSID.Factory.getTsid();
// 2. 生成Long类型ID
long id = TSID.Factory.getTsid().toLong(); // 输出:797263809025044592
// 3. 生成String类型ID
String strId = TSID.Factory.getTsid().toString(); // 输出:0P43KE5EXXM3Z
// 4. 批量生成
for (int i = 0; i < 20; i++) {
long id = TSID.Factory.getTsid().toLong();
System.out.println(id);
}
// 5. 快速生成(每毫秒最多4,194,304个)
TSID fastTsid = TSID.fast();
// 6. 从字符串恢复
TSID tsid = TSID.from("0123456789ABC");
System.out.println(tsid.toLong()); // 输出:38390726480144748
// 7. 获取创建时间戳
Instant instant = TSID.Factory.getTsid().getInstant();
System.out.println(instant); // 输出:2026-01-01T01:03:34.735Z
// 8. Base-62编码
String base62 = TSID.Factory.getTsid().encode(62);
System.out.println(base62); // 输出:0wtVRVNVMwu
// 9. 格式化输出
String formatted = tsid.format("pack%S");
System.out.println(formatted); // 输出:pack0P43SMJ0FH80H
2.4 密钥生成器实现
java
public class KeyGenerator {
public static String next() {
return TSID.Factory.getTsid().toString();
}
}
// 使用
KeyGenerator.next(); // 0P43T4Z0VAC3S
KeyGenerator.next(); // 0P43T4Z0VAC3T
KeyGenerator.next(); // 0P43T4Z0VAC3V
2.5 模拟Twitter Snowflake
java
// Twitter Snowflake配置
int datacenter = 1; // 最大值:31 (2⁵-1)
int worker = 1; // 最大值:31 (2⁵-1)
int node = (datacenter << 5 | worker); // 最大值:1023 (2¹⁰-1)
Instant customEpoch = Instant.ofEpochMilli(1288834974657L);
IntFunction<byte[]> randomFunction = (x) -> new byte[x];
TSID.Factory factory = TSID.Factory.builder()
.withRandomFunction(randomFunction)
.withCustomEpoch(customEpoch)
.withNode(node)
.build();
TSID tsid = factory.generate();
for (int i = 0; i < 5; i++) {
System.out.println(tsid.toLong());
}
3. JPA集成示例
3.1 TSID组件配置
java
@Component
public class TsidComponent {
public static final String TSID_NODE_COUNT_PROPERTY = "tsid.node.count";
public static final String TSID_NODE_COUNT_ENV = "TSID_NODE_COUNT";
public static TSID.Factory TSID_FACTORY;
static {
String nodeCountSetting = System.getProperty(TSID_NODE_COUNT_PROPERTY);
if (nodeCountSetting == null) {
nodeCountSetting = System.getenv(TSID_NODE_COUNT_ENV);
}
int nodeCount = nodeCountSetting != null ?
Integer.parseInt(nodeCountSetting) : 256;
TSID_FACTORY = getTsidFactory(nodeCount);
}
public static TSID.Factory getTsidFactory(int nodeCount, int nodeId) {
int nodeBits = ((int) (Math.log(nodeCount) / Math.log(2))) + 1;
return TSID.Factory.builder()
.withRandomFunction(TSID.Factory.THREAD_LOCAL_RANDOM_FUNCTION)
.withNodeBits(nodeBits)
.withNode(nodeId)
.build();
}
}
3.2 自定义ID生成器注解
java
// ID生成器类型注解
@IdGeneratorType(PackIdGenerator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD })
public @interface PackSequence {
}
// 实体类使用
@Entity
@Table(name = "o_student")
public class Student {
@Id
@PackSequence
private Long id;
private String name;
private String sno;
// getters and setters
}
3.3 测试示例
java
@Resource
private StudentService studentService;
@Test
public void testSave() {
for (int i = 0; i < 10; i++) {
Student student = new Student();
student.setName("pack_xg_%s".formatted(i));
student.setSno("X00002-%s".formatted(i));
this.studentService.save(student);
}
}
4. TSID优势总结
| 特性 | UUID | 自增ID | TSID |
|---|---|---|---|
| 排序性 | 随机无序 | 单库有序 | 时间有序 |
| 分布式 | 支持 | 不支持 | 完美支持 |
| 存储空间 | 16字节 | 通常8字节 | 8字节 |
| 索引性能 | 差(随机插入) | 好(顺序插入) | 优秀 |
| 可读性 | 差(长字符串) | 好(数字) | 好 |
| 冲突概率 | 极低 | 无(单库) | 极低 |
5. 适用场景
- 微服务架构:分布式系统需要全局唯一ID
- 数据库分片:跨多个数据库实例保持ID唯一性
- 时间序列数据:日志、事件流等按时间排序的数据
- 高并发系统:需要高性能ID生成的场景
- 替代UUID:需要保持唯一性但提升数据库性能的场景
6. 注意事项
- 时间回拨:系统时间回拨可能导致ID冲突,TSID内置了时间保护机制
- 节点配置:在生产环境中合理配置节点ID,避免冲突
- 安全性 :对于安全敏感场景,使用
Factory.getTsid()而非fast() - 迁移成本:从现有ID方案迁移需要考虑数据迁移和兼容性
TSID为现代分布式系统提供了一个高性能、可扩展且数据库友好的ID生成解决方案。