10个字符ID,全局和本地唯一

时间:2009-12-05 20:15:50

标签: c++ c linux x86-64 sip

我需要生成一个10个字符的唯一ID(SIP / VOIP人员需要知道它是P-Charging-Vector头中的param icid值)。每个字符应为26个ASCII字母之一(区分大小写),10个ASCII数字之一或连字符减号。

它必须是“全局唯一的(在生成id的机器之外)”并且足够“本地唯一(在生成id的机器内)”,并且所有需要打包成10个字符,p !.

这是我的看法。我是第一个编码'必须'编码全局唯一本地IP地址到base-63(它是一个无符号长整数,编码后将占用1-6个字符)然后尽可能多的当前时间戳(其一个time_t / long long int,编码后将占用9-4个字符,具体取决于编码的ip地址占用的空间大小。

我还在时间戳中添加了循环计数'i',以便在一秒钟内多次调用该函数时保留唯一性。

这是否足以在全球和本地独特,还是有另一种更好的方法?

拉​​夫

#include <stdio.h>
#include <string.h>
#include <sys/time.h>

//base-63 character set
static char set[]="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";

// b63() returns the next vacant location in char array x
int b63(long long longlong,char *x,int index){
    if(index > 9)
        return index+1;

    //printf("index=%d,longlong=%lld,longlong%63=%lld\n",index,longlong,longlong%63);
    if(longlong < 63){
        x[index] = set[longlong];
        return index+1;
    }  

    x[index] = set[longlong%63];
    return b63(longlong/63,x,index+1);
}

int main(){
    char x[11],y[11] = {0}; /* '\0' is taken care of here */

    //let's generate 10 million ids
    for(int i=0; i<10000000; i++){

        /*  add i to timestamp to take care of sub-second function calls,
            3770168404(is a sample ip address in n/w byte order) =                84.52.184.224 */
        b63((long long)time(NULL)+i,x,b63((long long)3770168404,x,0));

        // reverse the char array to get proper base-63 output
        for(int j=0,k=9; j<10; j++,k--)
            y[j] = x[k];

        printf("%s\n",y);
    }

    return 0;
}

7 个答案:

答案 0 :(得分:5)

  

它必须是'全球独一无二的(在外面   生成id)'和   足够'本地独特(内部)   生成id)'的机器,和   所有这些都需要打包成10个   人物,p !,

您是否掌控了所有软件生成ID?你在玩ids吗?如果不是......

我对SIP一无所知,但必须误解你对规范的看法(或规范必定是错误的)。如果另一个开发人员尝试使用与您编写的算法不同的算法来构建id,那么您将与他们的ID发生冲突,这意味着他们将在该系统中更长时间地全局唯一。

我将回到SIP文档,看看是否有附录,其中包含用于生成这些ID的算法。或者可能是一个更聪明的SO用户,而不是我可以回答生成这些id的SIP算法是什么。

答案 1 :(得分:2)

我会仔细研究RFC 4122,它描述了128位GUID的生成。有几种不同的生成算法,其中一些可能适合(基于MAC地址的一个想到的)。这是一个比你大2 ^ 128 = 3.4 * 10 ^ 38的数字空间,而63 ^ 10 = 9.8 * 10 ^ 17,所以你可能不得不对唯一性做出一些妥协。考虑诸如生成ID的频率等因素。

然而,在RFC中,他们考虑了一些实际问题,例如通过预先分配ID块来有效生成大量唯一值的能力。

答案 2 :(得分:1)

你不能只拥有一个分布式ID表吗?

答案 3 :(得分:1)

NAT的局域网上的机器通常会有一个小范围的IP,并不是所有的32位值都是有效的(想想多播等)。机器也可以获取相同的时间戳,特别是如果粒度很大(例如秒);请记住,这一年往往是相同的,所以它是较低的位,将给你最“独特”。

您可能希望获取各种值,使用加密哈希对其进行哈希处理,并将其转换为允许使用的字符,截断为10个字符。

但是你处理的是一个小于60位的值;你需要仔细考虑碰撞的影响。您可能以错误的方式处理问题......

答案 4 :(得分:1)

好吧,如果我抛弃我认为这是一个坏主意的事实,并集中精力解决你的问题,这就是我要做的事情:

你的id范围是10 ^ 63,大约相当于60位。您希望它既“全局”又“本地”独特。让我们生成前N位为全局唯一,其余为本地唯一。两者的串联将具有您正在寻找的属性。

首先,全球唯一性:IP不起作用,尤其是本地IP,它们的熵很少。我会选择MAC地址,它们是全球唯一的。它们覆盖256 ^ 6的范围,因此使用6 * 8 = 48位。

现在,对于本地唯一:为什么不使用进程ID?我假设每个过程的唯一性,如果不是,你将不得不考虑别的东西。在Linux上,进程ID是32位。如果我们想挑剔,那么2个最重要的字节可能只占很小的熵,因为它们在大多数机器上都是0。如果你知道自己在做什么,就弃掉它们。

所以现在你会发现你有一个问题,因为它会使用多达70位来生成一个体面的(但不是防弹的)全局和本地唯一ID(无论如何使用我的技术)。因为我还建议加一个随机数(至少8位长)以防万一,它绝对不适合。因此,如果我是你,我会将~78生成的位散列为SHA1(例如),并将生成的散列的前60位转换为ID格式。为此,请注意您有63个字符范围可供选择,因此几乎全部为6位。因此,将哈希值分成6位,并使用前10个部分从63个字符范围中选择ID的10个字符。显然,6位的范围是64个可能的值(你只想要63),所以如果你有一个6位的片段等于63,要么将它置于62或假设模63并选0。它会略微偏向分布,但它并不太糟糕。

那么,那应该会给你一个体面的全局和本地伪唯一ID。

最后几点:根据Birthday paradox,在生成~14.2亿个ID之后,您将获得约1%的碰撞机会,并且在生成3亿个ID之后有99%的机会。因此,如果您取得了巨大的商业成功并且生成了数百万个ID,请获取更大的ID。

最后,我认为我为你的问题提供了一个“好于坏”的解决方案,但我不禁想到你以错误的方式攻击这个问题,并且可能正如其他人提到的那样,误读规范。因此,如果没有其他方法可以更“防弹”(集中式ID提供商,更长的ID ......),请使用此方法。

编辑:我重新阅读了您的问题,并且您说您可能每秒多次调用此函数。我假设这是一种应用程序ID,在应用程序启动时生成一次,之后再也不会更改,直到它退出。由于情况并非如此,您绝对应该添加一个随机数,如果生成大量ID,请至少使用32位数。阅读并重新阅读我上面链接的生日悖论。并将您的数字生成器播种到高熵值,例如当前时间戳的usec值。甚至可以从/ dev / urandom中获取随机值。 老实说,我的努力是60位可能还不够......

答案 5 :(得分:0)

嗯,使用系统时钟可能是一个弱点......如果有人将时钟设置回来怎么办?您可能会再次重新生成相同的ID。但是如果要使用时钟,可以调用gettimeofday()而不是time();至少那样你会得到比一秒更好的分辨率。

答案 6 :(得分:0)

@Doug T. 不,我无法控制生成ID的所有软件。 我同意没有标准化的算法可能会发生冲突,我已经在相应的邮件列表中提出了这个问题。

@Florian 从你的回答中得到启示。我决定使用/ dev / urandom PRNG作为32位随机数作为id的空间唯一组件。我假设每台机器都有自己的噪音特征,可以假设它在瞬间安全地在空间中独一无二。我之前使用的时间独特组件保持不变。

生成这些唯一ID以整理从不同网络功能收集的所有计费信息,这些信息在呼叫处理期间独立生成特定呼叫的计费信息。

以下是更新后的代码:

拉​​夫

 #include <stdio.h>
 #include <string.h>
 #include <time.h>

 //base-63 character set
 static char set[]="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";

 // b63() returns the next vacant location in char array x
 int b63(long long longlong, char *x, int index){
     if(index > 9)
         return index+1;

     if(longlong < 63){
         x[index] = set[longlong];
         return index+1;
     }  

     x[index] = set[longlong%63];
     return b63(longlong/63, x, index+1);
 }

 int main(){
     unsigned int number;
     char x[11], y[11] = {0};

     FILE *urandom = fopen("/dev/urandom", "r");
     if(!urandom)
         return -1;

     //let's generate a 1 billion ids
     for(int i=0; i<1000000000; i++){

         fread(&number, 1, sizeof(number), urandom);

         // add i to timestamp to take care of sub-second function calls, 
         b63((long long)time(NULL)+i, x, b63((long long)number, x, 0));

         // reverse the char array to get proper base-63 output
         for(int j=0, k=9; j<10; j++, k--)
             y[j] = x[k];

         printf("%s\n", y);
     }

     if(urandom)
     fclose(urandom);

     return 0;
 }