雪花Id(snowflake)主键(Long) ,前端丢失精度原理及解决之道

雪花Id(snowflake)主键(Long) ,前端丢失精度原理及解决之道

1、案例场景
我们项目之中可能大多数都使用自增长id作为主键id,这样对于中小型系统来说一般没有啥问题。但是随着各种业务发展以及后续可能分库分表情况;我们有些项目或者项目之中的独立微服务可能使用的是雪花算法生成的主键id(19位)长整型的。于是最近我们在项目对接其他微服务(使用的雪花主键id)之后,在前端调用接口之后发现返回结果id被裁减了。经过组内同事定位📌Long型精度损失。具体原因下面原理部分分析。

2、雪花(snowflake)Id,Long id前端精度损失原因
本人试验以及结合网上的文章,发现一个问题,在PostMan里面请求接口能够完全正确的返回雪花算法生成的主键id,但是在Swagger或者前端接口请求 浏览器之中就会出现雪花算法精度损失问题。于是查找原因如下:

JavaScript 不支持后台返回的 Long 类型,JavaScript 的 number 类型的数值范围是 -253~253(不包含边界) 所以大于 9007199254740991 的数,进制转换会存在精度问题,而雪花ID生成的数值过大,导致 JavaScript 不能正常存储导致。

同时注意:建议雪花ID在数据库中使用 bigint 来存储,而不是使用 varchar;这样可以提高这样数据库的速度,使用索引的时候少一步字符串转换成数字的操作。

于是我的实际项目之中返回的雪花算法的主键id
在这里插入图片描述
于是就出现了 前端传递的id,找寻不到对应的记录问题。

3、解决JavaScript Number长整型精度丢失问题

解决方案1

使用Jackson注解,我们也可以用@JsonFormat做类型转换

在VO类中,id上添加注释

@ApiModelProperty(name = "id", value = "消息id")
@JsonProperty("id")
//此句为问题关键 相当于吧Long转换为String
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
//此处标识得在序列化的时候转换为字符串
private Long id;

其他相关:

@JSONField(serializeUsing= ToStringSerializer.class)

这个注解是Fastjson的,旨在让系统系列化时,保留相关精度。

解决方案2

写一个 JSON 配置类,将 Long 类型序列化成 JSON 的时候自动转为 String 类型

@Configuration
public class JacksonConfiguration {
 
    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
    private String pattern;
 
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            // Long 会自定转换成 String
            builder.serializerByType(Long.class, ToStringSerializer.instance);
        };
    }
}

4、总结
在解决的问题的过程之中一定要细致分析两者的之间不同的细小差异,这样才能发现问题得蛛丝马迹。比如本问题之中发现最后Id最后两位的id字段丢失。这样才能作为解决问题得突破口。

————————————————
版权声明:本文为CSDN博主「蜀山雪松」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jianxia801/article/details/124034856

Java中的雪花算法(Snowflake)可以用于生成唯一的主键ID。下面是一个简单的实现示例: ```java public class SnowflakeIdGenerator { private final static long START_TIMESTAMP = 1614556800000L; // 设置起始时间戳,这里假设为2021-03-01 00:00:00 private final static long DATA_CENTER_ID_BITS = 5L; // 数据中心ID所占的位数 private final static long WORKER_ID_BITS = 5L; // 工作机器ID所占的位数 private final static long SEQUENCE_BITS = 12L; // 序列号所占的位数 private final static long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS); // 支持的最大数据中心ID数量 private final static long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); // 支持的最大工作机器ID数量 private final static long WORKER_ID_SHIFT = SEQUENCE_BITS; // 工作机器ID左移位数 private final static long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; // 数据中心ID左移位数 private final static long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS; // 时间戳左移位数 private final static long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); // 序列号掩码 private long workerId; // 工作机器ID private long dataCenterId; // 数据中心ID private long sequence = 0L; // 序列号 private long lastTimestamp = -1L; // 上次生成ID的时间戳 public SnowflakeIdGenerator(long dataCenterId, long workerId) { if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) { throw new IllegalArgumentException("Data center ID can't be greater than " + MAX_DATA_CENTER_ID + " or less than 0"); } if (workerId > MAX_WORKER_ID || workerId < 0) { throw new IllegalArgumentException("Worker ID can't be greater than " + MAX_WORKER_ID + " or less than 0"); } this.dataCenterId = dataCenterId; this.workerId = workerId; } public synchronized long generateId() { long currentTimestamp = System.currentTimeMillis(); if (currentTimestamp < lastTimestamp) { throw new RuntimeException("Clock moved backwards. Refusing to generate ID for " + (lastTimestamp - currentTimestamp) + " milliseconds"); } if (currentTimestamp == lastTimestamp) { sequence = (sequence + 1) & SEQUENCE_MASK; if (sequence == 0) { currentTimestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = currentTimestamp; return ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT) | (dataCenterId << DATA_CENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence; } private long tilNextMillis(long lastTimestamp) { long timestamp = System.currentTimeMillis(); while (timestamp <= lastTimestamp) { timestamp = System.currentTimeMillis(); } return timestamp; } } ``` 使用示例: ```java public class Main { public static void main(String[] args) { // 创建一个SnowflakeIdGenerator实例,传入数据中心ID和工作机器ID SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1); // 生成ID long id = idGenerator.generateId(); System.out.println("Generated ID: " + id); } } ``` 这样就可以使用雪花算法在Java中生成唯一的主键ID了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值