实用向—总结一些唯一ID生成方式
一、生成方式
1、UUID产生命令唯一标识,32位的字母数字组合
/**
* 根据UUID产生命令唯一标识
*
* @throws InterruptedException
*/
public static String getUUIDHashCode() {
String orderSeq = UUID.randomUUID().toString();
return orderSeq; }
2、UUID取hash值+随机数,16位纯数字
/**
* 根据UUID取hash值+随机数,产生命令唯一标识
*
* @throws InterruptedException
*/
public static String getOrderSeq() {
String orderSeq = Math.abs(UUID.randomUUID().toString().hashCode()) + "";
while (orderSeq.length() < 16) {
orderSeq = orderSeq + (int) (Math.random() * 10);
}
return orderSeq;
}
3、十六进制随机数 ,长度16的十六进制字符串
//十六进制随机数 16位的十六进制字符串
public static String getRandomHexString() {
try {
StringBuffer result = new StringBuffer();
for (int i = 0; i < 16; i++) {
result.append(Integer.toHexString(new Random().nextInt(16)));
}
return result.toString().toUpperCase(); } catch (Exception e) {
// TODO: handle exception
e.printStackTrace(); }
return null; }
4、雪花算法
长度不超过20的纯数字,时间戳不同,长度会产生变化
/** 开始时间截 */
private final long twepoch = 1420041600000L; /** 机器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; /**
* 构造函数
* @param workerId 工作ID (0~31)
* @param datacenterId 数据中心ID (0~31)
*/
public SnowFlake(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;
} /**
* 获得下一个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位的ID
return ((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();
}
5、Redis Incr 命令
Redis Incr 命令会将 key 中储存的数字值增一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
这里以jedis为例提供两种自增ID的生成方式
第一种方式直接通过Incr命令获取自增ID
JedisUtils.incr("inc-test1")
第二张方式获取带有时间戳的唯一编码,时间细度为分钟
/**
* 基于Redis生成 时间+递增ID 的唯一编码
* @param key
* @return
*/
public static String getRedisTimeSeq(String key) { String time = DateUtils.parseDateToStr("yyyyMMddHHmm",new Date()); StringBuilder sBuilder = new StringBuilder(time); sBuilder.append(JedisUtils.incr(key+":"+time,120));//保证一分钟内KEY有效 return sBuilder.toString();
}
二、测试
下面我们从冲突率与时间性能上,对以上几种唯一ID的生成方式进行一个简单的测试,同时基于并发安全的考虑,测试分为单线程与多线程两种
// ---------------测试---------------
public static void main(String[] args) throws InterruptedException { int length = 10000000;
SnowFlake snowFlake = new SnowFlake(1, 1); final CountDownLatch countDownLatch = new CountDownLatch(10); Map<String, String> map = new ConcurrentHashMap<String, String>();
long begin = System.currentTimeMillis();
for(int i=0;i<10;i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i != 1000000; ++i) { String str = String.valueOf(snowFlake.nextId()); // String str = StringUtils.getUUIDHashCode(); //根据UUID产生命令唯一标识 长度 32 字母数字组合
//
// String str = StringUtils.getOrderSeq();//根据UUID取hash值+随机数,产生命令唯一标识 长度 16位纯数字
//
// String str =StringUtils.getRandomHexString(); //长度16的16进制字符串 map.put(str, str); }
countDownLatch.countDown(); }
}); thread.start();
} countDownLatch.await(); System.out.println("冲突数为: " + (length - map.size()));
System.out.println("sync time: " + (System.currentTimeMillis() - begin)); Map<String, String> map1 = new ConcurrentHashMap<String, String>();
begin = System.currentTimeMillis();
for (int i = 0; i != length; ++i) { String str = String.valueOf(snowFlake.nextId()); // String str = StringUtils.getUUIDHashCode();//根据UUID产生命令唯一标识 // String str = StringUtils.getOrderSeq();//根据UUID取hash值+随机数,产生命令唯一标识 // String str =StringUtils.getRandomHexString(); map1.put(str, str); }
System.out.println("冲突数为: " + (length - map1.size()));
System.out.println("sync time: " + (System.currentTimeMillis() - begin));
}
测试结果如下:
| 生成方式 | 生成总数 | 并发 | 冲突数 | 耗时 |
UUID产生命令唯一标识 |
1000W | 单线程 | 0 | 26166ms |
UUID产生命令唯一标识 |
1000W | 多线程 | 0 | 27915ms |
根据UUID取hash值+随机数,产生命令唯一标识 |
1000W | 单线程 | 0 | 25405ms |
根据UUID取hash值+随机数,产生命令唯一标识 |
1000W | 多线程 | 0 | 25023ms |
十六位随机的十六进制字符串 |
1000W | 单线程 | 0 | 25723ms |
十六位随机的十六进制字符串 |
1000W | 多线程 | 0 | 28094ms |
雪花算法 |
1000W | 单线程 | 0 | 10100ms |
雪花算法 |
1000W | 多线程 | 0 | 11713ms |
针对 Redis Incr 命令进行了本地和局域网两种测试, 由于千万级数据耗时太长,数据量改为了百万级,结果如下:
| 生成方式 | 网络环境 | 生成总数 | 并发 | 冲突数 | 耗时 |
| Redis Incr命令获取自增ID | 本地 | 100W | 单线程 | 0 | 72445ms |
| Redis Incr命令获取自增ID | 本地 | 100W | 多线程 | 0 | 47879ms |
| Redis Incr命令获取自增ID | 局域网 | 100W | 单线程 | 0 | 71447ms |
| Redis Incr命令获取自增ID | 局域网 | 100W | 多线程 | 0 | 45888ms |
|
Redis Incr命令生成 时间+递增ID 的唯一编码 |
局域网 | 100W | 单线程 | 0 | 236795ms |
|
Redis Incr命令生成 时间+递增ID 的唯一编码 |
局域网 | 100W | 多线程 | 0 | 39281ms |
可以看到Redis相比前面一些轻量级的ID生成方式,生成效率上有明显差距,但在分布式环境下,且业务场景对全局唯一ID的生成样式有要求, redis做为统一的ID生成器还是很有必要的。
由于测试受机器配置、网络带宽等条件影响,以上得出的结果只是一个简单的测试结果,证明这几种唯一ID生成方式具备一定的可用性,大家如果有兴趣可以进行更深入的测试与优化;
三、总结
其实在日常开发中唯一ID的的生成方式与整体服务的架构与复杂度是密切相关的,本文从并发安全、冲突率、性能等多个方面列举了几种唯一ID的生成方式,相对比较简单实用,但在更复杂的架构与业务场景下,对唯一ID生成的方式的考量就需要更加全面,如并发量、持久化、获取方式等,都需要具体问题具体分析,这里不再深入探讨,希望本文对大家能有所帮助,其中如有不足与不正确的地方还望指出与海涵。
关注微信公众号,查看更多技术文章。

