当前位置: 首页 > 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.gsyq.cn/news/192655.html

相关文章:

  • offreg.dll文件损坏丢失找不到 打不开问题 下载方法
  • MyBatisPlus用得好,不如让AI帮你写SQL——基于Swift框架的NL2SQL模型部署指南
  • 仅限极客掌握的技术:C语言直接访问物理地址实现存算一体(附完整代码示例)
  • Loss-Scale机制解析:防止梯度溢出的有效手段
  • C语言量子计算实战(qubit初始化配置全解析)
  • qubit初始化配置陷阱频现,C语言开发者必须掌握的4个底层原理,99%的人忽略了第3点
  • 揭秘C语言在无人机数据采集中的应用:如何实现毫秒级响应与零误差传输
  • 深入浅出WinDbg Preview对PnP请求的跟踪方法
  • HuggingFace镜像网站支持HF_TOKEN免登录下载
  • Mamba架构讲解 - 实践
  • 全球变暖 DFS解 python
  • Grounding任务新突破:图文定位精度提升的秘密武器
  • 海南省自建房设计公司/机构权威测评推荐排行榜 - 苏木2025
  • SGLang推理引擎压测报告:每秒吞吐量突破万token
  • SimPO无需参考模型?Swift框架实现更高效的偏好优化
  • 批量采购折扣计划:适用于大规模AI项目客户
  • 想在广东省农村盖房子,靠谱的自建房设计公司口碑推荐 - 苏木2025
  • S7 - 200 PLC程序与MCGS组态构建轴承清洗机控制系统
  • 多节点训练集群搭建:基于ms-swift的企业级部署方案
  • Nature Machine Intelligence投稿:冲击顶级综合期刊
  • 打工人上班摸魚小說-第一章 卷王猝死,摸鱼系统到账
  • MLCC dc bias character
  • 打工人上班摸魚小說-第二章 带薪拉屎、策略划水与隐藏技能
  • 谁是TOP1?海南省海口市自建房设计公司评测排行榜 + 真实建房案例参考 - 苏木2025
  • 告别网盘限速!使用AI镜像站实现大模型文件直链高速下载
  • Cell Reports Physical Science:交叉学科创新潜力展示
  • 广西省南宁市自建房设计公司评测排行榜:6 家主流企业实地测评,哪家更靠谱? - 苏木2025
  • Mathtype公式识别升级之路:多模态大模型加持OCR精准解析
  • CPO偏好优化进阶:控制模型输出风格与伦理边界
  • 通俗解释为何未激活的Multisim打不开主数据库