在性能测试和业务场景中,生成全局唯一标识符(GUID)是常见需求。超市八的埋点系统中,唯一标识用于数据库索引、请求追踪和日志分析。例如,为用户行为(如浏览商品、支付)生成唯一ID,支持高并发场景下的事务隔离和瓶颈排查。简单的实现可能通过自增计数器加锁或ThreadLocal
,但复杂场景下可能影响性能或导致线程安全问题。本章介绍五种生成唯一标识的方案(UUID、中心化服务、雪花算法、线程独享、原子类),并分析其性能与适用场景,为超市八选择最优方案提供指导。
以下是五种常见唯一标识生成方案的特点和适用场景:
1. UUID (java.util.UUID
)
3b9f3ef9-3c5b-449e-be3a-204c58252e1d
)。UUID
uuid
= UUID.randomUUID();
System.out.println(uuid); // 输出:3b9f3ef9-3c5b-449e-be3a-204c58252e1d
2. 中心化服务
INCR
命令生成全局唯一ID。3. 雪花算法 (Snowflake)
public
classSnowflakeWorker {
privatestaticfinallongSTART_TIMESTAMP=1712972492276L;
privatelong dataId, workerId, sequence = 0L;
privatestaticfinallongMAX_MACHINE_ID=31L, MAX_DATA_CENTER_ID = 31L, SEQUENCE_BITS = 12L;
privatestaticfinallongWORKER_ID_SHIFT= SEQUENCE_BITS;
privatestaticfinallongDATA_CENTER_ID_SHIFT= SEQUENCE_BITS + WORKER_ID_SHIFT;
privatestaticfinallongTIMESTAMP_LEFT_SHIFT= DATA_CENTER_ID_SHIFT + 5;
privatestaticfinallongSEQUENCE_MASK= ~(-1L << SEQUENCE_BITS);
privatelonglastTimestamp= -1L;
publicSnowflakeWorker(long dataId, long workerId) {
if (dataId > MAX_DATA_CENTER_ID || dataId < 0 || workerId > MAX_MACHINE_ID || workerId < 0) {
thrownewIllegalArgumentException("ID参数错误");
}
this.dataId = dataId;
this.workerId = workerId;
}
publicsynchronizedlongnextId() {
longtimestamp= System.currentTimeMillis();
if (timestamp < lastTimestamp) {
thrownewRuntimeException("时钟回拨");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = nextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
longid= ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT) |
(dataId << DATA_CENTER_ID_SHIFT) |
(workerId << WORKER_ID_SHIFT) | sequence;
return id & Long.MAX_VALUE;
}
privatelongnextMillis(long lastTimestamp) {
longtimestamp= System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
使用示例:SnowflakeWorker
snowflakeWorker
=
new
SnowflakeWorker(1, 1);
for (int
i
=
0; i < 5; i++) {
System.out.println(snowflakeWorker.nextId());
}
输出:6338816475540754432
6338816475540754433
6338816475540754434
6338816475540754435
6338816475540754436
4. 线程独享 (ThreadLocal)
ThreadLocal
为每个线程维护独立计数器,结合线程ID生成唯一标识,格式如ThreadName-Sequence
。ThreadLocal
实现线程隔离。Integer
计数器溢出风险(可换Long
)。ThreadLocal
生成请求ID,适合日志追踪但不适合分布式系统.ThreadLocal<Long> exclusive = ThreadLocal.withInitial(() -> 0L);
for (inti=0; i < 4; i++) {
Threadthread=newThread(() -> {
for (intj=0; j < 10; j++) {
Longid= exclusive.get();
exclusive.set(id + 1);
System.out.println(Thread.currentThread().getName() + "-" + (100000000 + id));
}
});
thread.setName("Thread-" + i);
thread.start();
}
输出(简化):Thread-0-100000000
Thread-0-100000001
Thread-1-100000000
Thread-2-100000000
Thread-3-100000000
5. 原子类 (AtomicInteger)
java.util.concurrent.atomic.AtomicInteger
的getAndIncrement
生成线程安全的递增ID。AtomicLong
支持更大范围。AtomicInteger
生成唯一ID,适合本地埋点数据处理。AtomicInteger
count=newAtomicInteger();
for (inti=0; i < 4; i++) {
newThread(() -> {
for (intj=0; j < 10; j++) {
System.out.println(count.getAndIncrement() + 10000000);
}
}).start();
}
输出(简化):10000000
10000001
10000002
10000003
在超市八的埋点系统中,唯一标识生成需平衡性能、唯一性和适用场景:
AtomicInteger
性能最高,适合高并发埋点ID生成,推荐用于本地日志处理。Snowflake
兼顾性能和递增性,适合订单系统主键生成。UUID
或ThreadLocal
生成可读ID,便于调试,适合非性能敏感场景。AtomicInteger
或ThreadLocal
,结合第10章的FunObjPool
复用UserBehavior
对象,减少内存分配。Snowflake
,通过配置数据中心ID和机器ID支持多节点,定期检查时钟同步。ThreadLocal
或AtomicInteger
的ID格式,减少空间占用。