对于类似于URL缩短器服务的应用程序,我想创建不可猜测的id,我们都认为这是你所熟悉的。以下是此类ID的示例:
在数据库表中将这些作为主键插入时,生成这些技术的有效且高效的技术是什么?最小(或没有)冲突风险?
修改
Piskvor当然是一个很好的观点。我应该提到在达到36 ^ 6限制之前我意味着最小的碰撞风险。
编辑2
呃,废弃了,他的观点远远超过了当然。嗯。然后,或许(就像我已经在其他地方读过的那样)预先创建一个带有id的表格?当我受到36 ^ 6和非连续约束时,这会是最有效的技术吗?
答案 0 :(得分:5)
Set ID length. // e.g. 6
do {
Generate a short random ID of the given length
Collision?
- No:
DONE!
- Yes:
increase ID length
} while true
对于任何有限ID长度,始终存在冲突风险:假设您的示例中有[a-z0-9] {6}个ID,一旦您拥有2,176,782,336个ID,则冲突为100%保证(没有更多可用的密钥)。由于birthday effect,你很快就会发生碰撞。有了这么小的键空间,就没有办法避免碰撞 - 你需要进行某种碰撞恢复。
你可以生成一个ID,直到它没有碰撞 - 但是当关键空间被填充时,这将逐渐变慢:想象一个[az]的键空间,已经采用了[an]和[pz] - 现在每个新的随机ID更容易发生碰撞;当你完全填满键空间时,循环根本不会终止。
我建议的算法在这方面可能过于谨慎:如果它发现了冲突,它将逐渐尝试更长的ID(因为它假设“collision =>不可行来检查更短的密钥空间”)。虽然效率不高,但可能会在几次迭代中找到非碰撞ID。
答案 1 :(得分:3)
有点奇怪的想法。为什么不使用排列? 例如 你有一组值[0-9a-z] 当你生成第一个id。你按照字典顺序进行第一次排列。然后是第二个等等。 为了使它看起来不那么可猜测你可以改变字典顺序的规则。说'a'在't'之后或之类似。你也可以使用元组而不是完整的排列。 这将确保没有碰撞。
这个想法实际上是关于制作某种双向散列函数。基本上如果你能够以某种方式编码数字“1”以获得类似“q8d3dw”的东西并且能够将“q8d3dw”解码回“1”,你可以确定这个函数将为你提供所有值的唯一字符串从1到36 ^ 6.
问题实际上是选择此功能。简单的方法是将“1”与“000000”,“2”与“000001”,“12”与“00000b”相关联。基本上按字典顺序排列所有可用字符串,并在等于id的位置上选择字符串。然而,这很容易猜到。所以你可以做的是人为地改变词典顺序的规则。说而不是正常的顺序(0,1,2,3 ... a,b,c ... x,y,z)你可以稍微改变它并获得类似(a,5,t,3)的东西...)。这将产生更多混淆的结果。然而它仍然是可以猜测的,因为第一个元素是“aaaaaa”,第二个元素是“aaaaa5”,然后是“aaaaat”。因此,您可以进一步更改字典顺序的规则,使它们取决于角色的位置。说出第一个char id(a,5,t,3 ...)的顺序为第二个(y,7,3,r ...)等。
现在,我不会发布任何伪代码,因为它会很长。除非你有兴趣创建这种算法以获得乐趣,否则我不建议你选择这条路线。但是,如果你使用这条路线,它可能是一种非常有效的方法来生成这个id而没有碰撞的可能性。我建议你阅读Donald Knuth博士的第4卷“计算机编程艺术”。实现这些算法有很多建议。
答案 2 :(得分:2)
@ivan:这是一个实现。
首先你需要6个字符串,这里是生成它们的代码:
$letters = range('a', 'z');
$numbers = range(0, 9);
$char_list = array_merge_recursive($letters, $numbers);
for ($i = 0; $i <= 5; $i++) {
shuffle($char_list);
echo '$mysterykey[' . $i . "] = '" . implode($char_list) . "';\n";
}
然后你可以使用函数中的输出:
function int_to_x36($value) {
$mysterykey[0] = 'awkbs81t3jyg20v4qhoxzcuenr65dfimlp97';
$mysterykey[1] = 'ut17qclz96n3msky8jwp4r25gdvhxo0biaef';
$mysterykey[2] = 'cewszx142nph05mi9ulafbdvy36o8gq7trjk';
$mysterykey[3] = '37hp28wjdqe5vnlzxofrsybu4im90k16agtc';
$mysterykey[4] = 'n9a3jksl5ct0eqfzo7pvgyd4m2hiu18xrb6w';
$mysterykey[5] = 'mq0nbk3zs529gu4tecli8j176vardxoypfwh';
$x36 = array();
for ($i = 5; $i >= 0; $i--) {
$x36[$i] = 0;
$y = pow(36, $i);
if ($value >= $y) {
$z = floor($value/$y);
$value = $value - ($z * $y);
$x36[$i] = $z;
}
}
$encoded = '';
for ($i = 0; $i <= 5; $i++) {
$encoded .= $mysterykey[$i][$x36[$i]];
}
return $encoded;
}
function x36_to_int($value) {
$mysterykey[0] = 'awkbs81t3jyg20v4qhoxzcuenr65dfimlp97';
$mysterykey[1] = 'ut17qclz96n3msky8jwp4r25gdvhxo0biaef';
$mysterykey[2] = 'cewszx142nph05mi9ulafbdvy36o8gq7trjk';
$mysterykey[3] = '37hp28wjdqe5vnlzxofrsybu4im90k16agtc';
$mysterykey[4] = 'n9a3jksl5ct0eqfzo7pvgyd4m2hiu18xrb6w';
$mysterykey[5] = 'mq0nbk3zs529gu4tecli8j176vardxoypfwh';
$value36 = str_split($value);
$x36 = array();
for ($i = 0; $i <= 5; $i++) {
$x36[$i] = strpos($mysterykey[$i], $value36[$i]);
}
$x = 0;
for ($i = 5; $i >= 0; $i--) {
$x += $x36[$i] * pow(36, $i);
}
return $x;
}
我确信我已经忽略了一些东西,但它似乎正在发挥作用。
答案 3 :(得分:1)
大随机数和SHA-256哈希例如?您可以稍后缩短它以满足您的需求。
答案 4 :(得分:1)
如果网站足够大,我的意思是大 - 如“我们需要每秒分配数百个新ID”(这会产生其他问题,例如耗尽36 ^ 6密钥空间)一年),你可以预先生成密钥并分配它们。
以下是伪代码示例 - 在如此大流量的站点中,您可能需要优化所使用的算法。
预生成很简单 - 只需从000000
开始,一直到zzzzzz
,将每一行插入表中并将其标记为未使用:
ID | used
==============
000000 0
000001 0
...
zzzzzz 0
当您收到新ID的请求时,请选择一个随机的ID并将其标记为已使用(危险!并发问题!):
SELECT ID FROM ids WHERE RAND() LIMIT 1
UPDATE ids SET used = 1, url=what_you_want_shortened WHERE ID = whatever_you_got_from_previous_query
你会遇到效率问题(例如WHERE RAND()
,表格锁定等),但它是可行的。
答案 5 :(得分:0)
如果你不反对引入.NET dll,我已经创建了一个项目来完成这个。来源为on GitHub here,二进制文件位于IdGenerator NuGet Package。
它为您提供有序序列和/或用户指定长度的随机值,全部在base-36中。即使有多个id生成器实例或在分布式环境中,ID也是唯一的。
var generator = new Base36IdGenerator(
numTimestampCharacters: 11,
numServerCharacters: 4,
numRandomCharacters: 5,
reservedValue: "",
delimiter: "-",
delimiterPositions: new[] {15, 10, 5});
// This instance would give you a 20-character Id, with an
// 11-character timestamp, 4-character servername hash,
// and 5 character random sequence. This gives you 1.6 million
// hash combinations for the server component, and 60 million
// possible combinations for the random sequence. Timestamp is
// microseconds since epoch:
Console.WriteLine(generator.NewId());
// 040VZC6SL01003BZ00R2
// Argument name specified for readability only:
Console.WriteLine(generator.NewId(delimited: true));
// 040VZ-C6SL0-1003B-Z00R2
当然,如果你对字符串不感兴趣而不是有序序列感兴趣,你可以使用GetRandomString方法:
// 6-characters give you a little over 2 billion possible
// combinations (36 ^ 6). 7 characters is about 78 billion.
Console.WriteLine(generator.GetRandomString(length: 6));
其背后的基本原则是:
0
至所需长度Base36编码器本身来自http://www.stum.de/2008/10/20/base36-encoderdecoder-in-c/。