资讯专栏INFORMATION COLUMN

关于生成订单号规则的一些思考

omgdog / 3333人阅读

摘要:关于我为什么写这篇文章是因为今天在做订单模块的时候看到之前的上描述的年月日用户位企业位四位自增长数。背景对于其定订单的生成。个人的看法是主要是唯一,其他关于业务方面的不是太太重要。自增实现了用于将的值递增,并返回结果。

关于我为什么写这篇文章是因为今天在做订单模块的时候,看到之前的PRD上描述的年月日+用户id2位+企业id位
+四位自增长数。然后竟被我反驳的突然改成了精确时间+4位自增长数,于是我更失望了。

我们考虑一下,据我所常见的订单基本都14-20位。(年月日时分秒和随机数)基本上就有14位了。虽然一般项目做不到淘宝双11这种
支付峰值达到每秒10万笔订单.但是我觉得至少事先可以考虑到,想必当初淘宝或许也没意识到以后发展
得这么好。

背景

对于其定订单的生成。我觉得要至少要符合以下这三种,全局唯一 ,

在复杂的分布式系统中,很多场景需要的都是全局唯一ID的场景,一般为了防止冲突可以考虑的有36
位的UUID,twitter的snowflake等。

但是可以思考这些问题?

是不是应该有一些其他意义的思考,比如说订单系统有买家的id(取固定几位)

是否有商品的标识,方便熟悉业务的排查问题或者查询也通过不去系统查找可以有个初步的认识,但是业务量大的话感觉就可以排除这个人为的去辨识了。

个人的看法是主要是唯一,其他关于业务方面的不是太太重要。

查阅了相关资料,主要有以下这几种

1.UUID
组成:当前日期+时间+时钟序列+机器识别号(Mac地址或其他)没有mac网卡的话会有别的东西识别。
在分布式系统中,所有元素(WEB服务器)都不需要通过中央控制端来判断数据唯一性。几十年之内可以达到全球唯一性。
snowflake的结构如下(每部分用-分开):

2.Mysql通过AUTO_INCREMENT实现、Oracle通过Sequence序列实现。
在数据库集群环境下,不同数据库节点可设置不同起步值、相同步长来实现集群下生产全局唯一、递增ID

3.Snowflake算法 雪花算法 
41位时间戳+10位机器ID+12位序列号(自增) 转化长度为18位的长整型。
Twitter为满足美秒上万条消息的创建,且ID需要趋势递增,方便客户端排序。
Snowflake虽然有同步锁,但是比uuid效率高。

4.Redis自增ID
实现了incr(key)用于将key的值递增1,并返回结果。如果key不存在,创建默认并赋值为0。 具有原子性,保证在并发的时候。

但是我在这主要想说的是雪花算法生成id

关于序列
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)

一共加起来刚好64位,为一个Long型。(转换成字符串长度为18)

snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。据说:snowflake每秒能够产生26万个ID。

以下是代码
部分借鉴与网络
100万个ID 耗时2秒

/**
 * Created by youze on 18-7-5
 */
public class IdWorker {

    /**
     * 起始的时间戳
     */
    private final static long START_STMP = 1530795377086L;

    /**
     * 每一部分占用的位数
     */

    /**
     * 序列号占用的位数
     */
    private final static long SEQUENCE_BIT = 12;

    /**
     * 机器标识占用的位数
     */
    private final static long MACHINE_BIT = 5;

    /**
     * 数据中心占用的位数
     */
    private final static long DATACENTER_BIT = 5;

    /**
     * 每一部分的最大值
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    /**
     * 数据中心
     */
    private long datacenterId;

    /**
     * 机器标识
     */
    private long machineId;
    /**
     * 序列号
     */
    private long sequence = 0L;

    /**
     * 上一次时间戳
     */
    private long lastStmp = -1L;

    public IdWorker(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can"t be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId can"t be greater than MAX_MACHINE_NUM or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    /**
     * 产生下一个ID
     * @return
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currStmp == lastStmp) {
            //相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            //不同毫秒内,序列号置为0
            sequence = 0L;
        }

        lastStmp = currStmp;

        return (
                //时间戳部分
                currStmp - START_STMP) << TIMESTMP_LEFT
                //数据中心部分
                | datacenterId << DATACENTER_LEFT
                //机器标识部分
                | machineId << MACHINE_LEFT
                //序列号部分
                | sequence;
    }

    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        IdWorker snowFlake = new IdWorker(2, 3);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            System.out.println(snowFlake.nextId());
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71472.html

相关文章

  • 聚合Aggregate

    摘要:每个事物的范围限定在单个聚合内。当然,记住仅仅因为是两个表的关系设计不易任何方式表明他们是两个聚合。一个捕获这个事件,并在每个指定的聚合上执行命令。尽管如此,不得不诉诸于此解决方案,这表明您的聚合的总体边界并不正确。 什么是聚合: 聚合是一个更大的封装单位,而不仅仅是一个类。每个事物的范围限定在单个聚合内。聚合组件的使用期被界限在整个聚合的生命周期中。 具体的,一个聚合将会处理命令,请...

    garfileo 评论0 收藏0
  • 2016年终工作总结

    摘要:由于初版需求及开发工作都没有参与,在接手项目后过了遍前端结构发现所有交互及组件都是现撸,并未使用市面上已有的优秀前端框架从我个人角度理解上出发,后续需求变更中当需要实现某些常用组件样式或交互时,基本上都需要现撸或者寻找合适的组件。 2016悄无声息的过去了,再过不久便是农历新年 这几天相对清闲梳理了一下去年所做的工作,希望在新的一年能发展的更好 今年一共研发或升级了五款产品:合伙人、夺...

    hoohack 评论0 收藏0
  • 分布式幂等问题解决方案三部曲

    摘要:解决幂等问题的三部曲,也是作者的思考框架。这是解决幂等问题的第二部曲列出并减少副作用的分析维度。所以在并发执行的维度,将并发重复执行变成串行重复执行是最好的幂等解决方案。 纲要 文章目的:本文旨在提炼一套分布式幂等问题的思考框架,而非解决某个具体的分布式幂等问题。在这个框架体系内,会有一些方案举例说明。文章目标:希望读者能通过这套思考框架设计出符合自己业务的完备的幂等解决方案。文章内容...

    mumumu 评论0 收藏0
  • 由「抽象一致性」思考如何书写可读性代码

    摘要:结论二从不同的角度去看抽象一致性得出的结论是不一样的只有最符合现有业务的没有最正确的说明三对于实现一方法是查询用户的消费信息而这里的详细计算订单金额直接写在这个方法里面抽象层次十分混乱所以实现一是不推荐的。 场景 用户实体User 通过用户Id查找用户的订单信息接口List orders = orderService.queryOrders(long userId) 通过用户Id查找...

    xiaodao 评论0 收藏0
  • 2017--年度个人总结

    摘要:离职新路线年的总结在这里年总结,其实在发布这个文章之前,就已经跟阿里那边再谈新的,会以的级别入职阿里闲鱼部门。总所周知,我司在月份调整了一次架构,具体如下美团调整了组织架构。 17年的总结来的更晚一点,其实是一直在犹豫要不要写,主要感觉去年一年折腾的有点凶残,连续换工作以及地点,一路走来有纠结,有痛苦,有快乐,有兴奋,有迷茫,有得有失,所以想了很久,还是来记录下这一年的关键点。 离职 ...

    2450184176 评论0 收藏0

发表评论

0条评论

omgdog

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<