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

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

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

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

在尽可能不遍历表的情况下,还有更难以重复的生成算法吗?求不吝赐教
21279 次点击
所在节点    PHP
34 条回复
xi_lin
2016-12-09 19:14:53 +08:00
debiann
2016-12-09 19:42:33 +08:00
纯随机重复的概率比你想的方法更低。用时间戳等于缩小了取值范围,增加了重复概率。
haython
2016-12-09 19:48:33 +08:00
说一下我的做法,仅限于每种券只允许一个用户领一张,比如“双 11 满 100 减 20 ”,“双 11 满 100 减 20 ”这种券生成一个不重复券号,给每个用户的都一样,验证的时候直接去这个用户的所有券里找有没有这个券号就行。
am241
2016-12-09 19:48:49 +08:00
固定前缀+递增 id 拼接字符串后对称加密
SuperFashi
2016-12-09 19:49:25 +08:00
单纯随机是不可能做到不碰撞的,就像你不可能造出一个物理骰子使第二次扔的数一定不等于第一次扔的数。
下手点有三个,查重、提取和生成。
查重指的你第一个想法的优化,即缩短查重时间。可以利用散列表来做,用 mysql 做可持久化存储。
提取指的预先将千万个号码随机出来。随机方式可以为每次随机一个起始值和步长,保证步长不超过某个值不低于某个值使最后不会大于 1e12 。之后只需要每次从表中随机提取出来一个数就好。
生成指的并非随机,而是利用现有信息,例如你的最后一个想法。还可以做比如拼接一个字符串为姓名+时间+乱七八糟的什么,做个哈希生成一个 12 位数。只要哈希函数好,碰撞概率基本为 0 。
debiann
2016-12-09 19:53:15 +08:00
@haython +1

用各种奇技淫巧改造随机数,都是白费功夫,只会影响原本随机算法的随机性。
k9982874
2016-12-09 20:38:34 +08:00
预先生成一个不重复的兑换码表, lpush 到 redis
每次从 redis lpop 取个兑换码

性能最佳,且绝对不重复
xurubin
2016-12-09 21:45:12 +08:00
@am241 +1
jsjscool
2016-12-10 00:34:57 +08:00
MySQL 的自增 ID 就能解决你说的唯一性问题,而且是百分百唯一。还不到千万的数据, MySQL 的 IO 损耗可以忽略。
Valyrian
2016-12-10 07:38:11 +08:00
找一个千万级的质数 p ,然后费马小定理,加一个一一对应的 hash ?
doubleflower
2016-12-10 08:39:40 +08:00
> 最糟糕的方案是纯随机,然后到表里查一遍是否重复,重复则重新生成;不重复则插入

这种方法是最好的。
dangyuluo
2016-12-10 08:52:30 +08:00
我记得有个商业提供随机数生成的网站,用的是 alpha 粒子?
Septembers
2016-12-10 09:24:54 +08:00
生成一个随机数然后 Hash (比如 SHA2) 然后截取 10 个字节
检查数据库是否历史发行过如果没有就发布有则重新生成
Septembers
2016-12-10 09:25:41 +08:00
上面说错啦是 5 个字节 转换成 integer 即可
dremy
2016-12-10 09:59:52 +08:00
@doubleflower 为啥不试试 Bloom Filter ?最佳的时间、空间效率啊,况且千万数据量就更合适了,比数据库里一条一条查不知道快到哪里去了。
realpg
2016-12-10 10:46:34 +08:00
@haython 并不是所有的优惠券系统都是每个用户只能用一张或者只能用几张的


@mokeyjay
我做过字符形式的,后来因为字符存储比较占数据库空间(数据库几个字符串占空间都考虑了你自己衡量一下商城量级吧不能透露更多了),最后变成存储纯数字,显示是字符串的(自己写了个一对一的算法把数字翻译成字符串),最后本质也是纯数字的优惠券

变更过多种生成方案,然后进行综合执行速度、还有重码分析,最后得出的方案还是单纯的 rand(1000000000000,9999999999999)的综合开销最低,远低于其他几种方案综合开销,重复率其实很低

另外,你说的没一个先 select 冲突之类的,说明你对 mysql 运用还不够精

直接用 INSERT IGNORE INTO 一大批,然后记录实际插入量,比计划的少的就是重复的,再次进行插入即可。

记录实际插入量,单点 MYSQL 可以用 AFFECTED ROWS ,我们这边是分布式的后面数据架构比较乱, AFFECTED_ROWS 不太好用,所以使用的方法是每次插入生成一个 job 做一个批次号,然后去查询这个批次已经生成多少,缺的继续补足
powergx
2016-12-10 11:42:54 +08:00
lz 建一个 pool 放好 id ,随机抽不就好了
Xrong
2016-12-10 14:28:57 +08:00
如果量不大的情况下楼主说的:『最糟糕的方案是纯随机,然后到表里查一遍是否重复,重复则重新生成;不重复则插入』就是最快最好的方法了。
Miy4mori
2016-12-11 01:37:37 +08:00
@jsjscool 同意
txlty
2016-12-11 10:20:47 +08:00
以前用这个。虽然是自己乱拼凑的方法。但经测试, 1000 万条数据重复率为 0 。随机生成则有 20+个重复。
你自己对比测试一下。
function testnum(){
$arr = explode(' ',microtime());
$num = $arr[0]*10000000000 + $arr[1] - $arr[0]*1000000;
$num = str_pad($num,11,mt_rand(0,9));
$num = str_pad($num,12,mt_rand(0,9));
return $num;
}

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

https://tanronggui.xyz/t/326514

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

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

© 2021 V2EX