雪花算法及爬坑学习

雪花算法的原理

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

  1. 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
  2. 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 年。
  3. 10bit-工作机器id,用来记录工作机器id。
    – 可以部署在2^10 = 1024 个节点,包括5位datacenterId和5位workerId
    – 5位(bit)可以表示的最大正整数是2 ^ 5 -1,即可以用0、1、2、3、…31这32个数字,来表示不同的datecenterId或workerId。
  4. 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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值