Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何高效产生不重复的随机数序列? #46

Open
abbshr opened this issue Jul 30, 2015 · 1 comment
Open

如何高效产生不重复的随机数序列? #46

abbshr opened this issue Jul 30, 2015 · 1 comment

Comments

@abbshr
Copy link
Owner

abbshr commented Jul 30, 2015

你或许考虑过这个问题. 可是我曾用一个十分粗暴地办法"解决"了这个问题: 每生成一个序列检测是否有重复, 如果有,重新生成再进入这个检测, 否则存储在hash的一个位置.

这学期春季面试时, 当和面试官谈起Yinle.me在提取码生成过程中无碰撞4位随机数如何高效生成的问题, 我才发现不经意间错过了一个值的思考的问题. 我可以把这个问题描述为: "对于Event Driven的Web Server, 如果需要处理上千上万的并发连接, 怎么高效的为每个连接分配UUID (就是在这个server生存周期内无碰撞的数)?", 我脑海中立刻闪现第二个简单粗暴的办法: 预先生成一个bitmap, 记录所有可能的值, 再由主线程顺序分配, 当我说完之后自己都乐了... 如果一个记录占用1byte空间, 4位要1M左右空间, 这确实不算什么, 假如位数稍微增大, 空间占用就会指数级增长, 显然这个方法在给定字符空间大的时候将不再适用.

那该怎么办呢? 可见我在概率/随机领域知识的匮乏... 我向面试官请教有什么高明的办法, 他提示我用堆或者队列, 我又考虑半天, 仍没结论.

现在整理一下思绪, 先把上面的各种问题放在一边, 来想这样一个问题: 何为随机?

不翻书我们也能答上来几个关键点吧:

  • 不可预知
  • 均匀分布

即然这样那你有没有想过编程语言中的随机数是怎么来的? 仅凭几行代码就能模拟随机吗? 你应该知道在C语言里它叫伪随机数.

对于所学所用, 人们往往知其然而不知其所以然. 凭感性的认识, "伪随机数"好像就是随机的, 于是我们往往叫他随机数. 可是伪随机数

是使用一个确定性的算法计算出来的似乎是随机的数序,因此伪随机数实际上并不随机!

就是说, 伪随机数其实是按照某种计算方法得出, 服从某种分布规律的, 也是可以预测的. 这显然不符合对随机性的要求.

那么如何得到真正的随机数? 就是要保证过程的不确定性!

/dev/random来说, Linux内核会为其维护一个熵池, 专门存储从各处搜集来的噪声. 比如: 鼠标在某一时刻的位置, 驱动产生的中断信号, 键盘中断信号, 甚至是附近电磁场变化. 这就保证了从熵池中取出数据的不可预知性.

现在我们回到产生不重复的随机数这个问题上来, 要求产生的随机数不重复本身就是种悖论. 既然是随机过程, 就没办法控制其结果.

但我们可以hack一下这个问题: 如何产生一组无重复的id?

或许少量id我们可以选择bitmap或者循环检测. 现在考虑上亿id的分配问题, 要保证在高效利用空间/时间的前提下每个id不重复, 假设id空间是8位[a-z0-9], (36^8), 怎么做?

为了方便, 可能首选就是UUID/GUID, 它是一个128bit的16进制数字. 下面是一个来自stackoverflow的UUID生成算法:

function generateUUID(){
    var d = new Date().getTime();
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = (d + Math.random()*16)%16 | 0;
    d = Math.floor(d/16);
      return (c=='x' ? r : (r&0x7|0x8)).toString(16);
    });
    return uuid;
};

理论依据就是小概率事件, UUID的每个字符都是伪随机的, 这样小概率的乘积结果就几乎可以忽略不计了, 也就是几乎不会冲突.

与被陨石击中的概率比较的话,已知一个人每年被陨石击中的概率估计为170亿分之1,也就是说概率大约是0.00000000006 (6 x 10-11),等同于在一年内置立数十兆笔UUID并发生一次重复。换句话说,每秒产生10亿笔UUID,100年后只产生一次重复的概率是50%。如果地球上每个人都各有6亿笔UUID,发生一次重复的概率是50%。

产生重复UUID并造成错误的情况非常低,是故大可不必考虑此问题。

或许讨论来讨论去还是回到Hash算法抗碰撞能力设计中. Hash运算就有点类似寻找无重复串的过程, 目的是将不同规模, 内容不等的输入映射到规模相同的不同输出中, 如SHA-*, MD5等HASH算法.

那么如何利用HASH帮住我们为每个连接/会话生成唯一id? 这和如何生成sessionId问题差不多, 就是将每个连接的特征值(比如访问时间, 携带数据, ip地址, pid, 文件描述符, 内存地址等等信息)作为输入, 为其生成长度相等的无重复字符串.

但是无论什么方法, 也只是满足一定数量级内无相似度或较低相似度, 但是从业内普遍的密码保存方式可以知道设计良好的HASH算法足矣应付碰撞问题, 对于极小概率事件也没什么可担心的.

@abbshr abbshr changed the title (面试问题总结) - 1. 如何产生不重复的随机数序列? (面试问题总结) - 1. 如何高效产生不重复的随机数序列? Jul 30, 2015
@abbshr abbshr self-assigned this Jul 30, 2015
@abbshr abbshr changed the title (面试问题总结) - 1. 如何高效产生不重复的随机数序列? 如何高效产生不重复的随机数序列? Jul 30, 2015
@byronhe
Copy link

byronhe commented Feb 28, 2017

int seq=0;
function generateID(){
    return skip32(++seq,“password”);
}

这样如何?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants