雪花算法生成ID
前言
我们的数据库在设计时一般有两个ID,自增的id为主键,还有一个业务ID使用UUID生成。自增id在需要分表的情况下做为业务主键不太理想,所以我们增加了uuid作为业务ID,有了业务id仍然还存在自增id的原因具体我也说不清楚,只知道和插入的性能以及db的要求有关。
我个人一直想将这两个ID换成一个字段来处理,所以要求这个id是数字类似的,且是趋抛增长的,这样mysql创建索引以及查询时性能会比较好。于时网上找到了雪花算法.关于雪花算法大家可以看一下我后面引用的资料。
ID生成器代码:
从网上抄的,自己改的,目前我还没有应用到实际项目中,如需应用,请先进行严格自测
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter; /**
* <p>
* 在雪花算法基础生稍做改造生成Long Id
* https://www.jianshu.com/p/d3881a6a895e
* </p>
* 1 - 41位 - 10位 - 12位
* 0 - 41位 - 10位 - 12位
* <p>
* <PRE>
* <BR> 修改记录
* <BR>-----------------------------------------------
* <BR> 修改日期 修改人 修改内容
* </PRE>
*
* @author cuiyh9
* @version 1.0
* @Date Created in 2018年11月29日 20:46
* @since 1.0
*/
public final class ZfIdGenerator { /**
* 起始的时间戳
*/
private static final long START_TIME_MILLIS; /**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long WORKID_BIT = 10; //机器标识占用的位数 /**
* 每一部分的最大值
*/
private final static long MAX_WORK_NUM = -1L ^ (-1L << WORKID_BIT);
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); /**
* 每一部分向左的位移
*/
private final static long WORKID_SHIFT = SEQUENCE_BIT;
private final static long TIMESTMP_SHIFT = WORKID_SHIFT + WORKID_BIT; private long sequence = 0L; //序列号
private long lastStmp = -1L; /** workId */
private long workId; static {
String startDate = "2018-01-01 00:00:00";
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.parse(startDate, df);
START_TIME_MILLIS = localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli(); } /**
* 获取分部署式发号器
* @param workId 每台服务需要传一个服务id
* @return
*/
public static synchronized ZfIdGenerator getDistributedIdGenerator(long workId) {
return new ZfIdGenerator(workId);
} public static synchronized ZfIdGenerator getStandAloneIdGenerator() {
long workId = MAX_WORK_NUM;
return new ZfIdGenerator(workId);
} private ZfIdGenerator(long workId) {
if (workId > MAX_WORK_NUM || workId <= 0) {
throw new RuntimeException("workdId的值设置错误");
}
this.workId = workId;
} /**
* 生成id
* @return
*/
public synchronized long nextId() {
long currStmp = System.currentTimeMillis();
if (currStmp < START_TIME_MILLIS) {
throw new RuntimeException("机器时间存在问题,请注意查看");
} if (currStmp == lastStmp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0L) {
currStmp = getNextMillis(currStmp);
}
} else {
sequence = 0L;
}
lastStmp = currStmp; return ((currStmp - START_TIME_MILLIS) << TIMESTMP_SHIFT)
| (workId << WORKID_SHIFT)
| (sequence);
} public long getNextMillis(long currStmp) {
long millis = System.currentTimeMillis();
while (millis <= currStmp) {
millis = System.currentTimeMillis();
}
return millis;
} /**
* 获取最大的工作数量
* @return
*/
public static long getMaxWorkNum() {
return MAX_WORK_NUM;
} public static void main(String[] args) {
ZfIdGenerator idGenerator1 = ZfIdGenerator.getDistributedIdGenerator(1);
// ZfIdGenerator idGenerator2 = ZfIdGenerator.getDistributedIdGenerator(2);
for (int i = 0; i < 1000000; i++) {
System.out.println(idGenerator1.nextId());
} // System.out.println(idGenerator2.nextId()); } }
分布式情况
上面的ID生成器在单机情况下使用没有问题,但如果在分布下使用,就需要分配不同的workId,如果workId相同,可能会导致生成的id相同。
解决方案:
1、使用java环境变量,人为通过-D预先设置workid.这种方案简单,不会出现重复情况,但需要每个服务的启动脚本不同.
2、使用sharding-jdbc中的算法,使用IP后几位来做workId,这种方案也很简单,不需要修改服务的启动脚本,但在某些情况下会出现生成重复ID的情况,详细见我下面的参考资料
3、使用zk,在启动时给每个服务分配不同的workId,缺点:多了依赖,需要zk,优点:不会出现重复情况,且不需要修改服务的启动脚本。这个是我个人使用的方案,实现思路为,系统启动时创建一个永久性的结点(zookeeper保证原子性),然后在这个永久性的节点下,遍历workId去zookeeper创建临时结点,zookeeper会保证相同路径只会有一个可能创建成功,如果创建失败继续遍历即可。详细可看一下代码
实例化ID生成器如下(Spring boot项目):
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean; /**
* <p>TODO</p>
* <p>
* <PRE>
* <BR> 修改记录
* <BR>-----------------------------------------------
* <BR> 修改日期 修改人 修改内容
* </PRE>
*
* @author cuiyh9
* @version 1.0
* @Date Created in 2018年11月30日 16:37
* @since 1.0
*/
@Slf4j
@SpringBootConfiguration
public class IdGeneratorConfig { @Autowired
private ZkClient zkClient; @Value("${idgenerator.zookeeper.parent.path}")
private String IDGENERATOR_PARENT_PATH; @Bean
public ZfIdGenerator idGenerator() {
boolean flag = zkClient.createParent(IDGENERATOR_PARENT_PATH);
if (!flag) {
throw new RuntimeException("创建发号器父节点失败");
} // 获取workId
long workId = 0;
long maxWorkNum = ZfIdGenerator.getMaxWorkNum();
for (long i = 1; i < maxWorkNum; i++) {
String workPath = IDGENERATOR_PARENT_PATH + "/" + i;
flag = zkClient.createNotExistEphemeralNode(workPath);
if (flag) {
workId = i;
break;
}
} if (workId == 0) {
throw new RuntimeException("获取机器id失败");
}
log.warn("idGenerator workId:{}", workId);
return ZfIdGenerator.getDistributedIdGenerator(workId); }
}
ZkClient代码(基于apache curator)
注意apache curator版本,我最初使用的是4.x版本,程序执行到forPath()方法就会阻塞,后来查到是与zookeeper版本不匹配导致.
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; /**
* <p>TODO</p>
* <p>
* <PRE>
* <BR> 修改记录
* <BR>-----------------------------------------------
* <BR> 修改日期 修改人 修改内容
* </PRE>
*
* @author cuiyh9
* @version 1.0
* @Date Created in 2018年11月30日 16:36
* @since 1.0
*/
@Slf4j
@Component
public class ZkClient { @Autowired
private CuratorFramework client; /**
* 创建父节点,创建成功或存在都返回成功
* @param path
* @return
*/
public boolean createParent(String path) {
try {
client.create().creatingParentsIfNeeded().forPath(path);
return true;
} catch (KeeperException.NodeExistsException e) {
return true;
} catch (Exception e) {
log.error("createParent fail path:{}", path, e);
}
return false;
} /**
* 创建不存在的节点。如果存在或创建失败,返回false
* @param path
* @throws Exception
*/
public boolean createNotExistEphemeralNode(String path) {
try {
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
return true;
} catch (KeeperException.NodeExistsException e) {
return false;
} catch (Exception e) {
log.error("createNotExistNode fail path:{}", path, e);
}
return false;
}
}
雪花算法生成ID的更多相关文章
- 分布式系统为什么不用自增id,要用雪花算法生成id???
1.为什么数据库id自增和uuid不适合分布式id id自增:当数据量庞大时,在数据库分库分表后,数据库自增id不能满足唯一id来标识数据:因为每个表都按自己节奏自增,会造成id冲突,无法满足需求. ...
- 分布式雪花算法获取id
实现全局唯一ID 一.采用主键自增 最常见的方式.利用数据库,全数据库唯一. 优点: 1)简单,代码方便,性能可以接受. 2)数字ID天然排序,对分页或者需要排序的结果很有帮助. 缺点: 1)不同数据 ...
- 基于雪花算法生成分布式ID(Java版)
SnowFlake算法原理介绍 在分布式系统中会将一个业务的系统部署到多台服务器上,用户随机访问其中一台,而之所以引入分布式系统就是为了让整个系统能够承载更大的访问量.诸如订单号这些我们需要它是全局唯 ...
- 雪花算法生成分布式ID
分布式主键ID生成方案 分布式主键ID的生成方案有以下几种: 数据库自增主键 缺点: 导入旧数据时,可能会ID重复,导致导入失败 分布式架构,多个Mysql实例可能会导致ID重复 UUID 缺点: 占 ...
- 雪花算法生成全局唯一ID
系统中某些场景少不了全局唯一ID的使用,来保证数据的唯一性.除了通过数据库自带的自增id来保证 id 的唯一性,通常为了保证的数据的可移植性会选择通过程序生成全局唯一 id.百度了不少php相关的生成 ...
- php实现雪花算法(ID递增)
雪花算法简单描述: 最高位是符号位,始终为0,不可用. 41位的时间序列,精确到毫秒级,41位的长度可以使用69年.时间位还有一个很重要的作用是可以根据时间进行排序. 10位的机器标识,10位的长度最 ...
- mybatis plus 主键生成 Twitter雪花算法 id 及修改id为字符型
mybatis plus配置主键生成策略为2,就是 使用Twitter雪花算法 生成id spring boot中配置为: GlobalConfiguration conf = new GlobalC ...
- 适用于分布式ID的雪花算法
基于Java实现的适用于分布式ID的雪花算法工具类,这里存一下日后好找 /** * 雪花算法生成ID */ public class SnowFlakeUtil { private final sta ...
- Snowflake(雪花算法),什么情况下会冲突?
文章首发在公众号(龙台的技术笔记),之后同步到博客园和个人网站:xiaomage.info 分布式系统中,有一些需要使用全局唯一 ID 的场景,这种时候为了防止 ID 冲突可以使用 36 位的 UUI ...
随机推荐
- .py与.pyc文件区别
原来Python的程序中,是把原始程序代码放在.py文件里,而Python会在执行.py文件的时候.将.py形式的程序编译成中间式文件(byte-compiled)的.pyc文件,这么做的目的就是为了 ...
- java面试要点
基础篇 基本功 面向对象的特征 final, finally, finalize 的区别 int 和 Integer 有什么区别 重载和重写的区别 抽象类和接口有什么区别 说说反射的用途及实现 说说自 ...
- centos7 apache php git pull
mkdir /usr/share/httpd/.ssh cp /root/.ssh/* /usr/share/httpd/.ssh chown -R apache:apache /usr/share/ ...
- String 类源码分析
String 源码分析 String 类代表字符序列,Java 中所有的字符串字面量都作为此类的实例. String 对象是不可变的,它们的值在创建之后就不能改变,因此 String 是线程安全的. ...
- Git-Runoob:Git 服务器搭建
ylbtech-Git-Runoob:Git 服务器搭建 1.返回顶部 1. Git 服务器搭建 上一章节中我们远程仓库使用了 Github,Github 公开的项目是免费的,但是如果你不想让其他人看 ...
- 在 Android 中进程的级别有哪些?
a) Foreground processb) Visible processc) Service processd) Background processe) Empty process
- flutter 网络请求以及数据处理
网络请求使用FutureBuilder来处理 import 'dart:convert'; Widget build(BuildContext context) { return FutureBuil ...
- nginx提示地址或端口被占用解决
nginx提示地址或端口被占用解决 今天小编在启动nginx 的时候遇到如下的错误 Starting nginx: nginx: [emerg] bind() to 0.0.0.0:80 failed ...
- 微信小程序UI学习
1.大纲: 2.flex的布局: 3.相对定位和绝对定位: position: relative 相对定位 position: absolute 绝对定位
- JMeter接口测试印象篇(win10)
参考博文1:https://www.cnblogs.com/suim1218/p/9257369.html 参考博文2:https://blog.csdn.net/u011541946/article ...