我的同事和我正在讨论使用哪些方法自动生成用户ID和帖子ID以便在数据库中进行识别:
一个选项使用Random的单个实例,并采用一些有用的参数,因此它可以重用于各种字符串gen情况(即从4位数字引脚到20位字母数字id)。这是代码:
// This is created once for the lifetime of the server instance
class RandomStringGenerator
{
public const string ALPHANUMERIC_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
public const string ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public const string NUMERIC = "1234567890";
Random rand = new Random();
public string GetRandomString(int length, params char[] chars)
{
string s = "";
for (int i = 0; i < length; i++)
s += chars[rand.Next() % chars.Length];
return s;
}
}
,另一种选择就是使用:
Guid.NewGuid();
我们都知道Guid.NewGuid()
可以满足我们的需求,但我宁愿使用自定义方法。它做同样的事情,但有更多的控制。
我的同事认为,由于自定义方法已经自行制作,因此更容易产生碰撞。我承认我并不完全了解Random的实现,但我认为它与Guid.NewGuid()一样随机。自定义方法的典型用法可能是:
RandomStringGenerator stringGen = new RandomStringGenerator();
string id = stringGen.GetRandomString(20, RandomStringGenerator.ALPHANUMERIC_CAPS.ToCharArray());
修改1:
编辑2:
我们还使用了煮熟的方法来生成帖子ID,与会话令牌不同,它需要在我们网站的网址(如http://mywebsite.com/14983336)中显示漂亮,所以guid不是这里的选项,但是仍然要避免碰撞。
答案 0 :(得分:47)
我正在寻找一个更深入的理由,为什么在与Guid相同的自由度下,煮熟的方法可能更容易产生碰撞。
首先,正如其他人所说,Random
不是线程安全的;从多个线程使用它可能会导致它破坏其内部数据结构,以便它始终生成相同的序列。
其次,Random
根据当前时间播种。在同一毫秒内创建的Random
的两个实例(回想一下,现代硬件上的毫秒数是百万处理器周期)将具有相同的种子,因此将生成相同的序列。
第三,我撒了谎。 Random
不根据当前时间播种;它是根据机器活动的时间播种的。种子是一个32位的数字,由于粒度是以毫秒为单位,所以只有几个星期,直到它包裹。但那不是问题;问题是:您创建Random
实例的时间很可能在机器启动后的几分钟内。每次重启机器时,或者在群集中使新机器联机,有一个小窗口,其中创建了Random实例,发生的越多,你获得种子的几率就越大。
正如其他人所说:如果你想要数据库的主键,那么让数据库生成一个主键;让数据库完成它的工作。如果您想要一个全球唯一标识符,那么使用guid ;这就是他们的目的。
最后,如果你有兴趣了解guids的使用和滥用,那么你可能想阅读我的“guid guide”系列;第一部分在这里:
http://blogs.msdn.com/b/ericlippert/archive/2012/04/24/guid-guide-part-one.aspx
答案 1 :(得分:7)
正如其他答案所述,我的实施有一些严重的问题:
(number of possible chars)^20
资源。给定相同的种子,任何序列的长度都是相同的。 Guid.NewGuid()
没问题,除非我们不想在网址和.NET中使用丑陋的GUID。不知道NewGuid()算法在会话令牌中使用加密安全 - 它可能会给出可预测的结果知道一点信息。
这是我们现在使用的代码,它是安全的,灵活的,据我所知,如果给出足够的长度和字符选择,它不太可能产生冲突:
class RandomStringGenerator
{
RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider();
public string GetRandomString(int length, params char[] chars)
{
string s = "";
for (int i = 0; i < length; i++)
{
byte[] intBytes = new byte[4];
rand.GetBytes(intBytes);
uint randomInt = BitConverter.ToUInt32(intBytes, 0);
s += chars[randomInt % chars.Length];
}
return s;
}
}
答案 2 :(得分:4)
“自动生成用户id和post id以便在数据库中进行标识”...为什么不使用数据库序列或标识来生成密钥?
对我来说,你的问题确实是,“在我的数据库中生成主键的最佳方法是什么?”如果是这种情况,您应该使用数据库的常规工具,它将是序列或标识。这些优于生成的字符串。
我想我的下一个问题是,你考虑GUID或生成字符串的原因是什么?您将跨分布式数据库进行集成吗?如果没有,你应该问自己是否正在解决一个不存在的问题。
答案 3 :(得分:3)
您的自定义方法有两个问题:
Random
的全局实例,但不使用锁定。 =&GT;多线程访问可能会破坏其状态。之后输出会比现在更糟糕。Random
实例(自系统启动以来),它们可能会创建相同的随机数序列。这意味着无论多长时间,您都不能依赖Random
的唯一输出。
即使您不需要安全性,我建议您使用CSPRNG(RNGCryptoServiceProvider
)。它的性能在大多数用途中仍然可以接受,我相信其随机数的质量超过Random
。如果你想要唯一性,我建议你得到大约128位的数字。
要使用RNGCryptoServiceProvider
生成随机字符串,您可以查看我对How can I generate random 8 character, alphanumeric strings in C#?的回答。
现在Guid.NewGuid()
返回的GUID是版本4 GUID。它们是从PRNG生成的,因此它们具有非常相似的属性,可以生成随机的122位数(其余6位是固定的)。它的熵源的质量远远高于Random
使用的熵源,但不能保证它在加密方面是安全的。
但是生成算法可以随时改变,所以你不能依赖它。例如,在过去,Windows GUID生成算法从v1(基于MAC +时间戳)变为v4(随机)。
答案 4 :(得分:1)
使用System.Guid
:
...可以在需要唯一标识符的所有计算机和网络中使用。
请注意,Random
是伪随机数生成器。它不是真正随机的,也不是唯一的。与128位GUID相比,它只能使用32位值。
但是,即使GUID也可能发生冲突(虽然机会非常小),因此您应该使用数据库自己的功能为您提供唯一标识符(例如自动增量ID列)。此外,您无法轻松将GUID转换为4或20(字母)数字。
答案 5 :(得分:1)
与某些人在评论中所说的相反,Guid.NewGuid()生成的GUID不依赖于任何特定于机器的标识符(只有类型1 GUID,Guid.NewGuid()返回类型4 GUID,这大多是随机的。
只要您不需要加密安全性,Random
类应该足够好,但如果您想要更加安全,请使用System.Security.Cryptography.RandomNumberGenerator
。对于Guid方法,请注意GUID中的所有数字都不是随机的。引自wikipedia:
在规范表示
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
中,N的最高有效位表示变量(取决于变体;使用一个,两个或三个位)。由UUID规范覆盖的变体由N的两个最高有效位表示为10(即,十六进制N将始终为8,9,A或B)。 在UUID规范涵盖的变体中,有五个版本。对于此变体,M的四个位表示UUID版本(即十六进制M将为1,2,3,4或5)。
答案 6 :(得分:0)
关于您的编辑,以下是选择GUID而不是生成的字符串的一个原因:
SQL Server中GUID(uniqueidentifier)的本机存储空间为16个字节。要存储等效长度的varchar(字符串),其中id中的每个“数字”都存储为一个字符,需要32到38个字节,具体取决于格式。
由于它的存储,SQL Server也能够比varchar列更有效地索引uniqueidentifier列。