首页 > 编程语言 >关于读者阅读“改良版雪花算法”后提出的几个共性问题的回复

关于读者阅读“改良版雪花算法”后提出的几个共性问题的回复

时间:2023-08-10 13:12:37浏览次数:40  
标签:11 10 改良版 毫秒 算法 时间 共性 序列号 ID

你好呀,我是歪歪。

周一的时候不是发了《在开源项目中看到一个改良版的雪花算法,现在它是你的了。》这篇破文章嘛。

然后有好几个读者都提出了几个类似的问题,再写个续集,给大家解答一下。

我就喜欢这种和读者有来有回,相互拉扯的感觉。

突出一个“相互学习,共同进步。”

超前消费

首先大家都在纠结的一个点是,文章中提到的超前消费。

我还是把这个改良版本后的图拿过来:

我们先把“超前消费”这个问题框定在一个服务节点上。

假设序列号为 100 的服务节点上的序列号出现了“超前消费”的问题,这个 100 我是随便说的,反正就是代表我们整个系列号的中节点 ID 是固定的。

由于它是固定的,所以在接下来的讨论中,我们并不需要特别关注它。

当我们刨除了“节点ID”之后,那么只剩下 41 位的时间戳和 12 位的序列号:

时间戳在前面的文章中分析过,只是在服务启动的时候会获取一次。

那么我也假设当前的时间戳为 2023 年 08 月 11 日 10 点 55 分 00 秒 000 毫秒。

先说明一下,这个时间戳不考虑人为破坏,修改时区啥的动作,只考虑正常的时间回拨。

你要是考虑人为破坏了,那别玩了,你开玩笑,人心这玩意,是你程序能玩得过的?

好,基于现在的条件,请听题:

基于 Seata 改良版的雪花算法,节点 100 的初始 ID 是多少?

我们完全可以算出来嘛:

首先,第一位恒为 0,就不说了。

然后,节点ID(10位),100 的二进制:1100100。前面补 3 个 0,凑够 10 位,所以就是 0001100100。

接着,时间戳(41位),2023 年 08 月 11 日 10 点 55 分 00 秒 000 毫秒的时间戳为 1691722500000:

1691722500000-1588435200000=103287300000。

有的读者肯定就会问了:这个 1588435200000 是从哪里冒出来的呢?

别问,一问就不是真爱粉了。

前面的文章里面写了:

初始化的时候获取时间戳的代码是这样的:

总之,我们最终算出来的时间戳是 103287300000。

对应的二进制则是这样的:

1100000001100011001110001111110100000

这一串数字一共 37 位,在前面补 4 个 0,凑够 41 位就是这样的:

00001100000001100011001110001111110100000

最后,就是序列号了。

都说了是初始化的时候了,序列号肯定是 0 啊。

所以序列号就是 12 个 0:

000000000000

在程序里面的表现就是把时间戳左移 12 位,把序列号的位置给腾出来。

那么我们最终得到的一个 64 位的二进制就是这样的:

0000110010000001100000001100011001110001111110100000000000000000

歪师傅在给你上个图。

再把这个 64 位的二进制转化为 10 进制,就是这个数:

901142990254899200,就是节点 ID 为 100 的服务器在 2023 年 08 月 11 日 10 点 55 分 00 秒启动瞬间对应的初始化 ID。

听懂掌声。

有的读者肯定要说了:歪师傅,我们不是说超前消费的问题吗?

别急啊,前面不得给你铺垫铺垫,来得太猛我怕你受不了,现在正式说这个超前消费的问题。

现在铺垫完了,那么问题就来了:在我们此时的场景下,出现了什么情况,我们才说这是“超前消费”现象”?

是不是时间戳的最后一位变成了 1 的情况:

因为我们当前的时间是 2023 年 08 月 11 日 10 点 55 分 00 秒 000 毫秒。

但是分布式 ID 生成的时候,时间戳组成部分对应的时间是 2023 年 08 月 11 日 10 点 55 分 00 秒 001 毫秒。

快了 1 毫秒,超前消费 1 毫秒。

那么从程序的角度来看,什么时候会快上这 1ms 呢?

别问,问就不是真爱粉了。

前面的文章说过这个问题了:

就是说在序列号用完了的情况下。

那么序列号怎么才算是用完了呢?

也就是这种情况下,就算用完了:

但是需要注意的是,我们此时的真实时间还是 2023 年 08 月 11 日 10 点 55 分 00 秒 000 毫秒。

意思就是这一毫秒内,序列号从 0 递增到了 4095。

然后还是这一毫秒,还在继续申请 ID,那么序列号归 0,溢出位加到时间戳上,导致时间超前 1ms:

也就是说,只有 QPS 持续稳定在 4096/ms 之上,才有可能出现“超前消费”的情况。

