我们在做生成id算法的时候一般都要求满足以下几点
1.全局唯一
2.趋势递增
3.单调递增
4.信息安全
5.含时间戳
基于以上要求,雪花算法是一个非常不错的选择
概述
而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra(由Facebook开发一套 开源分布式NoSQL数据库系统)因
为Cassandra没有顺序ID生成机制,所以开发了这样一套 全局唯一-ID生成服务。
Twitter的分布式雪花算法SnowFlake,经测试snowflake每秒能够产生26万个自增可排序的ID
- 1、twitter的SnowFlake 生成ID能够按照时间有序生成
- 2、SnowFlake算法生 成id的结果是一个64bit大小的整数, 为- 个Long型(转换成字符串后长度最多19)。
- 3、分布式系统内不会产生ID碰撞(由
datacenter
数据中心和workerld
机器id作区分)并且效率较高。
分布式系统中,有一些需要使用全局唯一-ID的场景, 生成ID的基本要求
- 1.在分布式的环境下必须全局且唯一。
- 2.-般都需要单调递增,因为一般唯- -ID都 会存到数据库,而Innodb的特性就是将内容存储在主键索引树上的叶子节点,而且是从左往右,递增的,所以考虑到数据库性能,一般生成的id也最好是单调递增。为了防止ID冲突可以使用36位的UUID,但是UUID有一 -些缺点, 首先他相对比较长,另外UUID一般是无序的
算法核心
雪花算法的核心组成:![在这里插入图片描述](https://img-blog.csdnimg.cn/20200802141801198.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzg3NjEyMQ==,size_16,color_FFFFFF,t_70)
号段解析:
- 1bit,
不用,因为二进制中最高位是符号位,1表示负数,0表示正数。
生成的id- - 般都是用整数,所以最高位固定为0。
41bit-时间戳,用来记录时间戳,亳秒级。 - 41位可以表示2^{41}-1个数字,
如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是: 0至2^{41}-1, 减1是因为可表示的数值范围是从0开始算的,而不是1。也就是说41位可以表示2^{41}-1个毫秒的值,转化成单位年则是69.73年 - 10bit-工作机器id,用来记录工作机器id。
- 可以部署在2^{10} =1024个节点,包括5位datacenterld和5位workerld
- 5位(bit) 可以表示的最大正整数是2^{5}-1=31,即可以用0、1、2、3、…1这32个数字,来表示不同的datecenterld或workerld
- 12bit-序列号,序列号,用来记录同毫秒内产生的不同id。
12位(bit) 可以表示的最大正整数是2^{12}-1 = 4095, 即可以用0、1、2、3、…4094这4095个数字,来表示同一-机 器同- -时间 截(亳秒)内产生的4095个ID序号。
特点
1.所有生成的id有递增趋势
2.整个分布式系统不会产生重复id
其他生成id策略的缺点
- UUID只能保证唯一性,但是性能和效率都不高。
原因:UUID会生成36为的字符串,8-4-4-12的形式,数据库本身压力就很大,每次新增数据需要重新维护索引,造成的性能消耗十分严重。而且UUID是无序的,无法确定生成id的时间,当然我们可以在数据库存一下创建时间 - mysql自增,可以保证唯一性,递增,但是也会出现问题
mysql递增id的内核是replace into,也就是数据库替换新增,先尝试插入,如果数据库的主键或者唯一索引值重复,则替换老数据。而且性能不高,容易替换掉老数据。为了避免单机故障,就要在布集群,集群要自己做自增步长和号码间断。 - 使用redis写一个新增id策略,有不少公司是这么做的。
这么做就会导致我们为了生成id可能要特意搭建以下redis数据库,对于配置redis也是相对来说比较麻烦的。redis和上述mysql自增一样都要设置自增步长,而且还要设置key的过期时间,当然默认是不过期。
测试使用的代码
package com.jingchu.snowflake;
/**
* @description: 雪花算法生成id代码
* @author: JingChu
* @createtime :2020-08-02 14:08:01
**/
public class MySnowflake {
/**
* 起始的时间戳
*/
private final static long twepoch = 1557825652094L;
/**
* 每一部分占用的位数
*/
private final static long workerIdBits = 5L;
private final static long datacenterIdBits = 5L;
private final static long sequenceBits = 12L;
/**
* 每一部分的最大值
*/
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private final static long maxSequence = -1L ^ (-1L << sequenceBits);
/**
* 每一部分向左的位移
*/
private final static long workerIdShift = sequenceBits;
private final static long datacenterIdShift = sequenceBits + workerIdBits;
private final static long timestampShift = sequenceBits + workerIdBits + datacenterIdBits;
private long datacenterId; // 数据中心ID
private long workerId; // 机器ID
private long sequence = 0L; // 序列号
private long lastTimestamp = -1L; // 上一次时间戳
public MySnowflake(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;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & maxSequence;
if (sequence == 0L) {
timestamp = tilNextMillis();
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return (timestamp - twepoch) << timestampShift // 时间戳部分
| datacenterId << datacenterIdShift // 数据中心部分
| workerId << workerIdShift // 机器标识部分
| sequence; // 序列号部分
}
private long tilNextMillis() {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
MySnowflake mySnowflake = new MySnowflake(0,0);
for(int i=0;i<10;i++){
long id =mySnowflake.nextId();
System.out.println("id: \t"+id);
}
}
}
结果: