雪花算法生成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 ...
随机推荐
- golang string、int、int64 float 互相转换
#string到int int,err := strconv.Atoi(string) #string到int64 int64, err := strconv.ParseInt(string, 10, ...
- 排序算法总结(java实现)
排序算法 介绍:排序分为内部排序和外部排序,内部排序指在内存中进行的排序过程:外部排序指在外存中进行排序的过程,但是此过程建立在多次访问内存的基础上(分成一段段利用内部排序进行排序). 以下排序均属于 ...
- html外部文件读取/写入
1.文件的读取 外部文件读取控件: <input type="file" id="file_jquery" onchange="file_jqu ...
- 使用ffmpeg切片出hls
测试的时候经常要用到ffmpeg切hls的流 ffmpeg -re -i g:/media/baifa.mp4 -codec copy -f hls -hls_list_size -hls_wrap ...
- Computer Network Homework3’ s hard question
Computer Network Homework3’ s hard question 1. Which kind of protocol does CSMA belong to? A. Random ...
- MVVM中间接使用事件(命令)
在使用MVVM模式时, 按照模式的规则是尽量不直接使用事件. 所以对于以前一直使用事件模式的同行来说确实有点头疼. 还好微软给我们提供了几种间接使用事件(命令)的方法, 下面我就来看看这几种方法: I ...
- ALV SCRIPTFROM 内容比较全的一个例子
REPORT ZQM_PRT04. *----------------------------------------------------------------------* * TABLES ...
- 由于SID连接不匹配,监听器拒绝连接。
java.sql.SQLException: Listener refused the connection with the following error:ORA-12505, TNS:liste ...
- 搜索框的测试checklist
一:简单搜索输入框测试用例1:不输入任何字符,点击搜索按钮,一般搜索出网站所有的信息 2:一般搜索输入框中的有文章显示,当鼠标点击时,文章消失 3:输入全角/半角中文字符(一个字符.超长字符.已经信息 ...
- Java多线程ThreadLocal介绍
在Java多线程环境下ThreadLocal就像一家银行,每个线程就是银行里面的一个客户,每个客户独有一个保险箱来存放金钱,客户之间的金钱不影响. private static ThreadLocal ...