源码下载地址: https://github.com/twitter-archive/snowflake/tags源码是scala写的…
java版本:https://gitee.com/xyy-kk_admin/data-source/blob/master/SnowflakeIdWorker.java Twitter的分布式自增ID算法snowflake
概述:
Twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra(由Facebook开发一套开源分布式NoSQL数据库系统)。因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一生成服务。
Twitter的分布式雪花算法SnowFlake ,经测试snowflake 每秒能够产生26万个自增可排序的ID
- Twitter的SnowFlake生成ID能够按照时间有序生成。
- SnowFlake算法生成ID的结果是一个64bit大小的整数, 为一个Long型(转换成字符串后长度最多19)。
- 分布式系统内不会产生ID碰撞(由datacenter和workerld作区分)并且效率较高。
分布式系统中,有一些需要使用全局唯一ID的场景, 生成ID的基本要求:
- 在分布式的环境下必须全局且唯一 。
- 一般都需要单调递增,因为一般唯一ID都会存到数据库,而Innodb的特性就是将内容存储在主键索引树上的叶子节点而且是从左往右,递增的,所以考虑到数据库性能,一般生成的ID也最好是单调递增。 为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点, 首先他相对比较长, 另外UUID一般是无序的。
- 可能还会需要无规则,因为如果使用唯一ID作为订单号这种,为了不然别人知道一天的订单量是多少,就需要这个规则。
结构:
解析
SnowFlake可以保证:
所有生成的ID按时间趋势递增。
整个分布式系统内不会产生重复id(因为有DataCenterId和Workerld来做区分)
HUTOOL工具类
引入工具包:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
最简单的方式:
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.log.Log;
import cn.hutool.log.dialect.log4j.Log4jLog;
public class IdGeneratorSnowflake {
public synchronized long snowflakeId(long workerID,long datacenterID){
Snowflake snowflake = IdUtil.createSnowflake(workerID,datacenterID);
return snowflake.nextId();
}
}
高效 但是复杂的方式:
package service;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.log.Log;
import cn.hutool.log.dialect.log4j.Log4jLog;
/**
* @author xyy
* @date 2021年07月09日 17:16
*/
public class IdGeneratorSnowflake {
Log log = new Log4jLog(IdGeneratorSnowflake.class);
// 工作设备号
private static long workerID= 0;
// 服务 编号
private static long datacenterID=1;
// 创建雪花算法对象
private static Snowflake snowflake = IdUtil.createSnowflake(workerID,datacenterID);
public IdGeneratorSnowflake(){
try{
//获取机器的ipV4 转换成 long 型
workerID = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
log.info("当前机器的workerID:{}",workerID);
}catch (Exception e){
e.printStackTrace();
log.error("当前机器的workerID获取失败",e);
workerID = NetUtil.getLocalhostStr().hashCode();
}
}
public static synchronized long snowflakeId(){
return snowflake.nextId();
}
public static void main(String[] args) {
System.out.println(IdGeneratorSnowflake.snowflakeId());
}
}
纯java代码
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.lang.generator.SnowflakeGenerator;
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import org.springframework.stereotype.Component;
/**
* 雪花算法 自定义ID生成器
*
* mybatis-plus使用 需要实体类加上@TableId(value = "id", type = IdType.ASSIGN_ID)
*
* @author xyy
* @DateTime 2022/11/30 18:18
* @ClassName CustomIdGenerator
*/
@Component
public class CustomIdGenerator implements IdentifierGenerator {
/** 工作机器ID(0~31) */
private static long workerId;
/** 数据中心ID(0~31) */
private static final long dataCenterId = 0;
/** 开始时间戳 (2015-01-01) */
private final long twepoch = 1420041600000L;
/** 机器id所占的位数 */
private final long workerIdBits = 5L;
/** 数据标识id所占的位数 */
private final long dataCenterIdBits = 5L;
/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
private final long maxWorkerId = ~(-1L << workerIdBits);
/** 支持的最大数据标识id,结果是31 */
private final long maxDatacenterId = ~(-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 << sequenceBits);
/** 毫秒内序列(0~4095) */
private long sequence = 0L;
/** 上次生成ID的时间戳 */
private long lastTimestamp = -1L;
//==============================Constructors=====================================
/**
* 构造函数
*/
public CustomIdGenerator() {
try{
//获取机器的ipV4 转换成 long 型
workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
}catch (Exception e){
e.printStackTrace();
workerId = NetUtil.getLocalhostStr().hashCode();
}
}
// ==============================Methods==========================================
/**
* 1. hutool 包下的
*/
// private static final Snowflake snowflake = IdUtil.getSnowflake(workerId);
// @Override
// public synchronized Long nextId(Object object) {
// return snowflake.nextId();
// }
/**
* 2. 自定义 获得下一个ID (该方法是线程安全的)
* @return SnowflakeId
*/
@Override
public synchronized Long nextId(Object object) {
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();
}
}