12 位纯数字兑换码的生成算法,如何尽可能保证不重复?

2016-12-09 19:02:40 +08:00
 mokeyjay
在只有 PHP 和 mysql 的环境下,做一个简单的发券、核销程序。其中券的**兑换码必须为 12 位数字**,数据量不会超过千万。如何生成不重复且乱序的券码?

最糟糕的方案是纯随机,然后到表里查一遍是否重复,重复则重新生成;不重复则插入

一个自认为好点的方案是用户 id 加一位随机数加截取时间戳后 x 位
例如用户 ID 为 123 ,则是 3 位,加随机数 4 位。此时剩余 8 位则截取时间戳后 8 位补上。重复概率应该就很低了

在尽可能不遍历表的情况下,还有更难以重复的生成算法吗?求不吝赐教
21279 次点击
所在节点    PHP
34 条回复
fthvgb1
2016-12-11 14:26:55 +08:00
uniqid()不可以么?
lygmqkl
2016-12-12 00:14:00 +08:00
我也来个思路, 12 位分成 8+4 , 8 位账号+ 4 位密码,前 8 位可以按照一定规则直接递增,因为你要求 1000w 这个量级,然后用组合的方式来做验证。

从加密学角度出发,可以使用浮动的 账号密码位置模式,比如 8+4 , 4+4+4 根据一定的规则把数字拆开,具体还是位数太短, 1000w 量级, 16-20 位会比较方便一点。


其实上面也有很好的思路
1. 预先生成不重复的方 redis ,如果在生产环境我会这样做, 1000w 应该很快
2. 记录单据和所有者的关系,这样做一个 link 就可以了。
zhiweiv
2016-12-12 02:17:00 +08:00
有索引的话,数据库查重速度很快的。数据库是树形存储的,查询单个记录秒查的,不是瓶颈。
lslqtz
2016-12-12 12:15:20 +08:00
@jsjscool 自增 ID 不是随机的,有被爆破的可能性。
lslqtz
2016-12-12 12:17:42 +08:00
我觉得随机数 8 位,时间戳取 4 位不错。
如果重复就改用备用算法重新生成,就按照最糟糕的纯随机?
然后循环检查是否重复
zacard
2016-12-12 13:14:03 +08:00
先说下是否是分布式部署。。。
其实可以不用查库直接生成不重复的。。。
基本原理推荐查考:[twitter 的 id 生成]( https://github.com/twitter/snowflake)
mokeyjay
2016-12-12 14:18:21 +08:00
@xi_lin 鄙人愚钝……看不是很懂,但是这个兑换码要求**乱序**且**纯数字**,你回复的这个看起来似乎不那么贴近需求,谢谢
@debiann 主要是希望利用算法尽可能减少查询的次数,谢谢
@haython 这项目后期要允许单用户多个券的,所以没有采用你所说的这种方法,谢谢
@am241 请问有哪种对称加密算法加密后的结果是 12 位纯数字的?我暂时还没听说过,请指教,谢谢
@SuperFashi 沃日 dalao 亲自回复,萌新瑟瑟发抖……我暂时不清楚如何哈希出 12 位纯数字来, dalao 可以给个思路吗?谢谢
@k9982874 那个……没有 redis
@jsjscool 自增 ID 不是乱序的,很容易被他人猜出来,作为兑换码的话绝对不行
@Valyrian 愿闻其详,看不是很懂,谢谢
@realpg 刚毕业的萌新给 dalao 跪下了。看不是很懂你所说的“ INSERT IGNORE INTO 一大批”,因为这个项目里券是用户主动领取的,也就是服务端被动发券,每次一张,动态生成券码这样的
@txlty 感觉原理上和纯随机没有本质区别,稍后试一下重复率,谢谢
@fthvgb1 不行,需要 12 位纯数字
@lygmqkl 你这里所说的账号可以理解为用户的 id 吗?
@zacard 看不懂嘤嘤嘤……

感谢各位的关注及回复
realpg
2016-12-12 14:45:05 +08:00
@mokeyjay
主动领取模型,都没有集中插入的负载,那不是更简单
INSERT IGNORE INTO 一个随机码
返回 affected_rows 是 1 就成功了 不是 1 再生成一个往里插
kaneg
2016-12-12 16:48:43 +08:00
我觉的楼主自己的方案在产品的生命周期中出现重复的概率可以说为 0 ,如果这系统不是准备用上几百年,就没必要将简单的问题搞这么复杂。
koihik
2016-12-12 18:16:08 +08:00
这不是可以用 RSA 来做吗.
12 位数字就是万亿级别,那就取一百万左右的质数 2 个:
p = 997207 , q = 997219
则 n = p * q = 994433767333
然后取尽可能小的 d = 107 ,则 e = 9293754887

然后设自增 id 为 a,则有和 a 一一对应的 b
b = (a ^ d) % n

这样可以保证 id 从(2-994433767333)都不会重复

(2-10)的 ID 对应的卡号如下:
2 = 899396353970
3 = 781336638737
4 = 874206977826
5 = 756065937681
6 = 515322322794
7 = 538347577421
8 = 17370581741
9 = 812852944487
10 = 643487003155
xi_lin
2016-12-12 19:52:55 +08:00
@mokeyjay 大数异或肯定是乱序的
纯数字只要拿字母转数字就行,最基本就是直接变成对应的字母序。。建个映射嘛
SuperFashi
2016-12-12 22:26:08 +08:00
@mokeyjay 以 PHP 为例:
hexdec(substr(md5("Hello"), 5, 10))
Balthild
2016-12-13 14:31:38 +08:00
@debiann 不對吧,拼接上時間戳是縮小了可能重複的區域,而不是縮小了取值範圍。
akira
2016-12-15 15:09:57 +08:00
最糟糕的方案里面,检查部分工作可以由数据库自己做 unique 完成。

或者先生成足够数量,然后 distinct 到目标表

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/326514

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX