怎么生成可覆盖的ID
in Tutorial with 0 comment
怎么生成可覆盖的ID
in Tutorial with 0 comment

业务场景

在做数据分析的时候,我们有很多业务场景是需要进行数据覆盖的,用最新数据去覆盖旧数据。

而实现数据覆盖,在数据库里,只需要保证数据的主健相同即可。

正因为有数据覆盖的需求,我们需要有一个根据业务信息生成可覆盖ID的最佳实践。

主键类型

数据库主健指的是一个列或多列的组合,其值能唯一地标识表中的每一行,通过它可强制表的实体完整性。

一般分为逻辑主键和业务主键

逻辑主键

其中,在 mysql 中,是自增(auto_increment)算法,在 mongodb 中,是objectId算法,在 Elasticsearch 中,是GUID算法。

其中 GUID 算法生成的id,长度为 20 个字符,URL 安全,base64 编码,分布式系统并行生成时不可能会发生冲突。GUID 算法可以认为是 UUID 的变种。

除此之外,还有 Twitter 开源的Snowflake算法。

objectId算法 、GUID算法和Snowflake算法,可以理解都是分布式全局唯一ID生成算法

业务主键

业务主健,常见是把一个或者多个业务字段设置为主健,这种方法通常会受到长度的限制,解决这个问题的方法是进行对业务信息 hash。

hash 常用的算法有,md和sha,下面是各自的代表算法的对比,md5 vs sha1

MD5SHA1
MD5代表消息摘要。SHA1代表安全哈希算法。
MD5可以具有128位消息摘要的长度。SHA1可以具有160位长度的消息摘要。
与SHA1的速度相比, MD5的速度快。SHA1的速度比MD5的速度慢。
为了找出初始消息, 攻击者需要2 ^ 128次运算, 而要利用MD5算法程序。相反, 在SHA1中, 该值为2 ^ 160, 因此查找起来非常麻烦。
MD5比SHA1简单。SHA1比MD5更复杂。
MD5提供了较弱的安全性。它提供了平衡或可容忍的安全性。
在MD5中, 如果攻击者需要找出具有相同消息摘要的2条消息, 则攻击者将需要执行2 ^ 64次操作。在SHA1中, 攻击者将需要执行2 ^ 80次操作, 该操作大于MD5。
MD5于1992年提出。SHA1于1995年推出。

整理可以看到sha1是要比md5更好的,在UUID不同的版本里,其中有两个版本分别使用了这两种算法,其中UUID v3版本使用的是md5,UUID v5版本使用的是sha1。

UUID

UUID由16个字节组成,一共是128位,转换成16进制表示后共有32位,其定义如下:

const Size = 16
type UUID [Size]byte

一般会使用-来连接其中的各个部分,因此,常见的为36个字符:

784b99c1-2a77-11ec-8421-0800270e658d
              |    |
             版本  变体

在一个UUID中,可以通过上述两个位置得知UUID生成器的版本、以及该版本下的变体版本。UUID现有5个版本,每个版本定义了不同的生成逻辑,因此其安全性和适用场景也有所区别。

versiongeneration rule
Version 1date-time and MAC address
Version 2date-time and MAC address, DCE security version (removed)
Version 3based on MD5 hashing of a named value
Version 4random
Version 5based on SHA-1 hashing of a named value

最佳实践

js

const uuid = require('uuid')
const namespace = 'bb5d0ffa-9a4c-4d7c-8fc2-0a7d2220ba45'

const needToHashString = 'howtoensuredatacanbecover'
console.log(uuid.v5(needToHashString, namespace))

go

import "github.com/google/uuid"
import "crypto/sha1"

namespace := 'bb5d0ffa-9a4c-4d7c-8fc2-0a7d2220ba45'
needToHashString := 'howtoensuredatacanbecover' 

uuid := uuid.NewHash(sha1.New(), namespace, needToHashString, 5)
println(uuid.String())

demo

对一串业务信息进行唯一性id生成,生成 UUID 后,去掉 - 得到32位的字符串,直接就可以用了,不需要再做其他处理。

6215dca1ff35c30b03a52d491650016800000youthwomanentranceoneDay => 18391e4c-a83b-5f6f-abde-6eec799930e5 => 18391e4ca83b5f6fabde6eec799930e5
6215dca1ff35c30b03a52d491650013200000youthmanentranceoneDay => b23dd831-b258-5a94-9e64-51c824431581 => b23dd831b2585a949e6451c824431581
6215dca1ff35c30b03a52d491650013200000middle-agedwomanentranceoneDay => 1c71927b-b0b7-5ea8-9b4b-59aee46f04a4 => 1c71927bb0b75ea89b4b59aee46f04a4
6215dca1ff35c30b03a52d491650013200000middle-agedmanentranceoneDay => 6bb05f1a-9a81-54da-9bd3-e20b5a3cfc9a => 6bb05f1a9a8154da9bd3e20b5a3cfc9a
6215dca1ff35c30b03a52d491650013200000youthwomanentranceoneDay => 32bb7c40-e0af-55d9-8704-f239bd966a56 => 32bb7c40e0af55d98704f239bd966a56
6215dca1ff35c30b03a52d491650009600000youthmanentranceoneDay => 88a43d50-a9bc-5ee2-b25c-c2689837c646 => 88a43d50a9bc5ee2b25cc2689837c646
6215dca1ff35c30b03a52d491650009600000youthwomanentranceoneDay => 15678668-b721-5dd5-980f-dc5c5d925775 => 15678668b7215dd5980fdc5c5d925775
6215dca1ff35c30b03a52d491650009600000youthmanentrancetwoDays => 6dbf9b76-3d4e-505e-92e3-4a1e32b335d3 => 6dbf9b763d4e505e92e34a1e32b335d3
6215dca1ff35c30b03a52d491650006000000youthmanentranceoneDay => d3381c67-679d-53ae-a0a8-2e5bd5295f3c => d3381c67679d53aea0a82e5bd5295f3c
6215dca1ff35c30b03a52d491650006000000middle-agedmanentranceoneDay => ae27d2c2-4dc7-5ff6-bebb-0f980400dec7 => ae27d2c24dc75ff6bebb0f980400dec7

建议

面对需要根据业务字段生成一个全局唯一的id,同时还需要进行数据覆盖,最好按照我提供的方案进行。
方案是:对业务信息进行字符串拼接后,再使用 UUID v5 去hash这个字符串,然后去掉 - 得到32位字符串,就可以拿这个去作为数据的主键。
这个hash处理后的字符串,长度足够短,碰撞率几乎默认没有。

Responses