当前位置: 首页 > news >正文

使用mysql号段方式生成唯一ID

前言

之前项目中使用的唯一ID生成方式为雪花算法,但是该方式存在“需要动态分配机器标识”和”时钟回拨“的问题,且解决起来比较复杂,所以考虑切换实现方式为数据库号段,该方式参考美团开源的项目Leaf。具体原理为

  1. 每个服务实例第一次从数据库加载一个号段放到内存中,比如第一个实例1-1000,第二个实例1001-2000
  2. 下次先从内存加载,号段用完了再从数据库加载

代码实现

数据库初始化sql

CREATE TABLE `id_segment` (`biz_tag` int(11) NOT NULL COMMENT '业务标识',`max_id` bigint(20) NOT NULL COMMENT '当前最大ID',`step` int(11) NOT NULL COMMENT '步长',`description` varchar(256) NULL COMMENT '描述',`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`biz_tag`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='ID段表'
;
-- 初始化
INSERT INTO id_segment(`biz_tag`,`max_id`,`step`,`description`) VALUES (1,0,1000,'订单ID');

实体类

import lombok.Data;import java.sql.Timestamp;@Data
public class IdSegment {private Integer bizTag;private Long maxId;private Long step;private String description;private Timestamp updateTime;
}

号段包装类,使用线程安全的AtomicLong

import lombok.Data;import java.sql.Timestamp;
import java.util.concurrent.atomic.AtomicLong;@Data
public class IdSegmentWrapper {private IdSegment idSegment;private AtomicLong currentId;public IdSegmentWrapper(IdSegment idSegment) {this.idSegment = idSegment;this.currentId = new AtomicLong(idSegment.getMaxId() - idSegment.getStep());}public long getNextId() {return currentId.incrementAndGet();}public long getMaxId() {return idSegment.getMaxId();}
}

数据库层代码,注意要加事务注解

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Slf4j
@Service
public class IdSegmentDao {@Autowiredprivate IdSegmentMapper idSegmentMapper;@Transactionalpublic IdSegmentWrapper getNext(int bizTag) {long startTime = System.nanoTime();;idSegmentMapper.updateMaxIdByBizTag(bizTag);IdSegment idSegment = idSegmentMapper.selectMaxIdByBizTag(bizTag);long endTime = System.nanoTime();log.info("获取到ID段:{},耗时:{} ns", JSON.toJSONString(idSegment), endTime - startTime);return new IdSegmentWrapper(idSegment);}
}

服务层代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;@Service
public class IdGeneratorService {@Autowiredprivate IdSegmentDao idSegmentDao;private Map<Integer, IdSegmentWrapper> cache = new ConcurrentHashMap<>();/*** 获取下一个全局唯一ID,不同的业务标识起始ID不同,比如bizTag=1,则起始ID为72057594037927936** @param bizTag 业务标识*/public Long getNextId(Integer bizTag) {long id = generateIdByBizTag(bizTag);id |= Long.valueOf(bizTag) << 56; //其实就是相加return id;}/*** 同一个服务实例内生成递增的唯一ID** @param bizTag 业务标识*/private synchronized Long generateIdByBizTag(Integer bizTag) {{IdSegmentWrapper segment = cache.get(bizTag);if (Objects.isNull(segment)) {segment = idSegmentDao.getNext(bizTag);cache.put(bizTag, segment);}long id = segment.getNextId();if (id < segment.getMaxId()) {return id;} else {segment = idSegmentDao.getNext(bizTag);cache.put(bizTag, segment);return segment.getNextId();}}}
}

验证代码为

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;@RestController
@RequestMapping("/id")
@Slf4j
public class IdSegmentController {@Autowiredprivate IdGeneratorService idGeneratorService;@GetMapping("/get")public String getId(@RequestParam(name = "count") Integer count) throws InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(100);Set<Long> ids = new ConcurrentSkipListSet<>();CountDownLatch latch = new CountDownLatch(count);long start = System.nanoTime();for (int i = 0; i < count; i++) {executorService.submit(() -> {try {Long id = idGeneratorService.getNextId(1);ids.add(id);} finally {latch.countDown();}});}// wait until all tasks finish or timeoutboolean completed = latch.await(30, TimeUnit.SECONDS);long end = System.nanoTime();executorService.shutdown();executorService.awaitTermination(5, TimeUnit.SECONDS);long elapsedMs = TimeUnit.NANOSECONDS.toMillis(end - start);log.info("生成ID数量:{}", ids.size());log.info("生成 {} 个ID耗时:{} ms{}", count, elapsedMs, completed ? "" : " (超时未全部完成)");return "ID生成任务已提交,耗时:" + elapsedMs + " ms";}
}

总结

使用多线程生成100000个ID,平均耗时1200ms,这个生成速率已经能满足大部分场景了,如果想继续优化,那就需要预加载,内存号段快用完时,自动从数据库加载。

参考

Leaf:美团分布式ID生成服务开源

http://www.jsqmd.com/news/176610/

相关文章:

  • 学术研究首选框架:支持复现实验可重复性的关键特性
  • ComfyUI工作流优化:借助Swift框架加速节点执行
  • GitHub镜像网站哪家强?推荐一个专为AI开发者打造的极速通道
  • A.每日一题——66. 加一
  • offreg.dll文件损坏丢失找不到 打不开问题 下载方法
  • 启明910平台上的C语言性能调优(9大关键控制点深度剖析)
  • GaLore与Q-Galore优化器对比:内存节省高达70%
  • 2025年度总结 2026年度规划
  • 仅限极客掌握的技术:C语言直接访问物理地址实现存算一体(附完整代码示例)
  • 深度测评本科生必用的8款AI论文工具
  • P6794 [SNOI2020] 水池
  • Loss-Scale机制解析:防止梯度溢出的有效手段
  • MyBatisPlus用得好,不如让AI帮你写SQL——基于Swift框架的NL2SQL模型部署指南
  • SGLang推理引擎压测报告:每秒吞吐量突破万token
  • C语言量子计算实战(qubit初始化配置全解析)
  • qubit初始化配置陷阱频现,C语言开发者必须掌握的4个底层原理,99%的人忽略了第3点
  • 开启虚拟化之旅:HAXM安装操作指南
  • Java一段代碼
  • C#调用ONNX Runtime运行大模型?性能优化技巧分享
  • 工业控制系统中C语言实时性提升实战(从代码到硬件的全链路优化)
  • 揭秘C语言在无人机数据采集中的应用:如何实现毫秒级响应与零误差传输
  • 模拟服务与虚拟化工具深度解析:WireMock/MockServer/Mountebank技术全景
  • 深入浅出WinDbg Preview对PnP请求的跟踪方法
  • 百元预算跑大模型?RTX 3090+Swift框架性价比之选
  • 无人机数据采集难题,90%开发者都忽略的C语言优化技巧,你中招了吗?
  • 揭秘NVIDIA编译黑盒:如何用C语言实现CUDA内核性能翻倍优化
  • 多模态大模型怎么选?一锤定音提供300+模型对比与评测数据
  • 为什么你的TinyML模型总崩溃?深度剖析C语言内存泄漏根源
  • Mamba架构讲解 - 实践
  • 想在广东省农村盖房子,靠谱的自建房设计公司口碑推荐 - 苏木2025