雪花算法生成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 ...
随机推荐
- 代理模式与AOP
代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联 ...
- 套接字选项 之 SO_REUSEADDR && SO_REUSEPORT
说明 本文下面内容基本上是截取自stackoverflow,针对这两个选项,在另外一篇文章中做了总结,请移步<Linux TCP套接字选项 之 SO_REUSEADDR && S ...
- python3笔记二十三:正则表达式之其他函数
一:学习内容 re.split函数 re.finditer函数 re.sub函数 group()分组 re.compile函数 二:字符串切割---re.split函数 需要导入包:import re ...
- 【Spark机器学习速成宝典】模型篇05决策树【Decision Tree】(Python版)
目录 决策树原理 决策树代码(Spark Python) 决策树原理 详见博文:http://www.cnblogs.com/itmorn/p/7918797.html 返回目录 决策树代码(Spar ...
- vue指令之v-cloak
vue指令之v-cloak 一起学 vue指令 v-cloak 指令可看作标签属性 某些情况下可能由于机器性能故障或者网络原因,导致传输有问题,那么浏览器无法成功解析数据,此时浏览器输出的内容就是纯 ...
- 常用IDE 教程(IntelliJ IDEA、Android Studio、Chrome)
1.IntelliJ IDEA 使用教程 http://wiki.jikexueyuan.com/project/intellij-idea-tutorial/ 2.Chrome 开发工具指南 htt ...
- linux如何查看目录或文件夹的总大小--du命令
记录一下如何查看一个目录或文件夹的总大小. 使用du命令的选项-s,可以统计整个目录或文件夹的大小. 例如 du -sk ./ 156k -k表示以KB为单位计算.
- logistic regression中的cost function选择
一般的线性回归使用的cost function为: 但由于logistic function: 本身非凸函数(convex function), 如果直接使用线性回归的cost function的话, ...
- ControlTemplate in WPF —— Window
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" x ...
- ISO/ISO 参考模型 和 TCP/IP模型
OSI 参考模型 国际化标准组织(International Organization for Standardization,ISO)于1978年提出了一个网络体系结构,成为开放系统互联参考模型(O ...