4096/ms,注意我说的是 ms。

换算成秒是 409.6w/s。

QPS 持续稳定在 409.6w/s,你自己啥服务啊,有这么大流量吗,心里没点数?

而且,就算真的有这么大的流量,性能瓶颈一定不是在分布式 ID 生成器这里,前面一定有“高个子”顶着的。

也许你还是觉得看二进制不够直观,那我就给你来个直观的数据,让你感受一小小的震撼。

还是假设当前时间为 2023 年 08 月 11 日 10 点 55 分 00 秒 000 毫秒。

但是程序里面时间“超前消费”了一分钟,对应 2023 年 08 月 11 日 10 点 56 分 00 秒 000 毫秒。

那么按照前面我们的算法,此时对应的 64 位二进制为:

0000110010000001100000001100011010000000101000000000000000000000

对应 10 进制就是其 ID:

901142990500659200

用当前的这个 ID 减去我们之前算出来的 ID:

901142990500659200-901142990254899200=245760000

245,760,000,你自己数一数,2 亿多啊。

如果你想要把时间超前消费 1 分钟,需要持续在 409.6w/s 的流量下申请 2 亿多个流水号。

怎么样,有没有感受到一点点来自数据上的震撼。

所以,这个问题也就顺便回答了读者提出的另外一个问题。

如果当前真实时间是 2023-08 11 10:55:00:000。

而程序持续提前把时间消费到了 2023-08 11 10:56:00:000。

服务重启之后,时间是 2023-08 11 10:55:10:000。

那么由于获取到的时间戳是小于之前“提前消费”的时间戳,就是有可能生成之前重复的 ID。

是的,理论上可能,实际上,不可能会发生。

实际上可能会发生的时什么呢?

瞬时流量达到 4096/ms,出现毫秒级别的时间提前消费。

那么问题就来了:请问你能在毫秒级别完成重启吗?

听懂掌声。

时间戳和节点ID的位置

还有一个问的也很多的问题:为什么在改良版本里面,非要把时间戳和节点 ID 换位置,不换行不行?

看到这个问题的时候,我的回复是这样的:

我最开始想到的是,时间戳虽然只是在系统启动的时候获取一次,但是后续由于序列号一轮又一轮的摧残,还是会不断的被 +1。

那么把时间戳放在前面的话,会导致的现象就是时间戳一直在变化,中间的节点 id 又不会变化,会出现递增的时候 id 跨越非常大的情况出现吧。

比如就会出现这种情况:

节点 id 固定,固定的数据在前,id 跨度不会很大,更优雅一点,也就是这样的情况:

但是后来我想明白了,我的说法是错误的,因为在这种生成的 ID 不是单调递增的情况下:

中间跨度很大,很有可能有其他节点生成的序列号。

一旦在出现这种情况,那么就打破了 Seata 关于这次改造的底层设计,即页分裂的次数是有限次的,不考虑页合并的情况下,最多为 1024 次,整个过程最终是收敛的。

也就是这位同学在评论区提到这段话:

如果你看不懂这句话,说明你没看懂我前面发的《在开源项目中看到一个改良版的雪花算法,现在它是你的了。》这篇破文章,可以再瞅一眼。

为什么必须要有时间戳?

写到这里的时候,我突然又想明白了另外一个问题:

时间戳的 41 位初始值从 0 开始不行吗?

也就是这样:

我第一次看到这个问题想得是肯定不行,因为这是雪花算法的改良版本,雪花算法的基石就是基于时间,你连时间都不要了,这不是要革了命了吗?而且这样时间戳从 0 开始,那不就变成序列号了吗?

现在我突然想明白这个问题了。

为什么必须要有时间戳?

因为要考虑重启的情况啊。

你想,你第一次启动之后,如果没有时间戳在里面,从 0 开始一直递增,假设递增到了 999。

现在你的服务要重启了,请问,重启之后你的序列号是多少?

你的节点 ID 没变,也没有时间戳,只有个序列号,又没有地方存储重启之前的序列号是多少,那么这次是不是又得从 0 开始了。

哦豁,序列号重复。

谁看了不得说一句:哦豁,宝批龙,瓜起了涩。

啥,你问我“宝批龙”是什么意思?

没啥特别的,类似于“靓仔”。在川渝地区,就是打招呼,问好的一种意思。一般用来形容年轻的帅哥美女。比如你在重庆迷路了,问路的时候你就可以说:嘿宝批龙,你好,请问去朝天门怎么走。

代码

最后,Seata 里面的 IdWorker 类,加上注释一共也才 187 行,源码就在这里,你粘过去就能直接用:

https://github.com/seata/seata/blob/2.x/common/src/main/java/io/seata/common/util/IdWorker.java

另外你看文章的时候有没有感到一丝丝奇怪。

