V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Aluhao
V2EX  ›  PHP

求 16 位纯数字订单号产生算法

  •  
  •   Aluhao · 2020-12-26 16:35:25 +08:00 · 6896 次点击
    这是一个创建于 1418 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近业务方要求订单号不能大于 16 位,以前写的 18 位算法不能用了,改成 16 位重复的概率太高了,问问大家有什么好的算法分享!

    现在用的 18 位:

    <?php declare(strict_types=1);
    date_default_timezone_set('Etc/GMT-8');
    $time = microtime(true);
    function order()
    {
    $ids = 0;
    $aes = substr(microtime(), 2, 6);
    $pid = $aes . getmypid() . mt_rand(0, 999999);
    for($in = 0; $in < 18; $in++) {
    $ids += (int) (substr($pid, $in, 1));
    }
    return time() . $aes . str_pad(strval($ids), 2, '0', STR_PAD_LEFT);
    }

    $arr = array();
    for($i = 0; $i < 1000000; $i++) {
    $arr[] = order();
    }
    $arrs = array_count_values($arr);
    echo 'COUNT ' . count($arrs);
    echo '<br>';
    echo 'RAND ' . array_rand($arrs);
    echo '<br>';
    arsort($arrs);
    $as = [];
    foreach($arrs as $ids => $aos) {
    if ($aos > 1) {
    $as[] = $ids;
    echo $ids . ' - ' . $aos . '<br>';
    }
    }
    echo 'COUNT ' . count($as);
    echo '<br>';
    echo 'Time ' . number_format((microtime(true) - $time), 3);
    43 条回复    2020-12-28 13:09:11 +08:00
    hbolive
        1
    hbolive  
       2020-12-26 16:51:54 +08:00
    单纯依靠算法,16 还是 18 位,都没法保证吧重复吧。
    所以应该有其他机制校验下生成的 ID 是否重复。
    laminux29
        2
    laminux29  
       2020-12-26 16:55:08 +08:00
    为啥订单号就不能老老实实从 1 开始自增?为啥订单号就不能常见的无符号 32 位整型?
    Aluhao
        3
    Aluhao  
    OP
       2020-12-26 16:56:26 +08:00
    @hbolive 是有校验,产生的订单写入 Redis 了,产生的时候从 Redis 检查一次。
    pxw2002
        4
    pxw2002  
       2020-12-26 17:02:13 +08:00 via Android
    有几位根据日期时间生成
    就不会重复了吧
    bleutee
        5
    bleutee  
       2020-12-26 17:03:29 +08:00 via iPhone
    @laminux29 萬一人家想操數據呢?一五一十的順序排列,咋操
    Aluhao
        6
    Aluhao  
    OP
       2020-12-26 17:03:53 +08:00
    @pxw2002 用毫秒加随机 2 位,每秒并发 1 万容易重复的。
    Aluhao
        7
    Aluhao  
    OP
       2020-12-26 17:06:19 +08:00
    @laminux29 自增要先写数据库,很多多业务是先拿订单号请求接口成功后才写入数据库。
    还有就是自增从 1 开始,容易被人查数据。
    jimmyismagic
        8
    jimmyismagic  
       2020-12-26 17:11:59 +08:00
    业务方的订单号和你内部的订单号完全可以分开啊,重复就重复呗
    Aluhao
        9
    Aluhao  
    OP
       2020-12-26 17:14:48 +08:00
    @jimmyismagic 这个就是内部订单号,不能重复的。
    jimmyismagic
        10
    jimmyismagic  
       2020-12-26 17:22:04 +08:00
    @Aluhao 所以他到底是甲方还是乙方?乙方糊弄过去就行了,甲方就怼回去,不懂瞎指导
    bsg1992
        11
    bsg1992  
       2020-12-26 17:24:44 +08:00
    时间戳 不就解决了吗
    crclz
        12
    crclz  
       2020-12-26 17:28:38 +08:00   ❤️ 3
    你说的 16 位是“个十百千万”的位吧,不是 bit 吧。

    如果是 16 个字符,那么约束我猜就是前端 js 的 number 超过 53~54bit 就会损失精度吧。

    一个解决方案是传字符串。

    如果非要数字,也不是不可以。你去看看 snowflake 算法。https://github.com/beyondfengyu/SnowFlake/blob/master/SnowFlake.java 把第 18 行里面的几个东西改一下,就可以应对不同的位数,本质是(使用年限,机器数量,数据中心数量,每秒并发数量)的权衡。当然首先得去看看 showflake 的结构再来改。
    Jooooooooo
        13
    Jooooooooo  
       2020-12-26 17:29:29 +08:00
    用时间戳+机器码

    每个机器上维护一个当前时间戳可用的序号

    你并发不至于那么大
    Aluhao
        14
    Aluhao  
    OP
       2020-12-26 17:30:08 +08:00
    @jimmyismagic 要拿内部唯一订单号去关联对方订单信息,要先请求传 16 位订单号过去。
    xuanbg
        15
    xuanbg  
       2020-12-26 17:31:15 +08:00   ❤️ 1
    魔改雪花算法,41 位时间戳不要动,10 位设备去掉,差不多就是 16 位数字了。

    但是,我估计你要的是 yyyyMMdd+2 位业务代码+2 位 xx+4 位流水号,流水号还要加密的那种。在我的 github 里面有,请自取
    DarkCat123
        16
    DarkCat123  
       2020-12-26 17:31:32 +08:00
    @laminux29 容易被人遍历数据,分析…… 而且不利于分布式数据库的 hash 分区。

    原来的 18 位用的是 snowflake?
    我建议要不要魔改下 snowflake, 牺牲一下时间来换位数降低(我记得原来的 snowflake 可以用 69 年)。
    要不然考虑找个 md5 sha1 之类的 hash 之后转 hex 看看能不能到 16 位吧。
    Aluhao
        17
    Aluhao  
    OP
       2020-12-26 17:31:56 +08:00
    @Jooooooooo 主要是只限制 16 个数字,长一点都好解决。
    DarkCat123
        18
    DarkCat123  
       2020-12-26 17:32:06 +08:00
    @xuanbg 老哥好巧,我们同时提到了魔改 snowlflake……
    jimmyismagic
        19
    jimmyismagic  
       2020-12-26 17:34:53 +08:00
    @Aluhao 那你从 1 开始不就行了,对方唯一,需要你传的唯一,对方根本不关心你传的什么,楼上的 snowfake 一般用的很多,分布式 id 用的比较多,单机也没什么问题,16 位 50 多个比特完全够了
    dorothyREN
        20
    dorothyREN  
       2020-12-26 17:43:05 +08:00
    13 位时间戳 加 3 位随机数或者 3 位自增数字
    lmmortal
        21
    lmmortal  
       2020-12-26 17:43:26 +08:00 via Android   ❤️ 1
    可以借鉴一下身份证号最后一位的校验算法
    lin07hui
        22
    lin07hui  
       2020-12-26 18:03:19 +08:00
    10 进制转 16 进制,不香吗
    lawler
        23
    lawler  
       2020-12-26 18:23:14 +08:00
    @dorothyREN #20 就这个方案里的自增就可以了。
    @Aluhao #7 redis incr,mq


    要觉得不行,不妨把解决 10w/tps 的方案分享出来?
    要觉得可以遍历,不妨把不能遍历的纯数字索引方案分享出来?
    ytmsdy
        24
    ytmsdy  
       2020-12-26 18:31:32 +08:00 via iPhone
    时间戳,加两个随机数,外加一个校验位。绰绰有余了!
    yuzo555
        25
    yuzo555  
       2020-12-26 18:40:12 +08:00
    “容易被人遍历数据”这个不是自增的问题,是你们程序权限控制的问题
    Soar360
        26
    Soar360  
       2020-12-26 19:49:48 +08:00
    20-1226-19-123456
    dorothyREN
        27
    dorothyREN  
       2020-12-26 20:16:37 +08:00
    @lawler #23 13 位时间戳+3 位数字,1 毫秒 1000 个单号,肯定够用了
    jzmws
        28
    jzmws  
       2020-12-26 20:45:54 +08:00
    @crclz 用雪花自己技术不过硬 然后 js 超出精度 坑死自己了 , 用这个算出来的是会操作 js 精度的
    jimmyismagic
        29
    jimmyismagic  
       2020-12-26 21:24:38 +08:00
    @jzmws js 太傻了,前端技术不行让后端想办法
    dream7758522
        30
    dream7758522  
       2020-12-26 21:41:24 +08:00 via Android   ❤️ 1
    很简单啊,时间戳→md5,16 位。→生成的 md5 中的 abcdef 用 123456 改写。
    举例:0123456789012345→md5,16 位→199aa7dcadfdb4e4→其中的字母字母用 123456 替换→1991174314642454 。
    觉得容易反算的话,可以加盐
    lrvinye
        31
    lrvinye  
       2020-12-26 22:31:17 +08:00
    @laminux29 #2 自增订单号会被竞争对手分析流水
    Lemeng
        32
    Lemeng  
       2020-12-27 00:56:21 +08:00
    重复 16 位 18 位都少。
    catror
        33
    catror  
       2020-12-27 01:36:49 +08:00 via Android
    任何一个生成 int64 ID 的算法都行,int64 的 16 进制编码结果就是 16 位
    mxT52CRuqR6o5
        34
    mxT52CRuqR6o5  
       2020-12-27 02:25:49 +08:00 via Android
    加密一个自增序列可以得到看上去随机的序列,不会重复,魔改加密算法让密文空间和明文空间在 16 位纯数字内
    mxT52CRuqR6o5
        35
    mxT52CRuqR6o5  
       2020-12-27 02:26:55 +08:00 via Android
    要是没有随机性的要求的话直接魔改 snowflake 就是了
    hijoker
        36
    hijoker  
       2020-12-27 04:09:34 +08:00
    13 位毫秒数+3 位随机数, 数据库这个字段设置类似唯一约束的东西, 如果真有重复,插入报错,根据报错信息重新生成随机数并重试插入
    oneisall8955
        37
    oneisall8955  
       2020-12-27 09:28:48 +08:00 via Android
    @jzmws #28 我公司 long 类型都转字符串出去给前端了
    mostkia
        38
    mostkia  
       2020-12-27 12:38:31 +08:00
    一般都时间戳+盐保证唯一性
    neptuno
        39
    neptuno  
       2020-12-27 13:46:31 +08:00
    时间戳+(用户 id 转成 3 位数)?是不是最简单+重复率低
    ciddechan
        40
    ciddechan  
       2020-12-28 09:04:41 +08:00
    @dream7758522 这种的会不会分不出是真的 123456 还是替换的 123456
    wangritian
        41
    wangritian  
       2020-12-28 09:29:13 +08:00
    1.雪花算法:毫秒时间戳+集群号+机器号+进程号+自增序号
    这里最关键的是进程常驻,需要 swoole/workerman 插件
    2.额外写一个单实例部署的 id 生成器,加锁单线程运行,性能没有 1 好
    nano91
        42
    nano91  
       2020-12-28 10:18:32 +08:00
    16 位 MD5 ?拿老 18 位订单号做一次转换
    afewok
        43
    afewok  
       2020-12-28 13:09:11 +08:00
    但凡深入调研下业界和 github,都不会问出这个问题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5699 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 03:20 · PVG 11:20 · LAX 19:20 · JFK 22:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.