其他分享
首页 > 其他分享> > 分布式id生成之雪花id

分布式id生成之雪花id

作者:互联网

前言
为什么需要分布式id,因为现在的系统都是集群、分布式的,依赖于数据库的自增id在量大的时候肯定扛不住。网上介绍分布式id的文章很多,现在都是倾向于应用雪花算法生成分布式id,可以一定程度上不依赖redis等中间件。但是看文章的时候发现很多文章有写错的地方,例如:workerId = Netutil.ipv4ToLong(NetUtil.getLocalhoststr());生成的机器id并没有应用,所以我决定写一篇总结性的文章进行答疑。

1.id生成需要注意什么:
特点 说明
全局唯一 不能出现重复的ID号,既然是唯一标识,这是最基本的要求
趋势递增 在MySQL的innoDB引擎中使用的是聚集索引,由于多数RDBMS使用Btree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能
单调递增 保证下一个ID大于上一个ID,例如事务版本号、IM增量信息、排序等特殊需求
信息安全 如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可 ,所以在一些应用场景下,需要ID无规则 不规则,让竞争对手不好猜
含时间戳 这样就能在开发中快速了解分布式id的生成时间
2.id生成的性能考核:
特点 说明
高可用 发一个获取分布式ID的请求,服务器就要保证99.999%的情况下给我创建一个唯一分布式ID
低延迟 发一个获取分布式ID的请求,服务器就要快,极速
高QPS 假如并发一口气创建分布式ID请求同时杀过来,服务器要顶得住且一下子成功创建10万
3.通用方案的优势和劣势对比:
方案 优点 缺点 备注
UUID 性能非常高:本地生成,没有网络消耗,无序,如果只考虑唯一性,OK 入数据库性能差 mysql的官方建议主键要尽量的越短越好
数据库自增主键 适合单机 不适合分布式
基于redis生成全局id策略 提供原子类INCR和INCRBY操作递增,满足性能需求 需要维护redis集群 如果有集群的话可以采用
4.重点介绍雪花算法生成:
图片介绍其原理: 图片名词解释:

名词 解释 备注
1位 不用。二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0 就是固定的0
41位 用来记录时间戳(毫秒) 41位可以表示 2 41 − 1 2^{41}-1 241−1个数字,如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 241 − 1 2{41}-1 241−1,减1是因为可表示的数值范围是从0开始算的,而不是1,也就是说41位可以表示 241 − 1 2{41}-1 241−1个毫秒的值,转化成单位年则是 ( 2 41 − 1 ) / ( 1000 ∗ 60 ∗ 60 ∗ 24 ∗ 365 ) = 69 (2^{41}-1) / (1000 * 60 * 60 * 24 * 365) = 69 (241−1)/(1000∗60∗60∗24∗365)=69年
10位 用来记录工作机器id 可以部署在 2 10 = 1024 2^{10} = 1024 210=1024个节点,包括 5位datacenterId 和 5位workerId,5位(bit)可以表示的最大正整数是 2 5 − 1 = 31 2^{5}-1 = 31 25−1=31,即可以用0、1、2、3、…31这32个数字,来表示不同的datecenterId或workerId
12位 序列号,用来记录同毫秒内产生的不同id 12位(bit)可以表示的最大正整数是 2 12 − 1 = 4095 2^{12}-1 = 4095 212−1=4095,即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号
说明:java中64bit刚好就是整数long类型,所以SnowFlake雪花算法中的id是long 类型存储。

这样的设计保证:1.生成的id按时间的趋势递增,保证有序,2.整个集群部署服务、分布式部署服务因为参数workerId和datacenterId的设计不会产生重复id 当然雪花id的使用的公认缺点是依赖机器时钟,如果机器时钟回拨,会导致重复ID生成在单机上是递增的。但是这种一般能够从运维角度避免掉。

其他大厂分布式id开源框架介绍:

厂商 说明
美团(Leaf) Leaf由美团开发,github地址:https://github.com/Meituan-Dianping/Leaf Leaf同时支持号段模式和snowflake算法模式,可以切换使用.
滴滴(Tinyid) Tinyid由滴滴开发,Github地址:https://github.com/didi/tinyid。 Tinyid是基于号段模式原理实现的与Leaf如出一辙,每个服务获取一个号段(1000,2000]、(2000,3000]、(3000,4000]
百度(uid-generator) uid-generator是由百度技术部开发,项目GitHub地址 https://github.com/baidu/uid-generatoruid-generator是基于Snowflake算法实现的,与原始的snowflake算法不同在于,uid-generator支持自定义时间戳、工作机器ID和 序列号 等各部分的位数,而且uid-generator中采用用户自定义workId的生成策略。uid-generator需要与数据库配合使用,需要新增一个WORKER_NODE表。当应用启动时会向数据库表中去插入一条数据,插入成功后返回的自增ID就是该机器的workId数据由host,port组成。workId,占用了22个bit位,时间占用了28个bit位,序列化占用了13个bit位,需要注意的是,和原始的snowflake不太一样,时间的单位是秒,而不是毫秒,workId也不一样,而且同一应用每次重启就会消费一个workId。
错误举例:代码:

import cn.hutool.core.lang.Snowflake;import cn.hutool.core.util.Idutil;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct ;@Slf4j@Componentpublic class IdGeneratorSnowflake
private 1ong workerId = 0; private long datacenterId = 1; private Snowflake snowflake = Idutil.createsnowflake(workerId , datacenterId);
//依赖注入完成后执行该方法,进行一些初始化工作
@PostConstruct
public void init() { try{
workerId = Netutil.ipv4ToLong(NetUtil.getLocalhoststr());
log.info(“当前机器的workerId: {}”,workerId);
}catch (Exception e){
e.printStackTrace();
log.warn(“当前机器的workerId获取失败” ,e); //释放ID
workerId = NetUtil.getLocalhostStr() . hashCode();
}
} //使用默认机房号获取ID
public synchronized 1ong snowflakeId( ) { return snowflake.nextId( );
} //自己制定机房号获取ID
public synchronized 1ong snowflakeId(long workerId, 1ong datacenterId) {
Snowflake snowflake = Idutil.createSnowflake (workerId, datacenterId); return snowflake.nextId( );
} /**
生成的是不带-的字符审,类似于: 73a64edf935d4952a287739a66f96e06
* @return
/
public String simpleUUID() { return IdUtil.simpleUUID();
} /

*生成的UUID是带-的字符串,类似于: b12b6401-6f9c-4351-b2b6-d8afc9ab9272
* @return
*/
public String randomUUID() { return IdUtil. randomUUID();
}
public static void main(String[] args)
System. out . println( new IdGeneratorSnowflake().snowflakeId());
}
}
上面的代码在大部分文章中都能看到,看着也生成workerId了,但是其实是没用到的,所以根本不起作用。并且上面的ip转long虽然是数字的,但是雪花算法里面的workerId是限制在0到31的,所以的话上面的代码示例是错误的,估计误导了一大批。争取的做法:

private static Long getWorkId(){ try {
String hostAddress = Inet4Address.getLocalHost().getHostAddress(); int[] ints = StringUtils.toCodePoints(hostAddress); int sums = 0; for(int b : ints){
sums += b;
} return (long)(sums % 32);
} catch (UnknownHostException e) { // 如果获取失败,则使用随机数备用
return RandomUtils.nextLong(0,31);
}
}
private static Long getDataCenterId(){ int[] ints = StringUtils.toCodePoints(SystemUtils.getHostName()); int sums = 0; for (int i: ints) {
sums += i;
} return (long)(sums % 32);
}
上面两个方法用到了依赖:

org.apache.commons commons-lang3 3.8 最终完整的代码:

package com.my.blog.website.utils;
import org.apache.commons.lang3.RandomUtils;import org.apache.commons.lang3.StringUtils;import org.apache.commons.lang3.SystemUtils;
import java.net.Inet4Address;import java.net.UnknownHostException;
/**

4.1最简单的使用分布式雪花id:
引入依赖:

cn.hutool hutool-captcha ${hutool.version}
使用代码:

@Component@Slf4jpublic class SnowflakeConfig { @JsonFormat(shape = JsonFormat.Shape.STRING)// private long workerId = getWorkId();//为终端ID
private long dataCenterId = 1;//数据中心ID
private Snowflake snowflake = IdUtil.createSnowflake(getWorkId(),dataCenterId); public synchronized long snowflakeId(){ return snowflake.nextId();
} public synchronized long snowflakeId(long workerId,long dataCenterId){
Snowflake snowflake = IdUtil.createSnowflake(workerId, dataCenterId); return snowflake.nextId();
} /**
* @Author gj
* @Description 获取机器id
* @Params
* @Return
* @Date 2021/5/26 13:52
/
private static Long getWorkId(){ try {
String hostAddress = Inet4Address.getLocalHost().getHostAddress(); int[] ints = StringUtils.toCodePoints(hostAddress); int sums = 0; for(int b : ints){
sums += b;
} return (long)(sums % 32);
} catch (UnknownHostException e) { // 如果获取失败,则使用随机数备用
return RandomUtils.nextLong(0,31);
}
} /
*
* @Author gj
* @Description获取数据id
* @Params
* @Return
* @Date 2021/5/26 13:52
*/
private static Long getDataCenterId(){ int[] ints = StringUtils.toCodePoints(SystemUtils.getHostName()); int sums = 0; for (int i: ints) {
sums += i;
} return (long)(sums % 32);
}
}
单元测试进行注入调用即可:

@Autowired
SnowflakeConfig snowflakeConfig;

@Test    public  void contextLoads() {        for (int i = 0; i < 10; i++) {
        System.out.println(snowflakeConfig.snowflakeId());
    }
}

自己实现雪花算法代码:

/**

标签:return,workerId,雪花,private,id,long,ID,分布式
来源: https://blog.csdn.net/u010720120/article/details/117607939