实用向—总结一些唯一ID生成方式的更多相关文章
- 全局唯一ID的生成方式
一.程序直接生成: 使用jdk中的concurrent包可以轻松实现唯一数字型ID的生成,且无需考虑单例.采用高效率的CAS无需考虑synchronized关键字 import java.util.c ...
- 分布式唯一ID生成服务
SNService是一款基于分布式的唯一ID生成服务,主要用于提供大数量业务数据建立唯一ID的需要;服务提供最低10K/s的唯一ID请求处理.如果你部署服务的CPU资源达到4核的情况下那该服务最低可以 ...
- 如何在高并发分布式系统中生成全局唯一Id
月整理出来,有兴趣的园友可以关注下我的博客. 分享原由,最近公司用到,并且在找最合适的方案,希望大家多参与讨论和提出新方案.我和我的小伙伴们也讨论了这个主题,我受益匪浅啊…… 博文示例: 1. ...
- 高并发分布式系统中生成全局唯一Id汇总
数据在分片时,典型的是分库分表,就有一个全局ID生成的问题.单纯的生成全局ID并不是什么难题,但是生成的ID通常要满足分片的一些要求: 1 不能有单点故障. 2 以时间为序,或者ID里包含时间 ...
- 分布式系统唯一ID生成方案汇总
系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结.生成ID的方法有很多,适应不同的场景.需求以及性能要求.所以有些比较复杂的系统会有多个ID生成的策略.下面就介绍一些常见 ...
- 全局唯一ID发号器的几个思路
标识(ID / Identifier)是无处不在的,生成标识的主体是人,那么它就是一个命名过程,如果是计算机,那么它就是一个生成过程.如何保证分布式系统下,并行生成标识的唯一与标识的命名空间有着密不可 ...
- 人人网框架导入uidGenerator的ID生成方式
人人网框架导入uidGenerator的ID生成方式 2019-03-11 LIUREN SpringBoot2.0 uidGenerator SpringBoot2.0 uidGener ...
- [转]分布式系统唯一ID生成方案汇总
系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结.生成ID的方法有很多,适应不同的场景.需求以及性能要求.所以有些比较复杂的系统会有多个ID生成的策略.下面就介绍一些常见 ...
- 分布式系统唯一ID生成方案汇总 转
系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结.生成ID的方法有很多,适应不同的场景.需求以及性能要求.所以有些比较复杂的系统会有多个ID生成的策略.下面就介绍一些常见 ...
随机推荐
- python读文件出现错误解决方法
python读文件经常会出现 UnicodeDecodeError: 'gbk' codec can't decode byte 0xbd in position 764: illegal multi ...
- 使用Seq搭建免费的日志服务
Seq简介 Seq是老外开发的一个针对.NET平台非常友好的日志服务.支持容器部署,提供一个单用户免费的开发版本. 官网:https://datalust.co/seq 使用文档:https://do ...
- 启用valgrind的MPI支持
TL;DR sudo apt install valgrind-mpi 内存泄漏和越界问题,是C/C++程序常见问题.有一些工具提供了检测内存泄漏的功能,如 valgrind 的 memchecker ...
- DNSPod 修改NS 服务器?
其实我几乎没在国内注册过域名,更没想过用国内的DNS 服务,DNSPod 也是属于听说过名字的地步而已,但是正好在腾讯云注册了一个cn 域名,又觉得对DNSPod 的DNS 服务不是特别满意,所以想把 ...
- 一篇文章教会你使用Java8中的Lambda表达式
简介 Java 8为开发者带来了许多重量级的新特性,包括Lambda表达式,流式数据处理,新的Optional类,新的日期和时间API等.这些新特性给Java开发者带来了福音,特别是Lambda表达式 ...
- Elementor如何隐藏页面上的标题(2种办法)
原文首发于:https://loyseo.com/how-to-hide-page-title-in-elementor/ 本文介绍两种隐藏Elementor页面默认标题的方法,一种是单个隐藏,一种是 ...
- 力扣Leetcode 572. 另一个树的子树
另一个树的子树 给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树.s 的一个子树包括 s 的一个节点和这个节点的所有子孙.s 也可以看做它自身的一棵子树. 示例 ...
- 【Pod Terminating原因追踪系列之二】exec连接未关闭导致的事件阻塞
前一阵有客户docker18.06.3集群中出现Pod卡在terminating状态的问题,经过排查发现是containerd和dockerd之间事件流阻塞,导致后续事件得不到处理造成的. 定位问题的 ...
- linux字符串转数字
方法一: [root@ffcs211 test_dir]# echo "96.56"| awk '{print int($0)}' 输出结果 96 方法二: A="2&q ...
- 【Linux利用远程SSH连接】SecureCRT中文出现乱码解决办法 Linux服务器技术
1. 修改远程linux机器的配置 vim /etc/sysconfig/i18n 把LANG改成支持UTF-8的字符集 如: LANG="zh_CN.UTF-8″ 或者 ...