分布式 id 生成器
点击上方“方志朋”,选择“设为星标”
回复”666“获取新整理的面试资料
本文来源:
www.juejin.im/post/5d8882d8f265da03e369c063
在高并发或者分表分库情况下怎么保证数据id的幂等性呢?
经常用到的解决方案有以下几种。
微软公司通用唯一识别码(UUID)
Twitter公司雪花算法(SnowFlake)
基于数据库的id自增
对id进行缓存
这里我们要谈到snowflake算法了
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。
其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号,最后还有一个符号位,永远是0。
snowflake算法所生成的ID结构,如下图:
整个结构是64位,所以我们在Java中可以使用long来进行存储。
该算法实现基本就是二进制操作,单机每秒内理论上最多可以生成1024*(2^12),也就是409.6万个ID(1024 X 4096 = 4194304)
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
41位时间截(毫秒级)。注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
加起来刚好64位,为一个Long型。
SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高
经测试,SnowFlake每秒能够产生26万ID左右。
snowFlake算法的优点:
生成ID时不依赖于DB,完全在内存生成,高性能高可用。
ID呈趋势递增,后续插入索引树的时候性能较好。
SnowFlake算法的缺点:
依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成ID冲突,或者ID乱序
算法代码如下
public class SnowflakeIdWorker {// ==============================Fields==================/** 开始时间截 (2019-08-06) */private final long twepoch = 1565020800000L;/** 机器id所占的位数 */private final long workerIdBits = 5L;/** 数据标识id所占的位数 */private final long datacenterIdBits = 5L;/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */private final long maxWorkerId = -1L ^ (-1L << workerIdBits);/** 支持的最大数据标识id,结果是31 */private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);/** 序列在id中占的位数 */private final long sequenceBits = 12L;/** 机器ID向左移12位 */private final long workerIdShift = sequenceBits;/** 数据标识id向左移17位(12+5) */private final long datacenterIdShift = sequenceBits + workerIdBits;/** 时间截向左移22位(5+5+12) */private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */private final long sequenceMask = -1L ^ (-1L << sequenceBits);/** 工作机器ID(0~31) */private long workerId;/** 数据中心ID(0~31) */private long datacenterId;/** 毫秒内序列(0~4095) */private long sequence = 0L;/** 上次生成ID的时间截 */private long lastTimestamp = -1L;//==============================Constructors====================/*** 构造函数* @param workerId 工作ID (0~31)* @param datacenterId 数据中心ID (0~31)*/public SnowflakeIdWorker(long workerId, long datacenterId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}this.workerId = workerId;this.datacenterId = datacenterId;}// ==============================Methods=================================/*** 获得下一个ID (该方法是线程安全的)* @return SnowflakeId*/public synchronized long nextId() {long timestamp = timeGen();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - twepoch) << timestampLeftShift) //| (datacenterId << datacenterIdShift) //| (workerId << workerIdShift) //| sequence;}/*** 阻塞到下一个毫秒,直到获得新的时间戳* @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间* @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}//==============================Test=============================================/** 测试 */public static void main(String[] args) {SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);for (int i = 0; i < 1000; i++) {long id = idWorker.nextId();System.out.println(Long.toBinaryString(id));System.out.println(id);}}
}
快速使用snowflake算法只需以下几步
引入hutool依赖
<dependency><groupId>cn.hutoolgroupId><artifactId>hutool-captchaartifactId><version>${hutool.version}version>
dependency>
ID 生成器
public class IdGenerator {private long workerId = 0;@PostConstructvoid init() {try {workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());log.info("当前机器 workerId: {}", workerId);} catch (Exception e) {log.warn("获取机器 ID 失败", e);workerId = NetUtil.getLocalhost().hashCode();log.info("当前机器 workerId: {}", workerId);}}/*** 获取一个批次号,形如 2019071015301361000101237** 数据库使用 char(25) 存储** @param tenantId 租户ID,5 位* @param module 业务模块ID,2 位* @return 返回批次号*/public synchronized String batchId(int tenantId, int module) {String prefix = DateTime.now().toString(DatePattern.PURE_DATETIME_MS_PATTERN);return prefix + tenantId + module + RandomUtil.randomNumbers(3);}@Deprecatedpublic synchronized String getBatchId(int tenantId, int module) {return batchId(tenantId, module);}/*** 生成的是不带-的字符串,类似于:b17f24ff026d40949c85a24f4f375d42** @return*/public String simpleUUID() {return IdUtil.simpleUUID();}/*** 生成的UUID是带-的字符串,类似于:a5c8a5e8-df2b-4706-bea4-08d0939410e3** @return*/public String randomUUID() {return IdUtil.randomUUID();}private Snowflake snowflake = IdUtil.createSnowflake(workerId, 1);public synchronized long snowflakeId() {return snowflake.nextId();}public synchronized long snowflakeId(long workerId, long dataCenterId) {Snowflake snowflake = IdUtil.createSnowflake(workerId, dataCenterId);return snowflake.nextId();}/*** 生成类似:5b9e306a4df4f8c54a39fb0c* * ObjectId 是 MongoDB 数据库的一种唯一 ID 生成策略,* 是 UUID version1 的变种,详细介绍可见:服务化框架-分布式 Unique ID 的生成方法一览。** @return*/public String objectId() {return ObjectId.next();}}
测试类
public class IdGeneratorTest {@Autowiredprivate IdGenerator idGenerator;@Testpublic void testBatchId() {for (int i = 0; i < 100; i++) {String batchId = idGenerator.batchId(1001, 100);log.info("批次号: {}", batchId);}}@Testpublic void testSimpleUUID() {for (int i = 0; i < 100; i++) {String simpleUUID = idGenerator.simpleUUID();log.info("simpleUUID: {}", simpleUUID);}}@Testpublic void testRandomUUID() {for (int i = 0; i < 100; i++) {String randomUUID = idGenerator.randomUUID();log.info("randomUUID: {}", randomUUID);}}@Testpublic void testObjectID() {for (int i = 0; i < 100; i++) {String objectId = idGenerator.objectId();log.info("objectId: {}", objectId);}}@Testpublic void testSnowflakeId() {ExecutorService executorService = Executors.newFixedThreadPool(20);for (int i = 0; i < 20; i++) {executorService.execute(() -> {log.info("分布式 ID: {}", idGenerator.snowflakeId());});}executorService.shutdown();}}在项目中我们只需要注入 @Autowired private IdGenerator idGenerator;即可然后设置id:
order.setId(idGenerator.snowflakeId() + "");
热门内容:
Java 程序员必须清楚的 7 个性能指标
阿里巴巴的技术专家,是如何画好架构图的?
史上最烂的项目:苦撑 12 年,600 多万行代码
一次 Jar 包升级引发的血案 & 解决
如何优雅的导出 Excel
JDK 13 新特性一览
某小公司RESTful、共用接口、前后端分离、接口约定的实践
请停止学习框架
IntelliJ IDEA 2019.3这回真的要飞起来了,新特性抢先看!
最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡
分布式 id 生成器相关推荐
- 美团(Leaf)分布式ID生成器,好用的一批!
不了解分布式ID的同学,先行去看<一口气说出 9种 分布式ID生成方式,面试官有点懵了>温习一下基础知识,这里就不再赘述了 美团(Leaf) Leaf是美团推出的一个分布式ID生成服务,名 ...
- c#分布式ID生成器
c#分布式ID生成器 简介 这个是根据twitter的snowflake来写的.这里有中文的介绍. 如上图所示,一个64位ID,除了最左边的符号位不用(固定为0,以保证生成的ID都是正数),还剩余63 ...
- 工程搭建:搭建子工程之分布式id生成器
分布式ID生成器 目前微服务架构盛行,在分布式系统中的操作中都会有一些全局性ID的需求,所以我们不能使用数据库本身的自增 功能来产生主键值,只能由程序来生成唯一的主键值.我们采用的是开源的twitte ...
- 分布式ID生成器的解决方案总结
转载自 分布式ID生成器的解决方案总结 在互联网的业务系统中,涉及到各种各样的ID,如在支付系统中就会有支付ID.退款ID等.那一般生成ID都有哪些解决方案呢?特别是在复杂的分布式系统业务场景中,我们 ...
- java redis id生成器_基于redis的分布式ID生成器
项目地址 基于redis的分布式ID生成器. 准备 首先,要知道redis的EVAL,EVALSHA命令: 原理 利用redis的lua脚本执行功能,在每个节点上通过lua脚本生成唯一ID. 生成的I ...
- 融云发送图片消息_IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现...
1.引言 很多人一想到IM应用开发,第一印象就是"长连接"."socket"."保活"."协议"这些关键词,没错,这些确 ...
- 来吧,自己动手撸一个分布式ID生成器组件
在经过了众多轮的面试之后,小林终于进入到了一家互联网公司的基础架构组,小林目前在公司有使用到架构组研究到分布式id生成器,前一阵子大概看了下其内部的实现,发现还是存在一些架构设计不合理之处.但是又由于 ...
- 分布式id生成器:彻底解决雪花算法时间回拨问题
Butterfly 简介 雪花算法是twitter提出的分布式id生成器方案,但是有三个问题,其中前两个问题在业内很常见: 时间回拨问题 机器id的分配和回收问题 机器id的上限问题 Butterfl ...
- 推特雪花算法,分布式id生成器
推特雪花算法 分布式id生成器 package util;import java.lang.management.ManagementFactory; import java.net.InetAddr ...
- 基于Twitter的Snowflake算法实现的分布式ID生成器
/*** 基于Twitter的Snowflake算法实现的分布式ID生成器* ------------------------------------------------------------- ...
最新文章
- javascript设计模式学习日记--模板方法模式
- 掌握测试驱动开发的3个关键因素(译)
- 7.MATLAB变量——矩阵操作二
- Matlab中解决出现的错误使用 svmtrain (line 234) Y must be a vector or a character array.问题
- linux的swap增加的二个办法
- AI:你们是不是在等一顶红帽子?
- 基本类型数组转包装类型数组工具类
- stl vector 函数_vector :: crend()函数以及C ++ STL中的示例
- 解决: Your ApplicationContext is unlikely to start due to a @ComponentScan of the default
- 国庆前的最后3场直播活动!!
- BestCoder Round #77 (div.2)解题报告
- 随想录(单片机和步进电机学习笔记)
- TabHost和ActivityGroup用法
- 风变Python编程13类的学习2
- 如何用4K YouTube转换视频为MP3,同时设置成MP3桌面播放器?
- 基于Java毕业设计移动电商网站源码+系统+mysql+lw文档+部署软件
- 地图切图 java_多任务切图 | SuperMap iDesktop Java
- 联想android刷机教程,联想YOGA Tablet 2线刷刷机教程 Android版可救砖
- canva怎么组合_教你使用Canvas合成图片
- 利用朴素贝叶斯算法解决“公园凉鞋问题”