雪花算法的原理
SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的。
这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。

- 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
- 41bit-时间戳,用来记录时间戳,毫秒级。
– 41位可以表示 2^41 - 1个数字。
– 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 2^41-1,减1是因为可表示的数值范围是从0开始算的,而不是1。
– 也就是说41位可以表示 2^41 - 1个毫秒的值,转化成单位年则是( 2^41-1)/(1000 * 60 * 60 * 24 * 365)= 69 年。 - 10bit-工作机器id,用来记录工作机器id。
– 可以部署在2^10 = 1024 个节点,包括5位datacenterId和5位workerId
– 5位(bit)可以表示的最大正整数是2 ^ 5 -1,即可以用0、1、2、3、…31这32个数字,来表示不同的datecenterId或workerId。 - 12bit-序列号,序列号,用来记录同毫秒内产生的不同id。
– 12位(bit)可以表示的最大正整数是,即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。
由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。
java代码实现
public class SnowflakeIdWorker {
private final long twepoch = 1530028800000L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = 31L;
private final long maxDatacenterId = 31L;
private final long sequenceBits = 12L;
private final long workerIdShift = 12L;
private final long datacenterIdShift = 17L;
private final long timestampLeftShift = 22L;
private final long sequenceMask = 4095L;
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public void setWorkerId(long workerId) {
this.workerId = workerId;
}
public void setDatacenterId(long datacenterId) {
this.datacenterId = datacenterId;
}
public long getMaxWorkerId() {
return 31L;
}
public long getMaxDatacenterId() {
return 31L;
}
public SnowflakeIdWorker(long workerId, long datacenterId) {
if (workerId <= 31L && workerId >= 0L) {
if (datacenterId <= 31L && datacenterId >= 0L) {
this.workerId = workerId;
this.datacenterId = datacenterId;
} else {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", 31L));
}
} else {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", 31L));
}
}
public synchronized long nextId() {
long timestamp = this.timeGen();
if (timestamp < this.lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", this.lastTimestamp - timestamp));
} else {
if (this.lastTimestamp == timestamp) {
this.sequence = this.sequence + 1L & 4095L;
if (this.sequence == 0L) {
timestamp = this.tilNextMillis(this.lastTimestamp);
}
} else {
this.sequence = 0L;
}
this.lastTimestamp = timestamp;
return timestamp - 1530028800000L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence;
}
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp;
for(timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) {
}
return timestamp;
}
protected long timeGen() {
return System.currentTimeMillis();
}
private String now() {
String nowStr = LocalDate.now().toString().replace("-", "");
return nowStr;
}
}
前端接收精度丢失问题处理
1.现象描述
建表,表的主键是id BigINT,用来存储雪花算法生成的ID:
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
#其他字段省略
);
使用Long 类型对应数据库ID数据,雪花算法生成的就是一串数字,Long类型属于标准答案。
@Data
public class User {
private Long id;
}
在后端下断点。看到数据响应以JSON响应给前端,正常
{
id:1297873308628307970,
//其他属性省略
}
最后,这条数据返回给前端,前端接收到之后,修改这条数据,后端再次接收回来。奇怪的问题出现了:后端重新接收回来的id变成了:12978733086283000000,不再是1297873308628307970。
1297873308628300000 ---> 1297873308628307970
这两个数长得还挺像的,似乎是被四舍五入了。此时脑袋里面冒出一个想法,是精度丢失了么?哪里能导致精度丢失?
- 服务端都是Long类型的id,不可能丢失
- 前端是什么类型,JSON字符串转js对象,接收Long类型的是number
Number精度是16位(雪花ID是19位的),So:JS的Number数据类型导致的精度丢失。
2.处理方案
1、id字段由Long类型改成String类型;(表数量过多不建议)
2、后端的ID(Long) ==> Jackson(Long转String) ==> 前端使用String类型的ID,前端使用js string精度就不会丢失了。
在Spring Boot应用中,使用Jackson进行JSON序列化的时候怎么将Long类型ID转成String响应给前端。方案如下:
@Configuration
public class JacksonConfig {
@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder)
{
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 全局配置序列化返回 JSON 处理
SimpleModule simpleModule = new SimpleModule();
//JSON Long ==> String
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}
参考链接
雪花算法的原理及使用:https://blog.csdn.net/lq18050010830/article/details/89845790
雪花算法的实现:https://www.jianshu.com/p/2a27fbd9e71a
雪花算法Id前端接收精度丢失:https://www.cnblogs.com/zimug/p/13557662.html

3451

被折叠的 条评论
为什么被折叠?



