首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【连载 76】唯一标识解决方案

【连载 76】唯一标识解决方案

作者头像
FunTester
发布2025-07-24 15:24:31
发布2025-07-24 15:24:31
1630
举报
文章被收录于专栏:FunTesterFunTester

在性能测试和业务场景中,生成全局唯一标识符(GUID)是常见需求。超市八的埋点系统中,唯一标识用于数据库索引、请求追踪和日志分析。例如,为用户行为(如浏览商品、支付)生成唯一ID,支持高并发场景下的事务隔离和瓶颈排查。简单的实现可能通过自增计数器加锁或ThreadLocal,但复杂场景下可能影响性能或导致线程安全问题。本章介绍五种生成唯一标识的方案(UUID、中心化服务、雪花算法、线程独享、原子类),并分析其性能与适用场景,为超市八选择最优方案提供指导。

11.1 唯一标识解决方案

以下是五种常见唯一标识生成方案的特点和适用场景:

1. UUID (java.util.UUID)

  • 原理:生成128位随机标识符,格式为36字符字符串(如3b9f3ef9-3c5b-449e-be3a-204c58252e1d)。
  • 优点
  • 全局唯一性:基于随机性和128位长度,冲突概率极低。
  • 高性能:生成速度快,无需网络通信。
  • 无序性:无需中心化管理,适合分布式系统。
  • 缺点
  • 长度较长:36字符占用较多存储空间。
  • 不易读:无业务含义,难以解析。
  • 不连续:不适合需要递增顺序的场景。
  • 场景:超市八的埋点系统可使用UUID标识用户请求,适合日志追踪但不适合作为数据库主键(因长度和无序性)。
  • 示例UUID uuid = UUID.randomUUID(); System.out.println(uuid); // 输出:3b9f3ef9-3c5b-449e-be3a-204c58252e1d

2. 中心化服务

  • 原理:通过中心化组件(如Redis、MySQL、ZooKeeper)生成递增ID。例如,使用Redis的INCR命令生成全局唯一ID。
  • 优点
  • 唯一性:中心化管理确保全局唯一。
  • 可追踪:与分布式日志系统对接,适合请求链路分析。
  • 缺点
  • 性能瓶颈:网络通信导致延迟,中心化组件压力大。
  • 单点风险:服务宕机影响ID生成。
  • 场景:超市八可结合节点ID(如数据中心ID)和本地序列号,使用Redis生成分布式唯一ID,但需优化网络延迟。

3. 雪花算法 (Snowflake)

  • 原理:生成64位ID,包含时间戳、机器ID(或数据中心ID+机器ID)、序列号,确保唯一性和递增性。
  • 优点
  • 高性能:本地生成,无网络开销。
  • 递增性:时间戳和序列号保证ID大致有序。
  • 可扩展:支持分布式场景,通过机器ID区分节点。
  • 缺点
  • 实现复杂:需自定义算法,处理时钟回拨等异常。
  • 依赖时间戳:时钟同步问题可能导致ID冲突。
  • 场景:超市八的订单系统可使用雪花算法生成主键ID,支持高并发和分布式部署。
  • 实现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实现线程隔离。
  • 可读性:ID包含线程信息,便于调试。
  • 缺点
  • 非全局递增:线程间ID不连续,需附加线程标识。
  • 有限范围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.AtomicIntegergetAndIncrement生成线程安全的递增ID。
  • 优点
  • 高性能:基于CAS操作,高效无锁。
  • 简单易用:无需线程管理。
  • 递增性: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

11.4.2 性能分析与选择

在超市八的埋点系统中,唯一标识生成需平衡性能、唯一性和适用场景:

  • 单机场景AtomicInteger性能最高,适合高并发埋点ID生成,推荐用于本地日志处理。
  • 分布式场景Snowflake兼顾性能和递增性,适合订单系统主键生成。
  • 日志追踪UUIDThreadLocal生成可读ID,便于调试,适合非性能敏感场景。
  • 避免中心化服务:因网络延迟,超市八不建议使用Redis生成ID,除非结合节点ID优化。

11.4.3 优化建议

  1. 1. 单机优化:使用AtomicIntegerThreadLocal,结合第10章的FunObjPool复用UserBehavior对象,减少内存分配。
  2. 2. 分布式优化:部署Snowflake,通过配置数据中心ID和机器ID支持多节点,定期检查时钟同步。
  3. 3. 监控与回归:在代码变更后验证ID生成性能,确保不引入瓶颈。
  4. 4. 长度控制:根据存储需求,调整ThreadLocalAtomicInteger的ID格式,减少空间占用。

FunTester 原创精华
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-07-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FunTester 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 11.1 唯一标识解决方案
  • 11.4.2 性能分析与选择
  • 11.4.3 优化建议
    • FunTester 原创精华
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档