记一次雪花算法引发的故障

861 阅读3分钟

1、先看现象

image.png

第三条记录由于员工接口仅能查询在职员工,历史信息无法返回。这里做了兼容处理,直接展示账号id。

但用这个id去数据库里面查询返回的结果如下:

image.png

居然查询不到,这个表里面是所有员工的账号信息,包括离职员工的。

2、原因分析

那么先确定代码有没有问题,既然在职员工能正常返回。说明代码逻辑应该没有问题的。那么这个记录的创建人的id到底是不是这个值呢。 我们这个id是通过雪花算法生产的,具体的生成代码逻辑如下:

/**
 * 获得下一个ID (该方法是线程安全的)
 *
 * @return SnowflakeId
 */
public synchronized long nextId() {
    long timestamp = timeGen();

    //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
    if (timestamp < lastTimestamp) {
        throw new RuntimeException(
                String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
    }

    //如果是同一时间生成的,则进行毫秒内序列
    if (lastTimestamp == timestamp) {
        sequence = (sequence + 1) & sequenceMask;
        //毫秒内序列溢出
        if (sequence == 0) {
            //阻塞到下一个毫秒,获得新的时间戳
            timestamp = tilNextMillis(lastTimestamp);
        }
    }
    //时间戳改变,毫秒内序列重置
    else {
        sequence = 0L;
    }

    //上次生成ID的时间截
    lastTimestamp = timestamp;

    //移位并通过或运算拼到一起组成64位的ID
    return ((timestamp - twepoch) << timestampLeftShift)
            | (dataCenterId << dataCenterIdShift)
            | (workerId << workerIdShift)
            | sequence;
}

它生成的id都是比较长的。

2.1、真实值

我们把这个接口的返回值仔细分析一下:

{
    "creator": 1342811569921765378,
    "gmtCreate": "2024-04-02 14:46:12",
    "gmtModify": "2024-04-02 14:46:15",
    "deleteBranchFlag": 0
}

看到接口里面返回的居然是 1342811569921765378,这个就有点意外了。

2.2、JavaScript对long型数字处理的失真

image.png 这里能明显看到在转换为数字的时候失真了。

到底在JavaScript中能够表达的最大数字是多少呢,查询官方资料得到如下结果。developer.mozilla.org/zh-CN/docs/… 它能表达的最大数字是9007199254740991。如果超过它需要使用bigInt。

2.3、哪一步失真的?

接口返回结果里面的值是正确的,而且都是用字符串格式返回。由于返回的格式是Json。

return efetch(holeUrl, requestInit).then(
  (response) => {
    let json = response.json();
    let status = response.status;
    // 登录态失效,返回登录页面
    if(status === 417 || status === 418){
      logout();
    }
    return json;
  }
);

这里response.json()直接返回了Json对象,在这一步就出现了失真。

3、如何解决

这个记得以前处理订单编号也出现同样的现象。Java的Long对象的精度比js的高。彻底解决,就是对这种超长的数字,返回模型直接处理为字符串。避免由parse过程出现失真。

{
    "creator": "1342811569921765378",
    "gmtCreate": "2024-04-02 14:46:12",
    "gmtModify": "2024-04-02 14:46:15",
    "deleteBranchFlag": 0
}

如上格式即可解决。