为什么我会把当前时间假设为 2023 年 08 月 11 日 10 点 55 分 00 秒 000 毫秒。

这个时间,有零有整的。

因为这个时间是五月天成都演唱会开始抢票的时间:

写篇文章就当许个愿吧,希望我能抢到两张票,和 Max 同学一起去看五月天。

然后,一起唱《干杯》、《突然好想你》、《知足》、《倔强》、《温柔》、《后来的我们》、《恋爱ing》、《志明与春娇》...

一起去找高中时劣质 MP3 中传出的全损音质和在 KTY 时一定要扯着嗓子唱的《噢买尬》和《终结孤单》。

求求了。

好了,如果本文对你有一点点帮助的话,点个免费的赞,求个关注,不过分吧?

标签:11,10,改良版,毫秒,算法,时间,共性,序列号,ID
From: https://www.cnblogs.com/thisiswhy/p/17620059.html

相关文章

  • 代码随想录算法训练营第十天|力扣232.用栈实现队列、力扣225.用队列实现栈
    栈与队列理论知识栈提供push和pop等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。不像是set或者map提供迭代器iterator来遍历所有元素。栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制......
  • 代码随想录算法训练营第十四天| 理论基础 递归遍历 迭代遍历
     理论基础    卡哥建议:需要了解 二叉树的种类,存储方式,遍历方式 以及二叉树的定义   文章讲解:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html   补充的知识点:   名词的概念看卡哥文章。二叉树......
  • 单源最短路径算法
    单源最短路径算法1.原理单源最短路径算法是一种用于在有向图或无向图中找到从指定源节点到其他所有节点的最短路径的算法。常用的单源最短路径算法有Dijkstra算法、Floyd-Warshall算法和Bellman-Ford算法。2.Dijkstra算法Dijkstra算法是最常用的单源最短路径算法之一,它的基......
  • (未完全掌握)代码随想录算法训练营第八、九天|KMP算法;力扣28.实现strStr(),力扣459.重
    KMP算法(没掌握)主要功能:字符串匹配理论:检测文本串中是否出现过模式串前缀就是包含首字母不包含尾字母的所有子串后缀就是包含尾字母不包含首字母的所有子串最长相等前后缀:对子串分别分析,从左向右前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从......
  • Floyd 算法
    Floyd算法:动态规划中的最短路径问题一、简介Floyd算法是一种用于求解图中所有顶点对之间最短路径的动态规划算法。它是由RobertW.Floyd在1965年提出的,因此得名Floyd-Warshall算法。该算法的核心思想是使用动态规划来避免重复计算已经计算过的子问题的解。二、原理假......
  • 分解因数--递归算法
    【题目描述】给出一个正整数a,要求分解成若干个正整数的乘积,即a=a1×a2×a3×...×an,并且1<a1≤a2≤a3≤...≤an,问这样的分解的种数有多少。注意到a=a也是一种分解。【输入】第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,包括一个正整数a(1<a<32768)。【输出】n行,每......
  • MATLAB用改进K-Means(K-均值)聚类算法数据挖掘高校学生的期末考试成绩|附代码数据
    全文链接:http://tecdat.cn/?p=30832最近我们被客户要求撰写关于K-Means(K-均值)聚类算法的研究报告,包括一些图形和统计输出。本文首先阐明了聚类算法的基本概念,介绍了几种比较典型的聚类算法,然后重点阐述了K-均值算法的基本思想,对K-均值算法的优缺点做了分析,回顾了对K-均值改进......
  • LeetCode从算法到算命—1749.任意子数组和的绝对值的最大值
    1749.任意子数组和的绝对值的最大值题目信息给你一个整数数组nums。一个子数组[numsl,numsl+1,...,numsr-1,numsr]的和的绝对值为abs(numsl+numsl+1+...+numsr-1+numsr)。请你找出nums中和的绝对值最大的任意子数组(可能为空),并返回该最大值。abs(x)......
  • 最短路算法大全(Bellman-Ford &Spfa)
    Bellman-Ford算法1、基于松弛操作的单源最短路算法,针对于有向图、2、e[u]存u点的出边的邻点和边权,d[u]存u点到原点的距离3、初始化,d[s]=0,d[其他点]=INF(源点到本身的距离初始化为0到其他点的距离都初始化为无穷)4、执行多轮操作。每轮操作都对所有边尝试一次松弛操作5、......
  • 非对称加密算法
    非对称加密算法是一种使用公钥和私钥配对的加密算法,也称为公钥加密算法.常见的非对称加密算法包括RSA、DSA等,它们遵循公钥分发、私钥保密规则,也就是说公钥是公开的,可以自由分发给其他人.而私钥是保密的,只有私钥的持有者知道.这样可以确保加密和签名的安全性,因为即使公钥......