唯一的ID几乎在各处和每个应用程序中都使用。它们用于识别用户,产品,交易,会话等。这样,我们可以将交易分配给特定用户,而不是误认为另一个用户。在本文中,我将向您展示如何在应用程序中生成唯一的ID并给他们第二次使用。
如何获得独特的ID?
每个用户获取唯一ID的方法有几种方法。显然有些人可能会做
之类的事情
const min = 1000000
const max = 9999999999
const id = Math.floor(Math.random()*max) + min;
尽管我并不真正建议它,因为它显然不是真正唯一的。您可以并且将获得两次相同的ID生成的最终。当然,您可以在数据库中检查该ID是否已经存在并生成新的数据库,但是您可以获得我的观点。
自动增加整数
尽管这是一种非常基本的和广泛使用的方法,但我宁愿不使用它。关于数据库中这种外观的一个示例将与之接近:
id | 用户名 | 密码 | create_at |
---|---|---|---|
1 | 约翰 | some_hash | 1667329399 |
2 | jane | some_hash | 1667329404 |
3 | 杰克 | some_hash | 1667329412 |
在这里,我们可以清楚地看到ID每次逐一增加。
UUID
UUID是获得唯一ID的非常普遍的方法。它们是唯一的128位数字。 UUID非常有用,因为它们是唯一的,是的,有很小的机会您可以获得两次同一ID ,并且它们不是顺序的。它们也很容易生成。例如,我们可以使用以下命令通过终端生成一个:
~
🕙 20:07:00 ❯ uuidgen
67DB31FB-1887-4372-A8B0-E87C092D7D11
正如您预期的那样,此UUID会在您的数据库中占用空间,但这并不重要 ,当您使用UUID时,您的数据库最终看起来像这样:
id | 用户名 | 密码 | create_at |
---|---|---|---|
45915234-74BE-499A-B7D5-D6D4882DA5B7 | 约翰 | some_hash | 1667329399 |
885A26B8-C7CA-4C84-A871-0E89AF4997BD | jane | some_hash | 1667329404 |
689E682B-35C8-4594-A0D7-5893626B7B1D | 杰克 | some_hash | 1667329412 |
现在让我向您介绍 SpaceFlake ,这是一种可轻松创建唯一ID的分布式发电机;受Twitter's Snowflake
的启发什么是太空粉?
太空粉非常简单,它是基于Twitter的雪花算法的ID,它们正在积极使用,并且略微编辑以适合我的需求。该算法很简单:
您可以看到,空间粉由4个主要部分组成:
- timestamp :时间戳是自碱时期以来经过的毫秒。
- 节点ID :节点ID代表生成空格flake的节点的ID。它是0到31之间的数字。
- Worker ID :工人ID代表生成太空粉的工人的ID。它是0到31之间的数字。
- 序列:序列是工人在节点上产生的空格数量。它是0到4095之间的数字。
有了这些算法的规格,我们可以从理论上 生成up to 4 million unique IDs每毫秒ð
工人和节点=太空粉网络
太空粉网络是一个非常基本的概念,您拥有多个独立节点本身由多个工人组成。这些工人是可以生成太空碎片的工人。
理想情况下,太空粉网络代表您的整个应用程序或公司。每个节点代表公司内部的一个服务/服务器或应用程序,每个工人代表一个可以为特定目的生成空格flake的过程。这样,您可以轻松地识别通过查看其节点ID和Worker ID来生成空间flake。
在上面的示例中,我们可以将节点1 视为应用程序的 API/后端。 Worker(ID:1)将负责为用户ID 生成空间碎片。 工作者(ID:2)将负责为博客文章ID生成空间粉。这样,我们可以轻松地确定生成空间的位置以及出于什么目的。
然后,我们还拥有节点2 ,它可能是整个基础架构的记录系统,并且生成的ID将由 Worker(ID:1)。 p>
最后,您可以根据需要自由使用它们,只需确保使用这些节点和工人能够识别太空粉 - 这也是其背后的想法。
我为什么要创建空间碎片?
现在您可以问我
“为什么要创建这个?”
事实上,很多人已经问我这个...
要解释我的决定,让我们回到上面的数据库的示例。我故意在数据库中添加了一个created_at
字段,因为知道何时创建用户或帖子非常有用。有了空间粉和雪花,您将拥有ID内置的创建时间((spaceflake >> 22) + BASE_EPOCH
),因此您无需将其单独存储在数据库的新列中。这是非常有用的,因为它可以删除不必要的列,并且还使通过创建时间对数据进行排序变得更加容易。然后,您的数据库看起来像:
id | 用户名 | 密码 | |
---|---|---|---|
1037425364332843009 | 约翰 | some_hash | |
103742545856270978 | jane | some_hash | |
1037425546889924611 | 杰克 | some_hash | |
另一个原因是我使用 go (duh)用于我所有不同的后端。目的是使其尽可能容易地使用,并能够在我正在从事的任何项目中(例如Project Absence或其他未来项目)中使用它。
我在开发过程中遇到的一些问题
走很快
是的,走很快;这也是我的第一期。背后的原因是,起初我制作了序列随机,这绝对不是最好的主意 - 尽管我一直保留了它,以防万一我想使用它。问题是,正如您所猜到的那样,序列是从0到4095,这意味着它只能每毫秒生成4096个唯一IDS。这并不多,而且绝对不足以确保为在毫秒内生成的每个太空粉的唯一ID。因此,我不得不将其更改为增量。
增量不足以产生批量生产
因此,让我们假设某人想迁移其数据库以使用太空粉代替UUID。他们将不得不产生大量的空间流量,并且必须尽快做到这一点。这是问题所在的地方。如果您每毫秒每毫秒生成一个空间粉,则每毫秒只能生成4096个空间粉。这再一次没有很多。因此,我必须在使用BulkGenerate()
函数时制作节点ID和Worker ID 增量。
该代码有点疯狂,但是它起作用(至少我认为是)。
// BulkGenerate will generate the amount of Spaceflakes specified and auto scale the node and worker IDs
func BulkGenerate(s BulkGeneratorSettings) ([]*Spaceflake, error) {
node := NewNode(1)
worker := node.NewWorker()
worker.BaseEpoch = s.BaseEpoch
spaceflakes := make([]*Spaceflake, s.Amount)
for i := 1; i <= s.Amount; i++ {
if i%(MAX12BITS*MAX5BITS*MAX5BITS) == 0 { // When the total amount of Spaceflakes generated is the maximum per millisecond, such as 3'935'295
time.Sleep(1 * time.Millisecond) // We need to sleep to make sure the timestamp is different
newNode := NewNode(1) // We set the node ID to 1
newWorker := newNode.NewWorker() // And we create a new worker
newWorker.BaseEpoch = s.BaseEpoch
node = newNode
worker = newWorker
} else if len(node.workers)%MAX5BITS == 0 && i%(MAX5BITS*MAX12BITS) == 0 { // When the node ID is at its maximum value
newNode := NewNode(node.ID + 1) // We need to create a new node
newWorker := newNode.NewWorker() // And a new worker
newWorker.BaseEpoch = s.BaseEpoch
node = newNode
worker = newWorker
} else if i%MAX12BITS == 0 { // When the worker ID is at its maximum value
newWorker := worker.Node.NewWorker() // We just create a new worker
newWorker.BaseEpoch = s.BaseEpoch
worker = newWorker
}
spaceflake, err := generateSpaceflakeOnNodeAndWorker(worker, worker.Node) // We generate the Spaceflake on the node and worker specified
if err != nil {
return nil, err
}
spaceflakes[i-1] = spaceflake // i starts at 1, so we need to subtract 1 to get the correct index
}
return spaceflakes, nil
}
现在,我们可以每毫秒大量生成3'935'295太空粉,而无需自动缩放的情况大得多4096。一旦达到了最大数量,我们就需要睡1毫秒,以确保时间戳不同,并且我们需要创建一个新的节点和一个新的工人。
这是解决的另一个问题ð€
太空粉是大数字
最后但并非最不重要的一点是,我知道,如果不作为API中的字符串处理,大数字是痛苦的。
这正是在我的一个API中使用太空粉时发生的事情。我生成了一个太空粉-144328692659220481
-并制作了一个端点以获取用户ID。当通过浏览器向端点提出请求时,我得到了以下JSON:
{
"id": 144328692659220480
}
是的,有一个很小的区别 - 在这里:144432869265922048* 0 。现在,这可能是一个很小的差异,但是**这是一个差异*,我们不希望那样。唯一的解决方案是使API返回ID作为字符串。在客户端,如果我们愿意,我们可以将其转换为数字 - 例如,使用此信息:
spaceflakeID := "144328692659220481" // Will be the value returned by the API
id, _ := strconv.ParseUint(spaceflakeID, 10, 64)
sequence := spaceflake.ParseSequence(id)
这将返回空格的正确序列。
如何使用太空粉?
使用太空粉非常简单,如果您有一个GO项目,则可以使用
下载包裹
go get github.com/kkrypt0nn/spaceflake
然后,您可以通过导入并生成太空粉:
来使用它。
package main
import (
"fmt"
"github.com/kkrypt0nn/spaceflake"
)
func main() {
node := spaceflake.NewNode(5)
worker := node.NewWorker() // If BaseEpoch not changed, it will use the EPOCH constant
sf, err := worker.GenerateSpaceflake()
if err != nil {
panic(err)
}
fmt.Println(sf.Decompose()) // map[id:<Spaceflake> nodeID:5 sequence:1 time:<timestamp> workerID:1]
}
您可以在存储库的examples文件夹中查看其他示例。